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