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