View Javadoc
1   /*
2    * Portions of this software was developed by employees of the National Institute
3    * of Standards and Technology (NIST), an agency of the Federal Government and is
4    * being made available as a public service. Pursuant to title 17 United States
5    * Code Section 105, works of NIST employees are not subject to copyright
6    * protection in the United States. This software may be subject to foreign
7    * copyright. Permission in the United States and in foreign countries, to the
8    * extent that NIST may hold copyright, to use, copy, modify, create derivative
9    * works, and distribute this software and its documentation without fee is hereby
10   * granted on a non-exclusive basis, provided that this notice and disclaimer
11   * of warranty appears in all copies.
12   *
13   * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
14   * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
15   * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
16   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
17   * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
18   * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
19   * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
20   * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
21   * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
22   * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
23   * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
24   * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
25   */
26  
27  package gov.nist.secauto.metaschema.databind.model;
28  
29  import gov.nist.secauto.metaschema.core.model.AbstractModule;
30  import gov.nist.secauto.metaschema.core.model.IAssemblyDefinition;
31  import gov.nist.secauto.metaschema.core.model.IFieldDefinition;
32  import gov.nist.secauto.metaschema.core.model.IFlagDefinition;
33  import gov.nist.secauto.metaschema.core.model.IModule;
34  import gov.nist.secauto.metaschema.core.util.CollectionUtil;
35  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
36  import gov.nist.secauto.metaschema.databind.IBindingContext;
37  import gov.nist.secauto.metaschema.databind.model.annotations.Module;
38  
39  import java.lang.reflect.Constructor;
40  import java.lang.reflect.InvocationTargetException;
41  import java.net.URI;
42  import java.util.ArrayList;
43  import java.util.Arrays;
44  import java.util.Collection;
45  import java.util.Collections;
46  import java.util.List;
47  import java.util.Map;
48  import java.util.function.Function;
49  import java.util.stream.Collectors;
50  
51  import edu.umd.cs.findbugs.annotations.NonNull;
52  
53  public abstract class AbstractBoundModule
54      extends AbstractModule {
55    @NonNull
56    private final IBindingContext bindingContext;
57    private Map<String, IAssemblyClassBinding> assemblyDefinitions;
58    private Map<String, IFieldClassBinding> fieldDefinitions;
59  
60    /**
61     * Create a new Module instance for a given class annotated by the
62     * {@link Module} annotation.
63     * <p>
64     * Will also load any imported Metaschemas.
65     *
66     *
67     * @param clazz
68     *          the Module class
69     * @param bindingContext
70     *          the Module binding context
71     * @return the new Module instance
72     */
73    @NonNull
74    public static IModule createInstance(
75        @NonNull Class<? extends IModule> clazz,
76        @NonNull IBindingContext bindingContext) {
77  
78      if (!clazz.isAnnotationPresent(Module.class)) {
79        throw new IllegalStateException(String.format("The class '%s' is missing the '%s' annotation",
80            clazz.getCanonicalName(), Module.class.getCanonicalName()));
81      }
82  
83      Module moduleAnnotation = clazz.getAnnotation(Module.class);
84  
85      List<IModule> importedModules;
86      if (moduleAnnotation.imports().length > 0) {
87        importedModules = new ArrayList<>(moduleAnnotation.imports().length);
88        for (Class<? extends IModule> importClass : moduleAnnotation.imports()) {
89          assert importClass != null;
90          IModule moduleImport = bindingContext.getModuleByClass(importClass);
91          importedModules.add(moduleImport);
92        }
93      } else {
94        importedModules = CollectionUtil.emptyList();
95      }
96      return createInstance(clazz, bindingContext, importedModules);
97    }
98  
99    @NonNull
100   private static IModule createInstance(
101       @NonNull Class<? extends IModule> clazz,
102       @NonNull IBindingContext bindingContext,
103       @NonNull List<? extends IModule> importedModules) {
104 
105     Constructor<? extends IModule> constructor;
106     try {
107       constructor = clazz.getDeclaredConstructor(List.class, IBindingContext.class);
108     } catch (NoSuchMethodException ex) {
109       throw new IllegalArgumentException(ex);
110     }
111 
112     try {
113       return ObjectUtils.notNull(constructor.newInstance(importedModules, bindingContext));
114     } catch (InstantiationException | IllegalAccessException | InvocationTargetException ex) {
115       throw new IllegalArgumentException(ex);
116     }
117   }
118 
119   /**
120    * Construct a new Module instance.
121    *
122    * @param importedModules
123    *          Module imports associated with the Metaschema module
124    * @param bindingContext
125    *          the Module binding context
126    */
127   protected AbstractBoundModule(
128       @NonNull List<? extends IModule> importedModules,
129       @NonNull IBindingContext bindingContext) {
130     super(importedModules);
131     this.bindingContext = bindingContext;
132   }
133 
134   /**
135    * Get the Module binding context.
136    *
137    * @return the context
138    */
139   @NonNull
140   protected IBindingContext getBindingContext() {
141     return bindingContext;
142   }
143 
144   @Override
145   public URI getLocation() { // NOPMD - intentional
146     // not known
147     return null;
148   }
149 
150   @NonNull
151   protected Class<?>[] getAssemblyClasses() {
152     Class<?>[] retval;
153     if (getClass().isAnnotationPresent(Module.class)) {
154       Module moduleAnnotation = getClass().getAnnotation(Module.class);
155       retval = moduleAnnotation.assemblies();
156     } else {
157       retval = new Class<?>[] {};
158     }
159     return retval;
160   }
161 
162   @NonNull
163   protected Class<?>[] getFieldClasses() {
164     Class<?>[] retval;
165     if (getClass().isAnnotationPresent(Module.class)) {
166       Module moduleAnnotation = getClass().getAnnotation(Module.class);
167       retval = moduleAnnotation.fields();
168     } else {
169       retval = new Class<?>[] {};
170     }
171     return retval;
172   }
173 
174   protected void initDefinitions() {
175     synchronized (this) {
176       if (assemblyDefinitions == null) {
177         IBindingContext bindingContext = getBindingContext();
178         this.assemblyDefinitions = Arrays.stream(getAssemblyClasses())
179             .map(clazz -> {
180               assert clazz != null;
181               return (IAssemblyClassBinding) ObjectUtils.requireNonNull(bindingContext.getClassBinding(clazz));
182             })
183             .collect(Collectors.toUnmodifiableMap(
184                 IAssemblyClassBinding::getName,
185                 Function.identity()));
186         this.fieldDefinitions = Arrays.stream(getFieldClasses())
187             .map(clazz -> {
188               assert clazz != null;
189               return (IFieldClassBinding) ObjectUtils.requireNonNull(bindingContext.getClassBinding(clazz));
190             })
191             .collect(Collectors.toUnmodifiableMap(
192                 IFieldClassBinding::getName,
193                 Function.identity()));
194       }
195     }
196 
197   }
198 
199   @SuppressWarnings("null")
200   protected @NonNull Map<String, ? extends IAssemblyDefinition> getAssemblyDefinitionMap() {
201     initDefinitions();
202     return assemblyDefinitions;
203   }
204 
205   @SuppressWarnings("null")
206   @Override
207   public Collection<? extends IAssemblyDefinition> getAssemblyDefinitions() {
208     return getAssemblyDefinitionMap().values();
209   }
210 
211   @Override
212   public IAssemblyDefinition getAssemblyDefinitionByName(@NonNull String name) {
213     return getAssemblyDefinitionMap().get(name);
214   }
215 
216   protected Map<String, ? extends IFieldDefinition> getFieldDefinitionMap() {
217     initDefinitions();
218     return fieldDefinitions;
219   }
220 
221   @SuppressWarnings("null")
222   @Override
223   public Collection<? extends IFieldDefinition> getFieldDefinitions() {
224     return getFieldDefinitionMap().values();
225   }
226 
227   @Override
228   public IFieldDefinition getFieldDefinitionByName(@NonNull String name) {
229     return getFieldDefinitionMap().get(name);
230   }
231 
232   @NonNull
233   public Map<String, ? extends IFlagDefinition> getFlagDefinitionMap() {
234     // FlagContainer are always inline
235     return CollectionUtil.emptyMap();
236   }
237 
238   @SuppressWarnings("null")
239   @Override
240   public Collection<? extends IFlagDefinition> getFlagDefinitions() {
241     // FlagContainer are always inline
242     return Collections.emptyList();
243   }
244 
245   @Override
246   public IFlagDefinition getFlagDefinitionByName(@NonNull String name) { // NOPMD - intentional
247     // FlagContainer are always inline
248     return null;
249   }
250 }