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.of(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 try { 102 registerModule(MetaschemaModelModule.class); 103 } catch (MetaschemaException ex) { 104 throw new IllegalStateException("Unable to register the builtin Metaschema module.", ex); 105 } 106 } 107 108 @Override 109 @NonNull 110 public final IModuleLoaderStrategy getModuleLoaderStrategy() { 111 return moduleLoaderStrategy; 112 } 113 114 @Override 115 public IBindingModuleLoader newModuleLoader() { 116 return new ModuleLoader(this, getModuleLoaderStrategy()); 117 } 118 119 @Override 120 @NonNull 121 public final IBoundModule registerModule(@NonNull Class<? extends IBoundModule> clazz) throws MetaschemaException { 122 IModuleLoaderStrategy strategy = getModuleLoaderStrategy(); 123 IBoundModule module = strategy.loadModule(clazz, this); 124 registerImportedModules(module); 125 return strategy.registerModule(module, this); 126 } 127 128 private void registerImportedModules(@NonNull IBoundModule module) throws MetaschemaException { 129 IModuleLoaderStrategy strategy = getModuleLoaderStrategy(); 130 for (IBoundModule parentModule : module.getImportedModules()) { 131 assert parentModule != null; 132 registerImportedModules(parentModule); 133 strategy.registerModule(parentModule, this); 134 } 135 } 136 137 /** 138 * Get the binding matchers that are associated with this class. 139 * 140 * @return the list of matchers 141 */ 142 @NonNull 143 protected Collection<IBindingMatcher> getBindingMatchers() { 144 return getModuleLoaderStrategy().getBindingMatchers(); 145 } 146 147 @Override 148 public final IBoundDefinitionModelComplex registerClassBinding(IBoundDefinitionModelComplex definition) { 149 Class<?> clazz = definition.getBoundClass(); 150 return boundClassToStrategyMap.computeIfAbsent(clazz, k -> definition); 151 } 152 153 @Override 154 public final IBoundDefinitionModelComplex getBoundDefinitionForClass(@NonNull Class<? extends IBoundObject> clazz) { 155 return moduleLoaderStrategy.getBoundDefinitionForClass(clazz, this); 156 } 157 158 /** 159 * {@inheritDoc} 160 * <p> 161 * A serializer returned by this method is thread-safe. 162 */ 163 @Override 164 public <CLASS extends IBoundObject> ISerializer<CLASS> newSerializer( 165 @NonNull Format format, 166 @NonNull Class<CLASS> clazz) { 167 Objects.requireNonNull(format, "format"); 168 IBoundDefinitionModelAssembly definition; 169 try { 170 definition = IBoundDefinitionModelAssembly.class.cast(getBoundDefinitionForClass(clazz)); 171 } catch (ClassCastException ex) { 172 throw new IllegalStateException( 173 String.format("Class '%s' is not a bound assembly.", clazz.getClass().getName()), ex); 174 } 175 if (definition == null) { 176 throw new IllegalStateException(String.format("Class '%s' is not bound", clazz.getClass().getName())); 177 } 178 ISerializer<CLASS> retval; 179 switch (format) { 180 case JSON: 181 retval = new DefaultJsonSerializer<>(definition); 182 break; 183 case XML: 184 retval = new DefaultXmlSerializer<>(definition); 185 break; 186 case YAML: 187 retval = new DefaultYamlSerializer<>(definition); 188 break; 189 default: 190 throw new UnsupportedOperationException(String.format("Unsupported format '%s'", format)); 191 } 192 return retval; 193 } 194 195 /** 196 * {@inheritDoc} 197 * <p> 198 * A deserializer returned by this method is thread-safe. 199 */ 200 @Override 201 public <CLASS extends IBoundObject> IDeserializer<CLASS> newDeserializer( 202 @NonNull Format format, 203 @NonNull Class<CLASS> clazz) { 204 IBoundDefinitionModelAssembly definition; 205 try { 206 definition = IBoundDefinitionModelAssembly.class.cast(getBoundDefinitionForClass(clazz)); 207 } catch (ClassCastException ex) { 208 throw new IllegalStateException( 209 String.format("Class '%s' is not a bound assembly.", clazz.getClass().getName()), 210 ex); 211 } 212 if (definition == null) { 213 throw new IllegalStateException(String.format("Class '%s' is not bound", clazz.getName())); 214 } 215 IDeserializer<CLASS> retval; 216 switch (format) { 217 case JSON: 218 retval = new DefaultJsonDeserializer<>(definition); 219 break; 220 case XML: 221 retval = new DefaultXmlDeserializer<>(definition); 222 break; 223 case YAML: 224 retval = new DefaultYamlDeserializer<>(definition); 225 break; 226 default: 227 throw new UnsupportedOperationException(String.format("Unsupported format '%s'", format)); 228 } 229 230 return retval; 231 } 232 233 @Override 234 public Class<? extends IBoundObject> getBoundClassForRootXmlQName(@NonNull QName rootQName) { 235 Class<? extends IBoundObject> retval = null; 236 for (IBindingMatcher matcher : getBindingMatchers()) { 237 retval = matcher.getBoundClassForXmlQName(rootQName); 238 if (retval != null) { 239 break; 240 } 241 } 242 return retval; 243 } 244 245 @Override 246 public Class<? extends IBoundObject> getBoundClassForRootJsonName(@NonNull String rootName) { 247 Class<? extends IBoundObject> retval = null; 248 for (IBindingMatcher matcher : getBindingMatchers()) { 249 retval = matcher.getBoundClassForJsonName(rootName); 250 if (retval != null) { 251 break; 252 } 253 } 254 return retval; 255 } 256 257 @Override 258 public <CLASS extends IBoundObject> CLASS deepCopy(@NonNull CLASS other, IBoundObject parentInstance) 259 throws BindingException { 260 IBoundDefinitionModelComplex definition = getBoundDefinitionForClass(other.getClass()); 261 if (definition == null) { 262 throw new IllegalStateException(String.format("Class '%s' is not bound", other.getClass().getName())); 263 } 264 return ObjectUtils.asType(definition.deepCopyItem(other, parentInstance)); 265 } 266 267 /** 268 * Used to prevent finalizer attacks as recommended in SEI CERT Rule OBJ-11. 269 * This is needed because the class is non-final and the constructor can throw. 270 */ 271 @Override 272 @SuppressWarnings({ 273 "PMD.EmptyFinalizer", 274 "checkstyle:NoFinalizer" }) 275 protected final void finalize() { 276 // Do nothing 277 } 278 279 private static class ModuleLoader 280 extends BindingModuleLoader { 281 282 public ModuleLoader( 283 @NonNull IBindingContext bindingContext, 284 @NonNull ModuleLoadingPostProcessor postProcessor) { 285 super(bindingContext, postProcessor); 286 } 287 288 @Override 289 public IBindingMetaschemaModule load(URI resource) throws MetaschemaException, IOException { 290 IBindingMetaschemaModule module = super.load(resource); 291 getBindingContext().registerModule(module); 292 return module; 293 } 294 295 @Override 296 public IBindingMetaschemaModule load(Path path) throws MetaschemaException, IOException { 297 IBindingMetaschemaModule module = super.load(path); 298 getBindingContext().registerModule(module); 299 return module; 300 } 301 302 @Override 303 public IBindingMetaschemaModule load(URL url) throws MetaschemaException, IOException { 304 IBindingMetaschemaModule module = super.load(url); 305 getBindingContext().registerModule(module); 306 return module; 307 } 308 309 @Override 310 protected IBindingMetaschemaModule newModule( 311 URI resource, 312 METASCHEMA binding, 313 List<? extends IBindingMetaschemaModule> importedModules) throws MetaschemaException { 314 return super.newModule(resource, binding, importedModules); 315 } 316 } 317}