001/* 002 * Portions of this software was developed by employees of the National Institute 003 * of Standards and Technology (NIST), an agency of the Federal Government and is 004 * being made available as a public service. Pursuant to title 17 United States 005 * Code Section 105, works of NIST employees are not subject to copyright 006 * protection in the United States. This software may be subject to foreign 007 * copyright. Permission in the United States and in foreign countries, to the 008 * extent that NIST may hold copyright, to use, copy, modify, create derivative 009 * works, and distribute this software and its documentation without fee is hereby 010 * granted on a non-exclusive basis, provided that this notice and disclaimer 011 * of warranty appears in all copies. 012 * 013 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER 014 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY 015 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF 016 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM 017 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE 018 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE. IN NO EVENT 019 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT, 020 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM, 021 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY, 022 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR 023 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT 024 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER. 025 */ 026 027package gov.nist.secauto.metaschema.core.model; 028 029import gov.nist.secauto.metaschema.core.util.CollectionUtil; 030import gov.nist.secauto.metaschema.core.util.CustomCollectors; 031import gov.nist.secauto.metaschema.core.util.ObjectUtils; 032 033import org.apache.logging.log4j.LogManager; 034import org.apache.logging.log4j.Logger; 035 036import java.util.Collection; 037import java.util.List; 038import java.util.Locale; 039import java.util.Map; 040import java.util.function.Function; 041import java.util.function.Predicate; 042import java.util.stream.Collectors; 043import java.util.stream.Stream; 044 045import edu.umd.cs.findbugs.annotations.NonNull; 046import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 047 048/** 049 * Provides a common, abstract implementation of a {@link IModule}. 050 */ 051@SuppressWarnings("PMD.CouplingBetweenObjects") 052public abstract class AbstractModule 053 implements IModule { 054 private static final Logger LOGGER = LogManager.getLogger(AbstractModule.class); 055 056 @NonNull 057 private final List<? extends IModule> importedModules; 058 private Map<String, IFlagDefinition> exportedFlagDefinitions; 059 private Map<String, IFieldDefinition> exportedFieldDefinitions; 060 private Map<String, IAssemblyDefinition> exportedAssemblyDefinitions; 061 062 /** 063 * Construct a new Metaschema module object. 064 * 065 * @param importedModules 066 * the collection of Metaschema module objects this Metaschema module 067 * imports 068 */ 069 public AbstractModule(@NonNull List<? extends IModule> importedModules) { 070 this.importedModules 071 = CollectionUtil.unmodifiableList(ObjectUtils.requireNonNull(importedModules, "importedModules")); 072 } 073 074 @Override 075 @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "interface doesn't allow modification") 076 public List<? extends IModule> getImportedModules() { 077 return importedModules; 078 } 079 080 private Map<String, ? extends IModule> getImportedModulesByShortName() { 081 return importedModules.stream().collect(Collectors.toMap(IModule::getShortName, Function.identity())); 082 } 083 084 @Override 085 public IModule getImportedModuleByShortName(String name) { 086 return getImportedModulesByShortName().get(name); 087 } 088 089 @SuppressWarnings("null") 090 @Override 091 public Collection<? extends IFlagDefinition> getExportedFlagDefinitions() { 092 return getExportedFlagDefinitionMap().values(); 093 } 094 095 @Override 096 public IFlagDefinition getExportedFlagDefinitionByName(String name) { 097 return getExportedFlagDefinitionMap().get(name); 098 } 099 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}