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.core.model;
28  
29  import gov.nist.secauto.metaschema.core.util.CollectionUtil;
30  import gov.nist.secauto.metaschema.core.util.CustomCollectors;
31  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
32  
33  import org.apache.logging.log4j.LogManager;
34  import org.apache.logging.log4j.Logger;
35  
36  import java.util.Collection;
37  import java.util.List;
38  import java.util.Locale;
39  import java.util.Map;
40  import java.util.function.Function;
41  import java.util.function.Predicate;
42  import java.util.stream.Collectors;
43  import java.util.stream.Stream;
44  
45  import edu.umd.cs.findbugs.annotations.NonNull;
46  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
47  
48  /**
49   * Provides a common, abstract implementation of a {@link IModule}.
50   */
51  @SuppressWarnings("PMD.CouplingBetweenObjects")
52  public abstract class AbstractModule
53      implements IModule {
54    private static final Logger LOGGER = LogManager.getLogger(AbstractModule.class);
55  
56    @NonNull
57    private final List<? extends IModule> importedModules;
58    private Map<String, IFlagDefinition> exportedFlagDefinitions;
59    private Map<String, IFieldDefinition> exportedFieldDefinitions;
60    private Map<String, IAssemblyDefinition> exportedAssemblyDefinitions;
61  
62    /**
63     * Construct a new Metaschema module object.
64     *
65     * @param importedModules
66     *          the collection of Metaschema module objects this Metaschema module
67     *          imports
68     */
69    public AbstractModule(@NonNull List<? extends IModule> importedModules) {
70      this.importedModules
71          = CollectionUtil.unmodifiableList(ObjectUtils.requireNonNull(importedModules, "importedModules"));
72    }
73  
74    @Override
75    @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "interface doesn't allow modification")
76    public List<? extends IModule> getImportedModules() {
77      return importedModules;
78    }
79  
80    private Map<String, ? extends IModule> getImportedModulesByShortName() {
81      return importedModules.stream().collect(Collectors.toMap(IModule::getShortName, Function.identity()));
82    }
83  
84    @Override
85    public IModule getImportedModuleByShortName(String name) {
86      return getImportedModulesByShortName().get(name);
87    }
88  
89    @SuppressWarnings("null")
90    @Override
91    public Collection<? extends IFlagDefinition> getExportedFlagDefinitions() {
92      return getExportedFlagDefinitionMap().values();
93    }
94  
95    @Override
96    public IFlagDefinition getExportedFlagDefinitionByName(String name) {
97      return getExportedFlagDefinitionMap().get(name);
98    }
99  
100   private Map<String, ? extends IFlagDefinition> getExportedFlagDefinitionMap() {
101     initExports();
102     return exportedFlagDefinitions;
103   }
104 
105   @SuppressWarnings("null")
106   @Override
107   public Collection<? extends IFieldDefinition> getExportedFieldDefinitions() {
108     return getExportedFieldDefinitionMap().values();
109   }
110 
111   @Override
112   public IFieldDefinition getExportedFieldDefinitionByName(String name) {
113     return getExportedFieldDefinitionMap().get(name);
114   }
115 
116   private Map<String, ? extends IFieldDefinition> getExportedFieldDefinitionMap() {
117     initExports();
118     return exportedFieldDefinitions;
119   }
120 
121   @SuppressWarnings("null")
122   @Override
123   public Collection<? extends IAssemblyDefinition> getExportedAssemblyDefinitions() {
124     return getExportedAssemblyDefinitionMap().values();
125   }
126 
127   @Override
128   public IAssemblyDefinition getExportedAssemblyDefinitionByName(String name) {
129     return getExportedAssemblyDefinitionMap().get(name);
130   }
131 
132   private Map<String, ? extends IAssemblyDefinition> getExportedAssemblyDefinitionMap() {
133     initExports();
134     return exportedAssemblyDefinitions;
135   }
136 
137   /**
138    * Processes the definitions exported by the Metaschema, saving a list of all
139    * exported by specific model types.
140    */
141   protected void initExports() {
142     synchronized (this) {
143       if (exportedFlagDefinitions == null) {
144         // Populate the stream with the definitions from this module
145         Predicate<IDefinition> filter = IModule.allNonLocalDefinitions();
146         Stream<? extends IFlagDefinition> flags = getFlagDefinitions().stream()
147             .filter(filter);
148         Stream<? extends IFieldDefinition> fields = getFieldDefinitions().stream()
149             .filter(filter);
150         Stream<? extends IAssemblyDefinition> assemblies = getAssemblyDefinitions().stream()
151             .filter(filter);
152 
153         // handle definitions from any included module
154         if (!getImportedModules().isEmpty()) {
155           Stream<? extends IFlagDefinition> importedFlags = Stream.empty();
156           Stream<? extends IFieldDefinition> importedFields = Stream.empty();
157           Stream<? extends IAssemblyDefinition> importedAssemblies = Stream.empty();
158 
159           for (IModule module : getImportedModules()) {
160             importedFlags = Stream.concat(importedFlags, module.getExportedFlagDefinitions().stream());
161             importedFields = Stream.concat(importedFields, module.getExportedFieldDefinitions().stream());
162             importedAssemblies
163                 = Stream.concat(importedAssemblies, module.getExportedAssemblyDefinitions().stream());
164           }
165 
166           flags = Stream.concat(importedFlags, flags);
167           fields = Stream.concat(importedFields, fields);
168           assemblies = Stream.concat(importedAssemblies, assemblies);
169         }
170 
171         // Build the maps. Definitions from this module will take priority, with
172         // shadowing being reported when a definition from this module has the same name
173         // as an imported one
174         Map<String, IFlagDefinition> exportedFlagDefinitions = flags.collect(
175             CustomCollectors.toMap(
176                 IFlagDefinition::getName,
177                 CustomCollectors.identity(),
178                 AbstractModule::handleShadowedDefinitions));
179         Map<String, IFieldDefinition> exportedFieldDefinitions = fields.collect(
180             CustomCollectors.toMap(
181                 IFieldDefinition::getName,
182                 CustomCollectors.identity(),
183                 AbstractModule::handleShadowedDefinitions));
184         Map<String, IAssemblyDefinition> exportedAssemblyDefinitions = assemblies.collect(
185             CustomCollectors.toMap(
186                 IAssemblyDefinition::getName,
187                 CustomCollectors.identity(),
188                 AbstractModule::handleShadowedDefinitions));
189 
190         this.exportedFlagDefinitions = exportedFlagDefinitions.isEmpty()
191             ? CollectionUtil.emptyMap()
192             : CollectionUtil.unmodifiableMap(exportedFlagDefinitions);
193         this.exportedFieldDefinitions = exportedFieldDefinitions.isEmpty()
194             ? CollectionUtil.emptyMap()
195             : CollectionUtil.unmodifiableMap(exportedFieldDefinitions);
196         this.exportedAssemblyDefinitions = exportedAssemblyDefinitions.isEmpty()
197             ? CollectionUtil.emptyMap()
198             : CollectionUtil.unmodifiableMap(exportedAssemblyDefinitions);
199       }
200     }
201   }
202 
203   @SuppressWarnings({ "unused", "PMD.UnusedPrivateMethod" }) // used by lambda
204   private static <DEF extends IDefinition> DEF handleShadowedDefinitions(
205       @SuppressWarnings("unused") @NonNull String key, @NonNull DEF oldDef, @NonNull DEF newDef) {
206     if (!oldDef.equals(newDef) && LOGGER.isWarnEnabled()) {
207       LOGGER.warn("The {} '{}' from metaschema '{}' is shadowing '{}' from metaschema '{}'",
208           newDef.getModelType().name().toLowerCase(Locale.ROOT),
209           newDef.getName(),
210           newDef.getContainingModule().getShortName(),
211           oldDef.getName(),
212           oldDef.getContainingModule().getShortName());
213     }
214     return newDef;
215   }
216 }