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