1
2
3
4
5
6 package gov.nist.secauto.metaschema.databind;
7
8 import gov.nist.secauto.metaschema.core.model.IBoundObject;
9 import gov.nist.secauto.metaschema.core.model.IModule;
10 import gov.nist.secauto.metaschema.core.model.constraint.DefaultConstraintValidator;
11 import gov.nist.secauto.metaschema.core.qname.IEnhancedQName;
12 import gov.nist.secauto.metaschema.core.util.ObjectUtils;
13 import gov.nist.secauto.metaschema.databind.IBindingContext.IBindingMatcher;
14 import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelAssembly;
15 import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelComplex;
16 import gov.nist.secauto.metaschema.databind.model.IBoundModule;
17 import gov.nist.secauto.metaschema.databind.model.annotations.MetaschemaAssembly;
18 import gov.nist.secauto.metaschema.databind.model.annotations.MetaschemaField;
19 import gov.nist.secauto.metaschema.databind.model.annotations.MetaschemaModule;
20 import gov.nist.secauto.metaschema.databind.model.annotations.ModelUtil;
21 import gov.nist.secauto.metaschema.databind.model.impl.DefinitionAssembly;
22 import gov.nist.secauto.metaschema.databind.model.impl.DefinitionField;
23 import gov.nist.secauto.metaschema.databind.model.metaschema.binding.MetaschemaModelModule;
24
25 import org.apache.logging.log4j.LogManager;
26 import org.apache.logging.log4j.Logger;
27
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.HashMap;
31 import java.util.LinkedHashMap;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.concurrent.ConcurrentHashMap;
35 import java.util.concurrent.locks.Lock;
36 import java.util.concurrent.locks.ReentrantLock;
37 import java.util.stream.Collectors;
38
39 import edu.umd.cs.findbugs.annotations.NonNull;
40
41
42
43
44
45
46 public abstract class AbstractModuleLoaderStrategy implements IBindingContext.IModuleLoaderStrategy {
47 private static final Logger LOGGER = LogManager.getLogger(DefaultConstraintValidator.class);
48
49 @SuppressWarnings("PMD.UseConcurrentHashMap")
50 @NonNull
51 private final Map<IEnhancedQName, IBindingMatcher> bindingMatchers = new LinkedHashMap<>();
52 @NonNull
53 private final Map<IModule, IBoundModule> moduleToBoundModuleMap = new ConcurrentHashMap<>();
54 @SuppressWarnings("PMD.UseConcurrentHashMap")
55 @NonNull
56 private final Map<Class<? extends IBoundModule>, IBoundModule> modulesByClass = new HashMap<>();
57
58 @NonNull
59 private final Lock modulesLock = new ReentrantLock();
60 @SuppressWarnings("PMD.UseConcurrentHashMap")
61 @NonNull
62 private final Map<Class<? extends IBoundObject>, IBoundDefinitionModelComplex> definitionsByClass
63 = new HashMap<>();
64 @NonNull
65 private final Lock definitionsLock = new ReentrantLock();
66
67 @Override
68 public IBoundModule loadModule(
69 @NonNull Class<? extends IBoundModule> clazz,
70 @NonNull IBindingContext bindingContext) {
71 return lookupInstance(clazz, bindingContext);
72 }
73
74 @Override
75 public IBoundModule registerModule(
76 IModule module,
77 IBindingContext bindingContext) {
78 modulesLock.lock();
79 try {
80 return ObjectUtils.notNull(moduleToBoundModuleMap.computeIfAbsent(module, key -> {
81 assert key != null;
82
83 IBoundModule boundModule;
84 if (key instanceof IBoundModule) {
85 boundModule = (IBoundModule) key;
86 } else {
87 Class<? extends IBoundModule> moduleClass = handleUnboundModule(key);
88 boundModule = lookupInstance(moduleClass, bindingContext);
89 }
90
91 boundModule.getExportedAssemblyDefinitions().forEach(assembly -> {
92 assert assembly != null;
93 if (assembly.isRoot()) {
94
95 registerBindingMatcher(assembly);
96 }
97 });
98
99 return boundModule;
100 }));
101 } finally {
102 modulesLock.unlock();
103 }
104 }
105
106 @NonNull
107 protected abstract Class<? extends IBoundModule> handleUnboundModule(@NonNull IModule key);
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122 @NonNull
123 protected IBoundModule lookupInstance(
124 @NonNull Class<? extends IBoundModule> moduleClass,
125 @NonNull IBindingContext bindingContext) {
126 IBoundModule retval;
127 modulesLock.lock();
128 try {
129 retval = modulesByClass.get(moduleClass);
130 if (retval == null) {
131 if (!moduleClass.isAnnotationPresent(MetaschemaModule.class)) {
132 throw new IllegalStateException(String.format("The class '%s' is missing the '%s' annotation",
133 moduleClass.getCanonicalName(), MetaschemaModule.class.getCanonicalName()));
134 }
135
136 retval = IBoundModule.newInstance(moduleClass, bindingContext, getImportedModules(moduleClass, bindingContext));
137 modulesByClass.put(moduleClass, retval);
138 }
139 } finally {
140 modulesLock.unlock();
141 }
142 return retval;
143 }
144
145 @NonNull
146 protected IBindingMatcher registerBindingMatcher(@NonNull IBoundDefinitionModelAssembly definition) {
147 IBindingMatcher retval;
148 modulesLock.lock();
149 try {
150 if (!definition.isRoot()) {
151 throw new IllegalArgumentException(
152 String.format("The provided definition '%s' is not a root assembly.",
153 definition.getBoundClass().getName()));
154 }
155 IEnhancedQName qname = definition.getRootQName();
156 retval = IBindingMatcher.of(definition);
157
158 IBindingMatcher old = bindingMatchers.put(qname, retval);
159 if (old != null && !(definition.getContainingModule() instanceof MetaschemaModelModule)) {
160
161 LOGGER.atWarn().log("Replacing matcher for QName: {}", qname);
162 }
163
164
165
166
167
168
169
170
171
172
173
174
175 } finally {
176 modulesLock.unlock();
177 }
178 return retval;
179 }
180
181 @Override
182 public final List<IBindingMatcher> getBindingMatchers() {
183 modulesLock.lock();
184 try {
185
186 return new ArrayList<>(bindingMatchers.values());
187 } finally {
188 modulesLock.unlock();
189 }
190 }
191
192 @NonNull
193 private List<IBoundModule> getImportedModules(
194 @NonNull Class<? extends IBoundModule> moduleClass,
195 @NonNull IBindingContext bindingContext) {
196 MetaschemaModule moduleAnnotation = moduleClass.getAnnotation(MetaschemaModule.class);
197
198 return ObjectUtils.notNull(Arrays.stream(moduleAnnotation.imports())
199 .map(clazz -> lookupInstance(ObjectUtils.requireNonNull(clazz), bindingContext))
200 .collect(Collectors.toUnmodifiableList()));
201 }
202
203 @Override
204 public IBoundDefinitionModelComplex getBoundDefinitionForClass(
205 @NonNull Class<? extends IBoundObject> clazz,
206 @NonNull IBindingContext bindingContext) {
207
208 IBoundDefinitionModelComplex retval;
209 definitionsLock.lock();
210 try {
211 retval = definitionsByClass.get(clazz);
212 if (retval == null) {
213 retval = newBoundDefinition(clazz, bindingContext);
214 definitionsByClass.put(clazz, retval);
215 }
216
217
218
219
220 return retval;
221 } finally {
222 definitionsLock.unlock();
223 }
224 }
225
226 @NonNull
227 private IBoundDefinitionModelComplex newBoundDefinition(
228 @NonNull Class<? extends IBoundObject> clazz,
229 @NonNull IBindingContext bindingContext) {
230 IBoundDefinitionModelComplex retval;
231 if (clazz.isAnnotationPresent(MetaschemaAssembly.class)) {
232 MetaschemaAssembly annotation = ModelUtil.getAnnotation(clazz, MetaschemaAssembly.class);
233 Class<? extends IBoundModule> moduleClass = annotation.moduleClass();
234 IBoundModule module = loadModule(moduleClass, bindingContext);
235 retval = DefinitionAssembly.newInstance(clazz, annotation, module, bindingContext);
236 } else if (clazz.isAnnotationPresent(MetaschemaField.class)) {
237 MetaschemaField annotation = ModelUtil.getAnnotation(clazz, MetaschemaField.class);
238 Class<? extends IBoundModule> moduleClass = annotation.moduleClass();
239 IBoundModule module = loadModule(moduleClass, bindingContext);
240 retval = DefinitionField.newInstance(clazz, annotation, module, bindingContext);
241 } else {
242 throw new IllegalArgumentException(String.format("Unable to find bound definition for class '%s'.",
243 clazz.getName()));
244 }
245 return retval;
246 }
247 }