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 strategy.registerModule(module, this); 121 return module; 122 } 123 124 /** 125 * Get the binding matchers that are associated with this class. 126 * 127 * @return the list of matchers 128 */ 129 @NonNull 130 protected Collection<IBindingMatcher> getBindingMatchers() { 131 return getModuleLoaderStrategy().getBindingMatchers(); 132 } 133 134 @Override 135 public final IBoundDefinitionModelComplex registerClassBinding(IBoundDefinitionModelComplex definition) { 136 Class<?> clazz = definition.getBoundClass(); 137 return boundClassToStrategyMap.computeIfAbsent(clazz, k -> definition); 138 } 139 140 @Override 141 public final IBoundDefinitionModelComplex getBoundDefinitionForClass(@NonNull Class<? extends IBoundObject> clazz) { 142 return moduleLoaderStrategy.getBoundDefinitionForClass(clazz, this); 143 } 144 145 /** 146 * {@inheritDoc} 147 * <p> 148 * A serializer returned by this method is thread-safe. 149 */ 150 @Override 151 public <CLASS extends IBoundObject> ISerializer<CLASS> newSerializer( 152 @NonNull Format format, 153 @NonNull Class<CLASS> clazz) { 154 Objects.requireNonNull(format, "format"); 155 IBoundDefinitionModelAssembly definition; 156 try { 157 definition = IBoundDefinitionModelAssembly.class.cast(getBoundDefinitionForClass(clazz)); 158 } catch (ClassCastException ex) { 159 throw new IllegalStateException( 160 String.format("Class '%s' is not a bound assembly.", clazz.getClass().getName()), ex); 161 } 162 if (definition == null) { 163 throw new IllegalStateException(String.format("Class '%s' is not bound", clazz.getClass().getName())); 164 } 165 ISerializer<CLASS> retval; 166 switch (format) { 167 case JSON: 168 retval = new DefaultJsonSerializer<>(definition); 169 break; 170 case XML: 171 retval = new DefaultXmlSerializer<>(definition); 172 break; 173 case YAML: 174 retval = new DefaultYamlSerializer<>(definition); 175 break; 176 default: 177 throw new UnsupportedOperationException(String.format("Unsupported format '%s'", format)); 178 } 179 return retval; 180 } 181 182 /** 183 * {@inheritDoc} 184 * <p> 185 * A deserializer returned by this method is thread-safe. 186 */ 187 @Override 188 public <CLASS extends IBoundObject> IDeserializer<CLASS> newDeserializer( 189 @NonNull Format format, 190 @NonNull Class<CLASS> clazz) { 191 IBoundDefinitionModelAssembly definition; 192 try { 193 definition = IBoundDefinitionModelAssembly.class.cast(getBoundDefinitionForClass(clazz)); 194 } catch (ClassCastException ex) { 195 throw new IllegalStateException( 196 String.format("Class '%s' is not a bound assembly.", clazz.getClass().getName()), 197 ex); 198 } 199 if (definition == null) { 200 throw new IllegalStateException(String.format("Class '%s' is not bound", clazz.getName())); 201 } 202 IDeserializer<CLASS> retval; 203 switch (format) { 204 case JSON: 205 retval = new DefaultJsonDeserializer<>(definition); 206 break; 207 case XML: 208 retval = new DefaultXmlDeserializer<>(definition); 209 break; 210 case YAML: 211 retval = new DefaultYamlDeserializer<>(definition); 212 break; 213 default: 214 throw new UnsupportedOperationException(String.format("Unsupported format '%s'", format)); 215 } 216 217 return retval; 218 } 219 220 @Override 221 public Class<? extends IBoundObject> getBoundClassForRootXmlQName(@NonNull QName rootQName) { 222 Class<? extends IBoundObject> retval = null; 223 for (IBindingMatcher matcher : getBindingMatchers()) { 224 retval = matcher.getBoundClassForXmlQName(rootQName); 225 if (retval != null) { 226 break; 227 } 228 } 229 return retval; 230 } 231 232 @Override 233 public Class<? extends IBoundObject> getBoundClassForRootJsonName(@NonNull String rootName) { 234 Class<? extends IBoundObject> retval = null; 235 for (IBindingMatcher matcher : getBindingMatchers()) { 236 retval = matcher.getBoundClassForJsonName(rootName); 237 if (retval != null) { 238 break; 239 } 240 } 241 return retval; 242 } 243 244 @Override 245 public <CLASS extends IBoundObject> CLASS deepCopy(@NonNull CLASS other, IBoundObject parentInstance) 246 throws BindingException { 247 IBoundDefinitionModelComplex definition = getBoundDefinitionForClass(other.getClass()); 248 if (definition == null) { 249 throw new IllegalStateException(String.format("Class '%s' is not bound", other.getClass().getName())); 250 } 251 return ObjectUtils.asType(definition.deepCopyItem(other, parentInstance)); 252 } 253 254 private static class ModuleLoader 255 extends BindingModuleLoader { 256 257 public ModuleLoader( 258 @NonNull IBindingContext bindingContext, 259 @NonNull ModuleLoadingPostProcessor postProcessor) { 260 super(bindingContext, postProcessor); 261 } 262 263 @Override 264 public IBindingMetaschemaModule load(URI resource) throws MetaschemaException, IOException { 265 IBindingMetaschemaModule module = super.load(resource); 266 getBindingContext().registerModule(module); 267 return module; 268 } 269 270 @Override 271 public IBindingMetaschemaModule load(Path path) throws MetaschemaException, IOException { 272 IBindingMetaschemaModule module = super.load(path); 273 getBindingContext().registerModule(module); 274 return module; 275 } 276 277 @Override 278 public IBindingMetaschemaModule load(URL url) throws MetaschemaException, IOException { 279 IBindingMetaschemaModule module = super.load(url); 280 getBindingContext().registerModule(module); 281 return module; 282 } 283 284 @Override 285 protected IBindingMetaschemaModule newModule( 286 URI resource, 287 METASCHEMA binding, 288 List<? extends IBindingMetaschemaModule> importedModules) throws MetaschemaException { 289 return super.newModule(resource, binding, importedModules); 290 } 291 292 } 293}