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.codegen.config;
28  
29  import gov.nist.secauto.metaschema.core.model.IAssemblyDefinition;
30  import gov.nist.secauto.metaschema.core.model.IFieldDefinition;
31  import gov.nist.secauto.metaschema.core.model.IFlagContainer;
32  import gov.nist.secauto.metaschema.core.model.IModule;
33  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
34  import gov.nist.secauto.metaschema.databind.codegen.ClassUtils;
35  import gov.nist.secauto.metaschema.databind.codegen.xmlbeans.JavaModelBindingType;
36  import gov.nist.secauto.metaschema.databind.codegen.xmlbeans.JavaObjectDefinitionBindingType;
37  import gov.nist.secauto.metaschema.databind.codegen.xmlbeans.MetaschemaBindingType;
38  import gov.nist.secauto.metaschema.databind.codegen.xmlbeans.MetaschemaBindingsDocument;
39  import gov.nist.secauto.metaschema.databind.codegen.xmlbeans.MetaschemaBindingsType;
40  import gov.nist.secauto.metaschema.databind.codegen.xmlbeans.ModelBindingType;
41  import gov.nist.secauto.metaschema.databind.codegen.xmlbeans.ObjectDefinitionBindingType;
42  
43  import org.apache.xmlbeans.XmlException;
44  
45  import java.io.File;
46  import java.io.IOException;
47  import java.net.MalformedURLException;
48  import java.net.URI;
49  import java.net.URISyntaxException;
50  import java.net.URL;
51  import java.nio.file.Path;
52  import java.util.Map;
53  import java.util.Objects;
54  import java.util.concurrent.ConcurrentHashMap;
55  
56  import edu.umd.cs.findbugs.annotations.NonNull;
57  import edu.umd.cs.findbugs.annotations.Nullable;
58  
59  public class DefaultBindingConfiguration implements IBindingConfiguration {
60    private final Map<String, String> namespaceToPackageNameMap = new ConcurrentHashMap<>();
61    // metaschema location -> ModelType -> Definition Name -> IBindingConfiguration
62    private final Map<String, MetaschemaBindingConfiguration> moduleUrlToMetaschemaBindingConfigurationMap
63        = new ConcurrentHashMap<>();
64  
65    @Override
66    public String getPackageNameForModule(IModule module) {
67      URI namespace = module.getXmlNamespace();
68      return getPackageNameForNamespace(ObjectUtils.notNull(namespace.toASCIIString()));
69    }
70  
71    /**
72     * Retrieve the binding configuration for the provided {@code definition}.
73     *
74     * @param definition
75     *          the definition to get the config for
76     * @return the binding configuration or {@code null} if there is not
77     *         configuration
78     */
79    @Nullable
80    public IDefinitionBindingConfiguration getBindingConfigurationForDefinition(
81        @NonNull IFlagContainer definition) {
82      String moduleUri = ObjectUtils.notNull(definition.getContainingModule().getLocation().toString());
83      String definitionName = definition.getName();
84  
85      MetaschemaBindingConfiguration metaschemaConfig = getMetaschemaBindingConfiguration(moduleUri);
86  
87      IDefinitionBindingConfiguration retval = null;
88      if (metaschemaConfig != null) {
89        switch (definition.getModelType()) {
90        case ASSEMBLY:
91          retval = metaschemaConfig.getAssemblyDefinitionBindingConfig(definitionName);
92          break;
93        case FIELD:
94          retval = metaschemaConfig.getFieldDefinitionBindingConfig(definitionName);
95          break;
96        default:
97          throw new UnsupportedOperationException(
98              String.format("Unsupported definition type '%s'", definition.getModelType()));
99        }
100     }
101     return retval;
102   }
103 
104   @Override
105   public String getQualifiedBaseClassName(IFlagContainer definition) {
106     IDefinitionBindingConfiguration config = getBindingConfigurationForDefinition(definition);
107 
108     String retval = null;
109     if (config != null) {
110       retval = config.getQualifiedBaseClassName();
111     }
112     return retval;
113   }
114 
115   @Override
116   public String getClassName(IFlagContainer definition) {
117     IDefinitionBindingConfiguration config = getBindingConfigurationForDefinition(definition);
118 
119     String retval = null;
120     if (config != null) {
121       retval = config.getClassName();
122     }
123 
124     if (retval == null) {
125       retval = ClassUtils.toClassName(definition.getName());
126     }
127     return retval;
128   }
129 
130   @Override
131   public @NonNull String getClassName(@NonNull IModule module) {
132     // TODO: make this configurable
133     return ClassUtils.toClassName(module.getShortName() + "Module");
134   }
135 
136   /**
137    * Binds an XML namespace, which is normally associated with one or more Module,
138    * with a provided Java package name.
139    *
140    * @param namespace
141    *          an XML namespace URI
142    * @param packageName
143    *          the package name to associate with the namespace
144    * @throws IllegalStateException
145    *           if the binding configuration is changing a previously changed
146    *           namespace to package binding
147    */
148   public void addModelBindingConfig(String namespace, String packageName) {
149     if (namespaceToPackageNameMap.containsKey(namespace)) {
150       String oldPackageName = namespaceToPackageNameMap.get(namespace);
151       if (!oldPackageName.equals(packageName)) {
152         throw new IllegalStateException(
153             String.format("Attempt to redefine existing package name '%s' to '%s' for namespace '%s'",
154                 oldPackageName,
155                 packageName,
156                 namespace));
157       } // else the same package name, so do nothing
158     } else {
159       namespaceToPackageNameMap.put(namespace, packageName);
160     }
161   }
162 
163   /**
164    * Based on the current binding configuration, generate a Java package name for
165    * the provided namespace. If the namespace is already mapped, such as through
166    * the use of {@link #addModelBindingConfig(String, String)}, then the provided
167    * package name will be used. If the namespace is not mapped, then the namespace
168    * URI will be translated into a Java package name.
169    *
170    * @param namespace
171    *          the namespace to generate a Java package name for
172    * @return a Java package name
173    */
174   @NonNull
175   protected String getPackageNameForNamespace(@NonNull String namespace) {
176     String packageName = namespaceToPackageNameMap.get(namespace);
177     if (packageName == null) {
178       packageName = ClassUtils.toPackageName(namespace);
179     }
180     return packageName;
181   }
182 
183   /**
184    * Get the binding configuration for the provided Module.
185    *
186    * @param module
187    *          the Module module
188    * @return the configuration for the Module or {@code null} if there is no
189    *         configuration
190    */
191   protected MetaschemaBindingConfiguration getMetaschemaBindingConfiguration(@NonNull IModule module) {
192     String moduleUri = ObjectUtils.notNull(module.getLocation().toString());
193     return getMetaschemaBindingConfiguration(moduleUri);
194 
195   }
196 
197   /**
198    * Get the binding configuration for the Module modulke located at the provided
199    * {@code moduleUri}.
200    *
201    * @param moduleUri
202    *          the location of the Module module
203    * @return the configuration for the Module module or {@code null} if there is
204    *         no configuration
205    */
206   @Nullable
207   protected MetaschemaBindingConfiguration getMetaschemaBindingConfiguration(@NonNull String moduleUri) {
208     return moduleUrlToMetaschemaBindingConfigurationMap.get(moduleUri);
209   }
210 
211   /**
212    * Set the binding configuration for the Module module located at the provided
213    * {@code moduleUri}.
214    *
215    * @param moduleUri
216    *          the location of the Module module
217    * @param config
218    *          the Module binding configuration
219    * @return the old configuration for the Module module or {@code null} if there
220    *         was no previous configuration
221    */
222   public MetaschemaBindingConfiguration addMetaschemaBindingConfiguration(
223       @NonNull String moduleUri,
224       @NonNull MetaschemaBindingConfiguration config) {
225     Objects.requireNonNull(moduleUri, "moduleUri");
226     Objects.requireNonNull(config, "config");
227     return moduleUrlToMetaschemaBindingConfigurationMap.put(moduleUri, config);
228   }
229 
230   /**
231    * Load the binding configuration from the provided {@code file}.
232    *
233    * @param file
234    *          the configuration resource
235    * @throws IOException
236    *           if an error occurred while reading the {@code file}
237    */
238   public void load(Path file) throws IOException {
239     URL resource = file.toUri().toURL();
240     load(resource);
241   }
242 
243   /**
244    * Load the binding configuration from the provided {@code file}.
245    *
246    * @param file
247    *          the configuration resource
248    * @throws IOException
249    *           if an error occurred while reading the {@code file}
250    */
251   public void load(File file) throws IOException {
252     URL resource = file.toURI().toURL();
253     load(resource);
254   }
255 
256   /**
257    * Load the binding configuration from the provided {@code resource}.
258    *
259    * @param resource
260    *          the configuration resource
261    * @throws IOException
262    *           if an error occurred while reading the {@code resource}
263    */
264   public void load(URL resource) throws IOException {
265     MetaschemaBindingsDocument xml;
266     try {
267       xml = MetaschemaBindingsDocument.Factory.parse(resource);
268     } catch (XmlException ex) {
269       throw new IOException(ex);
270     }
271 
272     MetaschemaBindingsType bindings = xml.getMetaschemaBindings();
273 
274     for (ModelBindingType model : bindings.getModelBindingList()) {
275       processModelBindingConfig(model);
276     }
277 
278     for (MetaschemaBindingType metaschema : bindings.getMetaschemaBindingList()) {
279       try {
280         processMetaschemaBindingConfig(resource, metaschema);
281       } catch (MalformedURLException | URISyntaxException ex) {
282         throw new IOException(ex);
283       }
284     }
285   }
286 
287   private void processModelBindingConfig(ModelBindingType model) {
288     String namespace = model.getNamespace();
289 
290     if (model.isSetJava()) {
291       JavaModelBindingType java = model.getJava();
292       if (java.isSetUsePackageName()) {
293         addModelBindingConfig(namespace, java.getUsePackageName());
294       }
295     }
296   }
297 
298   private void processMetaschemaBindingConfig(URL configResource, MetaschemaBindingType metaschema)
299       throws MalformedURLException, URISyntaxException {
300     String href = metaschema.getHref();
301     URL moduleUrl = new URL(configResource, href);
302     String moduleUri = ObjectUtils.notNull(moduleUrl.toURI().toString());
303 
304     MetaschemaBindingConfiguration metaschemaConfig = getMetaschemaBindingConfiguration(moduleUri);
305     if (metaschemaConfig == null) {
306       metaschemaConfig = new MetaschemaBindingConfiguration();
307       addMetaschemaBindingConfiguration(moduleUri, metaschemaConfig);
308     }
309     for (ObjectDefinitionBindingType assemblyBinding : metaschema.getDefineAssemblyBindingList()) {
310       String name = ObjectUtils.requireNonNull(assemblyBinding.getName());
311       IDefinitionBindingConfiguration config = metaschemaConfig.getAssemblyDefinitionBindingConfig(name);
312       config = processDefinitionBindingConfiguration(config, assemblyBinding);
313       metaschemaConfig.addAssemblyDefinitionBindingConfig(name, config);
314     }
315 
316     for (ObjectDefinitionBindingType fieldBinding : metaschema.getDefineFieldBindingList()) {
317       String name = ObjectUtils.requireNonNull(fieldBinding.getName());
318       IDefinitionBindingConfiguration config = metaschemaConfig.getFieldDefinitionBindingConfig(name);
319       config = processDefinitionBindingConfiguration(config, fieldBinding);
320       metaschemaConfig.addFieldDefinitionBindingConfig(name, config);
321     }
322   }
323 
324   @NonNull
325   private static IMutableDefinitionBindingConfiguration processDefinitionBindingConfiguration(
326       @Nullable IDefinitionBindingConfiguration oldConfig,
327       @NonNull ObjectDefinitionBindingType objectDefinitionBinding) {
328     IMutableDefinitionBindingConfiguration config;
329     if (oldConfig != null) {
330       config = new DefaultDefinitionBindingConfiguration(oldConfig);
331     } else {
332       config = new DefaultDefinitionBindingConfiguration();
333     }
334 
335     if (objectDefinitionBinding.isSetJava()) {
336       JavaObjectDefinitionBindingType java = objectDefinitionBinding.getJava();
337       if (java.isSetUseClassName()) {
338         config.setClassName(ObjectUtils.notNull(java.getUseClassName()));
339       }
340 
341       if (java.isSetExtendBaseClass()) {
342         config.setQualifiedBaseClassName(ObjectUtils.notNull(java.getExtendBaseClass()));
343       }
344 
345       for (String interfaceName : java.getImplementInterfaceList()) {
346         config.addInterfaceToImplement(ObjectUtils.notNull(interfaceName));
347       }
348     }
349     return config;
350   }
351 
352   public static final class MetaschemaBindingConfiguration {
353     private final Map<String, IDefinitionBindingConfiguration> assemblyBindingConfigs = new ConcurrentHashMap<>();
354     private final Map<String, IDefinitionBindingConfiguration> fieldBindingConfigs = new ConcurrentHashMap<>();
355 
356     private MetaschemaBindingConfiguration() {
357     }
358 
359     /**
360      * Get the binding configuration for the {@link IAssemblyDefinition} with the
361      * provided {@code name}.
362      *
363      * @param name
364      *          the definition name
365      * @return the definition's binding configuration or {@code null} if no
366      *         configuration is provided
367      */
368     @Nullable
369     public IDefinitionBindingConfiguration getAssemblyDefinitionBindingConfig(@NonNull String name) {
370       return assemblyBindingConfigs.get(name);
371     }
372 
373     /**
374      * Get the binding configuration for the {@link IFieldDefinition} with the
375      * provided {@code name}.
376      *
377      * @param name
378      *          the definition name
379      * @return the definition's binding configuration or {@code null} if no
380      *         configuration is provided
381      */
382     @Nullable
383     public IDefinitionBindingConfiguration getFieldDefinitionBindingConfig(@NonNull String name) {
384       return fieldBindingConfigs.get(name);
385     }
386 
387     /**
388      * Set the binding configuration for the {@link IAssemblyDefinition} with the
389      * provided {@code name}.
390      *
391      * @param name
392      *          the definition name
393      * @param config
394      *          the new binding configuration for the definition
395      * @return the definition's old binding configuration or {@code null} if no
396      *         configuration was previously provided
397      */
398     @Nullable
399     public IDefinitionBindingConfiguration addAssemblyDefinitionBindingConfig(@NonNull String name,
400         @NonNull IDefinitionBindingConfiguration config) {
401       return assemblyBindingConfigs.put(name, config);
402     }
403 
404     /**
405      * Set the binding configuration for the {@link IFieldDefinition} with the
406      * provided {@code name}.
407      *
408      * @param name
409      *          the definition name
410      * @param config
411      *          the new binding configuration for the definition
412      * @return the definition's old binding configuration or {@code null} if no
413      *         configuration was previously provided
414      */
415     @Nullable
416     public IDefinitionBindingConfiguration addFieldDefinitionBindingConfig(@NonNull String name,
417         @NonNull IDefinitionBindingConfiguration config) {
418       return fieldBindingConfigs.put(name, config);
419     }
420   }
421 }