001/* 002 * SPDX-FileCopyrightText: none 003 * SPDX-License-Identifier: CC0-1.0 004 */ 005 006package gov.nist.secauto.metaschema.databind; 007 008import gov.nist.secauto.metaschema.core.model.IBoundObject; 009import gov.nist.secauto.metaschema.core.model.MetaschemaException; 010import gov.nist.secauto.metaschema.core.util.ObjectUtils; 011import gov.nist.secauto.metaschema.databind.io.BindingException; 012import gov.nist.secauto.metaschema.databind.io.Format; 013import gov.nist.secauto.metaschema.databind.io.IDeserializer; 014import gov.nist.secauto.metaschema.databind.io.ISerializer; 015import gov.nist.secauto.metaschema.databind.io.json.DefaultJsonDeserializer; 016import gov.nist.secauto.metaschema.databind.io.json.DefaultJsonSerializer; 017import gov.nist.secauto.metaschema.databind.io.xml.DefaultXmlDeserializer; 018import gov.nist.secauto.metaschema.databind.io.xml.DefaultXmlSerializer; 019import gov.nist.secauto.metaschema.databind.io.yaml.DefaultYamlDeserializer; 020import gov.nist.secauto.metaschema.databind.io.yaml.DefaultYamlSerializer; 021import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelAssembly; 022import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelComplex; 023import gov.nist.secauto.metaschema.databind.model.IBoundModule; 024import gov.nist.secauto.metaschema.databind.model.metaschema.BindingModuleLoader; 025import gov.nist.secauto.metaschema.databind.model.metaschema.IBindingMetaschemaModule; 026import gov.nist.secauto.metaschema.databind.model.metaschema.IBindingModuleLoader; 027import gov.nist.secauto.metaschema.databind.model.metaschema.ModuleLoadingPostProcessor; 028import gov.nist.secauto.metaschema.databind.model.metaschema.binding.METASCHEMA; 029import gov.nist.secauto.metaschema.databind.model.metaschema.binding.MetaschemaModelModule; 030 031import java.io.IOException; 032import java.net.URI; 033import java.net.URL; 034import java.nio.file.Path; 035import java.util.Collection; 036import java.util.List; 037import java.util.Map; 038import java.util.Objects; 039import java.util.concurrent.ConcurrentHashMap; 040 041import javax.xml.namespace.QName; 042 043import edu.umd.cs.findbugs.annotations.NonNull; 044import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 045import nl.talsmasoftware.lazy4j.Lazy; 046 047/** 048 * The implementation of a {@link IBindingContext} provided by this library. 049 * <p> 050 * This implementation caches Module information, which can dramatically improve 051 * read and write performance at the cost of some memory use. Thus, using the 052 * same singleton of this class across multiple I/O operations will improve 053 * overall read and write performance when processing the same types of data. 054 * <p> 055 * Serializers and deserializers provided by this class using the 056 * {@link #newSerializer(Format, Class)} and 057 * {@link #newDeserializer(Format, Class)} methods will 058 * <p> 059 * This class is synchronized and is thread-safe. 060 */ 061@SuppressWarnings("PMD.CouplingBetweenObjects") 062public class DefaultBindingContext implements IBindingContext { 063 private static Lazy<DefaultBindingContext> singleton = Lazy.lazy(DefaultBindingContext::new); 064 @NonNull 065 private final IModuleLoaderStrategy moduleLoaderStrategy; 066 @NonNull 067 private final Map<Class<?>, IBoundDefinitionModelComplex> boundClassToStrategyMap = new ConcurrentHashMap<>(); 068 069 /** 070 * Get the singleton instance of this binding context. 071 * <p> 072 * Note: It is general a better practice to use a new {@link IBindingContext} 073 * and reuse that instance instead of this global instance. 074 * 075 * @return the binding context 076 * @see IBindingContext#newInstance() 077 */ 078 @NonNull 079 static DefaultBindingContext instance() { 080 return ObjectUtils.notNull(singleton.get()); 081 } 082 083 /** 084 * Construct a new binding context. 085 */ 086 @SuppressFBWarnings("CT_CONSTRUCTOR_THROW") 087 public DefaultBindingContext() { 088 this(new SimpleModuleLoaderStrategy()); 089 } 090 091 /** 092 * Construct a new binding context. 093 * 094 * @param strategy 095 * the behavior class to use for loading Metaschema modules 096 * @since 2.0.0 097 */ 098 public DefaultBindingContext(@NonNull IBindingContext.IModuleLoaderStrategy strategy) { 099 // only allow extended classes 100 moduleLoaderStrategy = strategy; 101 registerModule(MetaschemaModelModule.class); 102 } 103 104 @Override 105 @NonNull 106 public final IModuleLoaderStrategy getModuleLoaderStrategy() { 107 return moduleLoaderStrategy; 108 } 109 110 @Override 111 public IBindingModuleLoader newModuleLoader() { 112 return new ModuleLoader(this, getModuleLoaderStrategy()); 113 } 114 115 @Override 116 @NonNull 117 public final IBoundModule registerModule(@NonNull Class<? extends IBoundModule> clazz) { 118 IModuleLoaderStrategy strategy = getModuleLoaderStrategy(); 119 IBoundModule module = strategy.loadModule(clazz, this); 120 registerImportedModules(module); 121 return strategy.registerModule(module, this); 122 } 123 124 private void registerImportedModules(@NonNull IBoundModule module) { 125 IModuleLoaderStrategy strategy = getModuleLoaderStrategy(); 126 module.getImportedModules().stream() 127 .forEachOrdered(parentModule -> { 128 registerImportedModules(ObjectUtils.notNull(parentModule)); 129 strategy.registerModule(ObjectUtils.notNull(parentModule), this); 130 }); 131 } 132 133 /** 134 * Get the binding matchers that are associated with this class. 135 * 136 * @return the list of matchers 137 */ 138 @NonNull 139 protected Collection<IBindingMatcher> getBindingMatchers() { 140 return getModuleLoaderStrategy().getBindingMatchers(); 141 } 142 143 @Override 144 public final IBoundDefinitionModelComplex registerClassBinding(IBoundDefinitionModelComplex definition) { 145 Class<?> clazz = definition.getBoundClass(); 146 return boundClassToStrategyMap.computeIfAbsent(clazz, k -> definition); 147 } 148 149 @Override 150 public final IBoundDefinitionModelComplex getBoundDefinitionForClass(@NonNull Class<? extends IBoundObject> clazz) { 151 return moduleLoaderStrategy.getBoundDefinitionForClass(clazz, this); 152 } 153 154 /** 155 * {@inheritDoc} 156 * <p> 157 * A serializer returned by this method is thread-safe. 158 */ 159 @Override 160 public <CLASS extends IBoundObject> ISerializer<CLASS> newSerializer( 161 @NonNull Format format, 162 @NonNull Class<CLASS> clazz) { 163 Objects.requireNonNull(format, "format"); 164 IBoundDefinitionModelAssembly definition; 165 try { 166 definition = IBoundDefinitionModelAssembly.class.cast(getBoundDefinitionForClass(clazz)); 167 } catch (ClassCastException ex) { 168 throw new IllegalStateException( 169 String.format("Class '%s' is not a bound assembly.", clazz.getClass().getName()), ex); 170 } 171 if (definition == null) { 172 throw new IllegalStateException(String.format("Class '%s' is not bound", clazz.getClass().getName())); 173 } 174 ISerializer<CLASS> retval; 175 switch (format) { 176 case JSON: 177 retval = new DefaultJsonSerializer<>(definition); 178 break; 179 case XML: 180 retval = new DefaultXmlSerializer<>(definition); 181 break; 182 case YAML: 183 retval = new DefaultYamlSerializer<>(definition); 184 break; 185 default: 186 throw new UnsupportedOperationException(String.format("Unsupported format '%s'", format)); 187 } 188 return retval; 189 } 190 191 /** 192 * {@inheritDoc} 193 * <p> 194 * A deserializer returned by this method is thread-safe. 195 */ 196 @Override 197 public <CLASS extends IBoundObject> IDeserializer<CLASS> newDeserializer( 198 @NonNull Format format, 199 @NonNull Class<CLASS> clazz) { 200 IBoundDefinitionModelAssembly definition; 201 try { 202 definition = IBoundDefinitionModelAssembly.class.cast(getBoundDefinitionForClass(clazz)); 203 } catch (ClassCastException ex) { 204 throw new IllegalStateException( 205 String.format("Class '%s' is not a bound assembly.", clazz.getClass().getName()), 206 ex); 207 } 208 if (definition == null) { 209 throw new IllegalStateException(String.format("Class '%s' is not bound", clazz.getName())); 210 } 211 IDeserializer<CLASS> retval; 212 switch (format) { 213 case JSON: 214 retval = new DefaultJsonDeserializer<>(definition); 215 break; 216 case XML: 217 retval = new DefaultXmlDeserializer<>(definition); 218 break; 219 case YAML: 220 retval = new DefaultYamlDeserializer<>(definition); 221 break; 222 default: 223 throw new UnsupportedOperationException(String.format("Unsupported format '%s'", format)); 224 } 225 226 return retval; 227 } 228 229 @Override 230 public Class<? extends IBoundObject> getBoundClassForRootXmlQName(@NonNull QName rootQName) { 231 Class<? extends IBoundObject> retval = null; 232 for (IBindingMatcher matcher : getBindingMatchers()) { 233 retval = matcher.getBoundClassForXmlQName(rootQName); 234 if (retval != null) { 235 break; 236 } 237 } 238 return retval; 239 } 240 241 @Override 242 public Class<? extends IBoundObject> getBoundClassForRootJsonName(@NonNull String rootName) { 243 Class<? extends IBoundObject> retval = null; 244 for (IBindingMatcher matcher : getBindingMatchers()) { 245 retval = matcher.getBoundClassForJsonName(rootName); 246 if (retval != null) { 247 break; 248 } 249 } 250 return retval; 251 } 252 253 @Override 254 public <CLASS extends IBoundObject> CLASS deepCopy(@NonNull CLASS other, IBoundObject parentInstance) 255 throws BindingException { 256 IBoundDefinitionModelComplex definition = getBoundDefinitionForClass(other.getClass()); 257 if (definition == null) { 258 throw new IllegalStateException(String.format("Class '%s' is not bound", other.getClass().getName())); 259 } 260 return ObjectUtils.asType(definition.deepCopyItem(other, parentInstance)); 261 } 262 263 private static class ModuleLoader 264 extends BindingModuleLoader { 265 266 public ModuleLoader( 267 @NonNull IBindingContext bindingContext, 268 @NonNull ModuleLoadingPostProcessor postProcessor) { 269 super(bindingContext, postProcessor); 270 } 271 272 @Override 273 public IBindingMetaschemaModule load(URI resource) throws MetaschemaException, IOException { 274 IBindingMetaschemaModule module = super.load(resource); 275 getBindingContext().registerModule(module); 276 return module; 277 } 278 279 @Override 280 public IBindingMetaschemaModule load(Path path) throws MetaschemaException, IOException { 281 IBindingMetaschemaModule module = super.load(path); 282 getBindingContext().registerModule(module); 283 return module; 284 } 285 286 @Override 287 public IBindingMetaschemaModule load(URL url) throws MetaschemaException, IOException { 288 IBindingMetaschemaModule module = super.load(url); 289 getBindingContext().registerModule(module); 290 return module; 291 } 292 293 @Override 294 protected IBindingMetaschemaModule newModule( 295 URI resource, 296 METASCHEMA binding, 297 List<? extends IBindingMetaschemaModule> importedModules) throws MetaschemaException { 298 return super.newModule(resource, binding, importedModules); 299 } 300 301 } 302}