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 if (LOGGER.isDebugEnabled()) {
162 LOGGER.atDebug().log("Replacing matcher for QName: {}", qname);
163 }
164 }
165
166
167
168
169
170
171
172
173
174
175
176
177 } finally {
178 modulesLock.unlock();
179 }
180 return retval;
181 }
182
183 @Override
184 public final List<IBindingMatcher> getBindingMatchers() {
185 modulesLock.lock();
186 try {
187
188 return new ArrayList<>(bindingMatchers.values());
189 } finally {
190 modulesLock.unlock();
191 }
192 }
193
194 @NonNull
195 private List<IBoundModule> getImportedModules(
196 @NonNull Class<? extends IBoundModule> moduleClass,
197 @NonNull IBindingContext bindingContext) {
198 MetaschemaModule moduleAnnotation = moduleClass.getAnnotation(MetaschemaModule.class);
199
200 return ObjectUtils.notNull(Arrays.stream(moduleAnnotation.imports())
201 .map(clazz -> lookupInstance(ObjectUtils.requireNonNull(clazz), bindingContext))
202 .collect(Collectors.toUnmodifiableList()));
203 }
204
205 @Override
206 public IBoundDefinitionModelComplex getBoundDefinitionForClass(
207 @NonNull Class<? extends IBoundObject> clazz,
208 @NonNull IBindingContext bindingContext) {
209
210 IBoundDefinitionModelComplex retval;
211 definitionsLock.lock();
212 try {
213 retval = definitionsByClass.get(clazz);
214 if (retval == null) {
215 retval = newBoundDefinition(clazz, bindingContext);
216 definitionsByClass.put(clazz, retval);
217 }
218
219
220
221
222 return retval;
223 } finally {
224 definitionsLock.unlock();
225 }
226 }
227
228 @NonNull
229 private IBoundDefinitionModelComplex newBoundDefinition(
230 @NonNull Class<? extends IBoundObject> clazz,
231 @NonNull IBindingContext bindingContext) {
232 IBoundDefinitionModelComplex retval;
233 if (clazz.isAnnotationPresent(MetaschemaAssembly.class)) {
234 MetaschemaAssembly annotation = ModelUtil.getAnnotation(clazz, MetaschemaAssembly.class);
235 Class<? extends IBoundModule> moduleClass = annotation.moduleClass();
236 IBoundModule module = loadModule(moduleClass, bindingContext);
237 retval = DefinitionAssembly.newInstance(clazz, annotation, module, bindingContext);
238 } else if (clazz.isAnnotationPresent(MetaschemaField.class)) {
239 MetaschemaField annotation = ModelUtil.getAnnotation(clazz, MetaschemaField.class);
240 Class<? extends IBoundModule> moduleClass = annotation.moduleClass();
241 IBoundModule module = loadModule(moduleClass, bindingContext);
242 retval = DefinitionField.newInstance(clazz, annotation, module, bindingContext);
243 } else {
244 throw new IllegalArgumentException(String.format("Unable to find bound definition for class '%s'.",
245 clazz.getName()));
246 }
247 return retval;
248 }
249 }