1
2
3
4
5
6 package dev.metaschema.databind;
7
8 import java.io.IOException;
9 import java.net.URI;
10 import java.net.URL;
11 import java.nio.file.Path;
12 import java.util.Collection;
13 import java.util.List;
14 import java.util.Map;
15 import java.util.Objects;
16 import java.util.concurrent.ConcurrentHashMap;
17
18 import javax.xml.namespace.QName;
19
20 import dev.metaschema.core.model.IBoundObject;
21 import dev.metaschema.core.model.MetaschemaException;
22 import dev.metaschema.core.util.ObjectUtils;
23 import dev.metaschema.databind.io.BindingException;
24 import dev.metaschema.databind.io.Format;
25 import dev.metaschema.databind.io.IDeserializer;
26 import dev.metaschema.databind.io.ISerializer;
27 import dev.metaschema.databind.io.json.DefaultJsonDeserializer;
28 import dev.metaschema.databind.io.json.DefaultJsonSerializer;
29 import dev.metaschema.databind.io.xml.DefaultXmlDeserializer;
30 import dev.metaschema.databind.io.xml.DefaultXmlSerializer;
31 import dev.metaschema.databind.io.yaml.DefaultYamlDeserializer;
32 import dev.metaschema.databind.io.yaml.DefaultYamlSerializer;
33 import dev.metaschema.databind.model.IBoundDefinitionModelAssembly;
34 import dev.metaschema.databind.model.IBoundDefinitionModelComplex;
35 import dev.metaschema.databind.model.IBoundModule;
36 import dev.metaschema.databind.model.metaschema.BindingModuleLoader;
37 import dev.metaschema.databind.model.metaschema.IBindingMetaschemaModule;
38 import dev.metaschema.databind.model.metaschema.IBindingModuleLoader;
39 import dev.metaschema.databind.model.metaschema.ModuleLoadingPostProcessor;
40 import dev.metaschema.databind.model.metaschema.binding.METASCHEMA;
41 import dev.metaschema.databind.model.metaschema.binding.MetaschemaModelModule;
42 import edu.umd.cs.findbugs.annotations.NonNull;
43 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
44 import nl.talsmasoftware.lazy4j.Lazy;
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60 @SuppressWarnings("PMD.CouplingBetweenObjects")
61 public class DefaultBindingContext implements IBindingContext {
62 private static Lazy<DefaultBindingContext> singleton = Lazy.of(DefaultBindingContext::new);
63 @NonNull
64 private final IModuleLoaderStrategy moduleLoaderStrategy;
65 @NonNull
66 private final Map<Class<?>, IBoundDefinitionModelComplex> boundClassToStrategyMap = new ConcurrentHashMap<>();
67
68
69
70
71
72
73
74
75
76
77 @NonNull
78 static DefaultBindingContext instance() {
79 return ObjectUtils.notNull(singleton.get());
80 }
81
82
83
84
85 @SuppressFBWarnings("CT_CONSTRUCTOR_THROW")
86 public DefaultBindingContext() {
87 this(new SimpleModuleLoaderStrategy());
88 }
89
90
91
92
93
94
95
96
97 public DefaultBindingContext(@NonNull IBindingContext.IModuleLoaderStrategy strategy) {
98
99 moduleLoaderStrategy = strategy;
100 try {
101 registerModule(MetaschemaModelModule.class);
102 } catch (MetaschemaException ex) {
103 throw new IllegalStateException("Unable to register the builtin Metaschema module.", ex);
104 }
105 }
106
107 @Override
108 @NonNull
109 public final IModuleLoaderStrategy getModuleLoaderStrategy() {
110 return moduleLoaderStrategy;
111 }
112
113 @Override
114 public IBindingModuleLoader newModuleLoader() {
115 return new ModuleLoader(this, getModuleLoaderStrategy());
116 }
117
118 @Override
119 @NonNull
120 public final IBoundModule registerModule(@NonNull Class<? extends IBoundModule> clazz) throws MetaschemaException {
121 IModuleLoaderStrategy strategy = getModuleLoaderStrategy();
122 IBoundModule module = strategy.loadModule(clazz, this);
123 registerImportedModules(module);
124 return strategy.registerModule(module, this);
125 }
126
127 private void registerImportedModules(@NonNull IBoundModule module) throws MetaschemaException {
128 IModuleLoaderStrategy strategy = getModuleLoaderStrategy();
129 for (IBoundModule parentModule : module.getImportedModules()) {
130 assert parentModule != null;
131 registerImportedModules(parentModule);
132 strategy.registerModule(parentModule, this);
133 }
134 }
135
136
137
138
139
140
141 @NonNull
142 protected Collection<IBindingMatcher> getBindingMatchers() {
143 return getModuleLoaderStrategy().getBindingMatchers();
144 }
145
146 @Override
147 public final IBoundDefinitionModelComplex registerClassBinding(IBoundDefinitionModelComplex definition) {
148 Class<?> clazz = definition.getBoundClass();
149 return boundClassToStrategyMap.computeIfAbsent(clazz, k -> definition);
150 }
151
152 @Override
153 public final IBoundDefinitionModelComplex getBoundDefinitionForClass(@NonNull Class<? extends IBoundObject> clazz) {
154 return moduleLoaderStrategy.getBoundDefinitionForClass(clazz, this);
155 }
156
157
158
159
160
161
162 @Override
163 public <CLASS extends IBoundObject> ISerializer<CLASS> newSerializer(
164 @NonNull Format format,
165 @NonNull Class<CLASS> clazz) {
166 Objects.requireNonNull(format, "format");
167 IBoundDefinitionModelAssembly definition;
168 try {
169 definition = IBoundDefinitionModelAssembly.class.cast(getBoundDefinitionForClass(clazz));
170 } catch (ClassCastException ex) {
171 throw new IllegalStateException(
172 String.format("Class '%s' is not a bound assembly.", clazz.getClass().getName()), ex);
173 }
174 if (definition == null) {
175 throw new IllegalStateException(String.format("Class '%s' is not bound", clazz.getClass().getName()));
176 }
177 ISerializer<CLASS> retval;
178 switch (format) {
179 case JSON:
180 retval = new DefaultJsonSerializer<>(definition);
181 break;
182 case XML:
183 retval = new DefaultXmlSerializer<>(definition);
184 break;
185 case YAML:
186 retval = new DefaultYamlSerializer<>(definition);
187 break;
188 default:
189 throw new UnsupportedOperationException(String.format("Unsupported format '%s'", format));
190 }
191 return retval;
192 }
193
194
195
196
197
198
199 @Override
200 public <CLASS extends IBoundObject> IDeserializer<CLASS> newDeserializer(
201 @NonNull Format format,
202 @NonNull Class<CLASS> clazz) {
203 IBoundDefinitionModelAssembly definition;
204 try {
205 definition = IBoundDefinitionModelAssembly.class.cast(getBoundDefinitionForClass(clazz));
206 } catch (ClassCastException ex) {
207 throw new IllegalStateException(
208 String.format("Class '%s' is not a bound assembly.", clazz.getClass().getName()),
209 ex);
210 }
211 if (definition == null) {
212 throw new IllegalStateException(String.format("Class '%s' is not bound", clazz.getName()));
213 }
214 IDeserializer<CLASS> retval;
215 switch (format) {
216 case JSON:
217 retval = new DefaultJsonDeserializer<>(definition);
218 break;
219 case XML:
220 retval = new DefaultXmlDeserializer<>(definition);
221 break;
222 case YAML:
223 retval = new DefaultYamlDeserializer<>(definition);
224 break;
225 default:
226 throw new UnsupportedOperationException(String.format("Unsupported format '%s'", format));
227 }
228
229 return retval;
230 }
231
232 @Override
233 public Class<? extends IBoundObject> getBoundClassForRootXmlQName(@NonNull QName rootQName) {
234 Class<? extends IBoundObject> retval = null;
235 for (IBindingMatcher matcher : getBindingMatchers()) {
236 retval = matcher.getBoundClassForXmlQName(rootQName);
237 if (retval != null) {
238 break;
239 }
240 }
241 return retval;
242 }
243
244 @Override
245 public Class<? extends IBoundObject> getBoundClassForRootJsonName(@NonNull String rootName) {
246 Class<? extends IBoundObject> retval = null;
247 for (IBindingMatcher matcher : getBindingMatchers()) {
248 retval = matcher.getBoundClassForJsonName(rootName);
249 if (retval != null) {
250 break;
251 }
252 }
253 return retval;
254 }
255
256 @Override
257 public <CLASS extends IBoundObject> CLASS deepCopy(@NonNull CLASS other, IBoundObject parentInstance)
258 throws BindingException {
259 IBoundDefinitionModelComplex definition = getBoundDefinitionForClass(other.getClass());
260 if (definition == null) {
261 throw new IllegalStateException(String.format("Class '%s' is not bound", other.getClass().getName()));
262 }
263 return ObjectUtils.asType(definition.deepCopyItem(other, parentInstance));
264 }
265
266
267
268
269
270 @Override
271 @SuppressWarnings({
272 "deprecation",
273 "checkstyle:NoFinalizer" })
274 protected final void finalize() {
275
276 }
277
278 private static class ModuleLoader
279 extends BindingModuleLoader {
280
281 public ModuleLoader(
282 @NonNull IBindingContext bindingContext,
283 @NonNull ModuleLoadingPostProcessor postProcessor) {
284 super(bindingContext, postProcessor);
285 }
286
287 @Override
288 public IBindingMetaschemaModule load(URI resource) throws MetaschemaException, IOException {
289 IBindingMetaschemaModule module = super.load(resource);
290 getBindingContext().registerModule(module);
291 return module;
292 }
293
294 @Override
295 public IBindingMetaschemaModule load(Path path) throws MetaschemaException, IOException {
296 IBindingMetaschemaModule module = super.load(path);
297 getBindingContext().registerModule(module);
298 return module;
299 }
300
301 @Override
302 public IBindingMetaschemaModule load(URL url) throws MetaschemaException, IOException {
303 IBindingMetaschemaModule module = super.load(url);
304 getBindingContext().registerModule(module);
305 return module;
306 }
307
308 @Override
309 protected IBindingMetaschemaModule newModule(
310 URI resource,
311 METASCHEMA binding,
312 List<? extends IBindingMetaschemaModule> importedModules) throws MetaschemaException {
313 return super.newModule(resource, binding, importedModules);
314 }
315 }
316 }