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