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.datatype.DataTypeService; 009import gov.nist.secauto.metaschema.core.datatype.IDataTypeAdapter; 010import gov.nist.secauto.metaschema.core.model.IBoundObject; 011import gov.nist.secauto.metaschema.core.model.IModule; 012import gov.nist.secauto.metaschema.core.model.IModuleLoader; 013import gov.nist.secauto.metaschema.core.util.ObjectUtils; 014import gov.nist.secauto.metaschema.databind.codegen.IProduction; 015import gov.nist.secauto.metaschema.databind.codegen.ModuleCompilerHelper; 016import gov.nist.secauto.metaschema.databind.io.BindingException; 017import gov.nist.secauto.metaschema.databind.io.DefaultBoundLoader; 018import gov.nist.secauto.metaschema.databind.io.Format; 019import gov.nist.secauto.metaschema.databind.io.IDeserializer; 020import gov.nist.secauto.metaschema.databind.io.ISerializer; 021import gov.nist.secauto.metaschema.databind.io.json.DefaultJsonDeserializer; 022import gov.nist.secauto.metaschema.databind.io.json.DefaultJsonSerializer; 023import gov.nist.secauto.metaschema.databind.io.xml.DefaultXmlDeserializer; 024import gov.nist.secauto.metaschema.databind.io.xml.DefaultXmlSerializer; 025import gov.nist.secauto.metaschema.databind.io.yaml.DefaultYamlDeserializer; 026import gov.nist.secauto.metaschema.databind.io.yaml.DefaultYamlSerializer; 027import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelAssembly; 028import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelComplex; 029import gov.nist.secauto.metaschema.databind.model.IBoundModule; 030import gov.nist.secauto.metaschema.databind.model.binding.metaschema.METASCHEMA; 031 032import java.io.IOException; 033import java.nio.file.Files; 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; 045 046/** 047 * The implementation of a {@link IBindingContext} provided by this library. 048 * <p> 049 * This implementation caches Module information, which can dramatically improve 050 * read and write performance at the cost of some memory use. Thus, using the 051 * same singleton of this class across multiple I/O operations will improve 052 * overall read and write performance when processing the same types of data. 053 * <p> 054 * Serializers and deserializers provided by this class using the 055 * {@link #newSerializer(Format, Class)} and 056 * {@link #newDeserializer(Format, Class)} methods will 057 * <p> 058 * This class is synchronized and is thread-safe. 059 */ 060public class DefaultBindingContext implements IBindingContext { 061 private static DefaultBindingContext singleton; 062 @NonNull 063 private final IModuleLoaderStrategy moduleLoaderStrategy; 064 @NonNull 065 private final Map<Class<?>, IBoundDefinitionModelComplex> boundClassToStrategyMap = new ConcurrentHashMap<>(); 066 @NonNull 067 private final Map<IBoundDefinitionModelAssembly, IBindingMatcher> bindingMatchers = new ConcurrentHashMap<>(); 068 069 /** 070 * Get the singleton instance of this binding context. 071 * 072 * @return the binding context 073 */ 074 @NonNull 075 public static DefaultBindingContext instance() { 076 synchronized (DefaultBindingContext.class) { 077 if (singleton == null) { 078 singleton = new DefaultBindingContext(); 079 } 080 } 081 return ObjectUtils.notNull(singleton); 082 } 083 084 /** 085 * Construct a new binding context. 086 * 087 * @param modulePostProcessors 088 * a list of module post processors to call after loading a module 089 */ 090 @SuppressFBWarnings("CT_CONSTRUCTOR_THROW") 091 public DefaultBindingContext(@NonNull List<IModuleLoader.IModulePostProcessor> modulePostProcessors) { 092 // only allow extended classes 093 moduleLoaderStrategy = new PostProcessingModuleLoaderStrategy(this, modulePostProcessors); 094 registerBindingMatcher(METASCHEMA.class); 095 } 096 097 /** 098 * Construct a new binding context. 099 */ 100 @SuppressFBWarnings("CT_CONSTRUCTOR_THROW") 101 public DefaultBindingContext() { 102 // only allow extended classes 103 moduleLoaderStrategy = new SimpleModuleLoaderStrategy(this); 104 registerBindingMatcher(METASCHEMA.class); 105 } 106 107 @NonNull 108 protected IModuleLoaderStrategy getModuleLoaderStrategy() { 109 return moduleLoaderStrategy; 110 } 111 112 /** 113 * Get the binding matchers that are associated with this class. 114 * 115 * @return the list of matchers 116 * @see #registerBindingMatcher(Class) 117 * @see #registerBindingMatcher(IBoundDefinitionModelAssembly) 118 */ 119 @NonNull 120 protected Collection<IBindingMatcher> getBindingMatchers() { 121 return ObjectUtils.notNull(bindingMatchers.values()); 122 } 123 124 @Override 125 @NonNull 126 public final IBindingMatcher registerBindingMatcher(@NonNull IBoundDefinitionModelAssembly definition) { 127 return ObjectUtils.notNull(bindingMatchers.computeIfAbsent(definition, key -> IBindingMatcher.of(definition))); 128 } 129 130 @Override 131 public final IBindingMatcher registerBindingMatcher(@NonNull Class<? extends IBoundObject> clazz) { 132 IBoundDefinitionModelComplex definition = getBoundDefinitionForClass(clazz); 133 if (definition == null) { 134 throw new IllegalArgumentException(String.format("Unable to find bound definition for class '%s'.", 135 clazz.getName())); 136 } 137 138 try { 139 IBoundDefinitionModelAssembly assemblyDefinition = IBoundDefinitionModelAssembly.class.cast(definition); 140 return registerBindingMatcher(ObjectUtils.notNull(assemblyDefinition)); 141 } catch (ClassCastException ex) { 142 throw new IllegalArgumentException( 143 String.format("The provided class '%s' is not a root assembly.", clazz.getName()), ex); 144 } 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); 156 } 157 158 @Override 159 public <TYPE extends IDataTypeAdapter<?>> TYPE getJavaTypeAdapterInstance(@NonNull Class<TYPE> clazz) { 160 return DataTypeService.getInstance().getJavaTypeAdapterByClass(clazz); 161 } 162 163 /** 164 * {@inheritDoc} 165 * <p> 166 * A serializer returned by this method is thread-safe. 167 */ 168 @Override 169 public <CLASS extends IBoundObject> ISerializer<CLASS> newSerializer( 170 @NonNull Format format, 171 @NonNull Class<CLASS> clazz) { 172 Objects.requireNonNull(format, "format"); 173 IBoundDefinitionModelAssembly definition; 174 try { 175 definition = IBoundDefinitionModelAssembly.class.cast(getBoundDefinitionForClass(clazz)); 176 } catch (ClassCastException ex) { 177 throw new IllegalStateException( 178 String.format("Class '%s' is not a bound assembly.", clazz.getClass().getName()), ex); 179 } 180 if (definition == null) { 181 throw new IllegalStateException(String.format("Class '%s' is not bound", clazz.getClass().getName())); 182 } 183 ISerializer<CLASS> retval; 184 switch (format) { 185 case JSON: 186 retval = new DefaultJsonSerializer<>(definition); 187 break; 188 case XML: 189 retval = new DefaultXmlSerializer<>(definition); 190 break; 191 case YAML: 192 retval = new DefaultYamlSerializer<>(definition); 193 break; 194 default: 195 throw new UnsupportedOperationException(String.format("Unsupported format '%s'", format)); 196 } 197 return retval; 198 } 199 200 /** 201 * {@inheritDoc} 202 * <p> 203 * A deserializer returned by this method is thread-safe. 204 */ 205 @Override 206 public <CLASS extends IBoundObject> IDeserializer<CLASS> newDeserializer( 207 @NonNull Format format, 208 @NonNull Class<CLASS> clazz) { 209 IBoundDefinitionModelAssembly definition; 210 try { 211 definition = IBoundDefinitionModelAssembly.class.cast(getBoundDefinitionForClass(clazz)); 212 } catch (ClassCastException ex) { 213 throw new IllegalStateException( 214 String.format("Class '%s' is not a bound assembly.", clazz.getClass().getName()), 215 ex); 216 } 217 if (definition == null) { 218 throw new IllegalStateException(String.format("Class '%s' is not bound", clazz.getName())); 219 } 220 IDeserializer<CLASS> retval; 221 switch (format) { 222 case JSON: 223 retval = new DefaultJsonDeserializer<>(definition); 224 break; 225 case XML: 226 retval = new DefaultXmlDeserializer<>(definition); 227 break; 228 case YAML: 229 retval = new DefaultYamlDeserializer<>(definition); 230 break; 231 default: 232 throw new UnsupportedOperationException(String.format("Unsupported format '%s'", format)); 233 } 234 235 return retval; 236 } 237 238 @Override 239 @SuppressWarnings({ "PMD.UseProperClassLoader", "unchecked" }) // false positive 240 @NonNull 241 public IBindingContext registerModule( 242 @NonNull IModule module, 243 @NonNull Path compilePath) throws IOException { 244 if (!(module instanceof IBoundModule)) { 245 Files.createDirectories(compilePath); 246 247 ClassLoader classLoader = ModuleCompilerHelper.newClassLoader( 248 compilePath, 249 ObjectUtils.notNull(Thread.currentThread().getContextClassLoader())); 250 251 IProduction production = ModuleCompilerHelper.compileMetaschema(module, compilePath); 252 production.getModuleProductions().stream() 253 .map(item -> { 254 try { 255 return (Class<? extends IBoundModule>) classLoader.loadClass(item.getClassName().reflectionName()); 256 } catch (ClassNotFoundException ex) { 257 throw new IllegalStateException(ex); 258 } 259 }) 260 .forEachOrdered(clazz -> { 261 IBoundModule boundModule = registerModule(ObjectUtils.notNull(clazz)); 262 // force the binding matchers to load 263 boundModule.getRootAssemblyDefinitions(); 264 }); 265 } 266 return this; 267 } 268 269 @Override 270 public IBoundModule registerModule(Class<? extends IBoundModule> clazz) { 271 return getModuleLoaderStrategy().loadModule(clazz); 272 // retval.getExportedAssemblyDefinitions().stream() 273 // .map(def -> (IBoundDefinitionModelAssembly) def) 274 // .filter(def -> def.isRoot()) 275 // .forEachOrdered(def -> registerBindingMatcher(ObjectUtils.notNull(def))); 276 // return retval; 277 } 278 279 @Override 280 public Class<? extends IBoundObject> getBoundClassForRootXmlQName(@NonNull QName rootQName) { 281 Class<? extends IBoundObject> retval = null; 282 for (IBindingMatcher matcher : getBindingMatchers()) { 283 retval = matcher.getBoundClassForXmlQName(rootQName); 284 if (retval != null) { 285 break; 286 } 287 } 288 return retval; 289 } 290 291 @Override 292 public Class<? extends IBoundObject> getBoundClassForRootJsonName(@NonNull String rootName) { 293 Class<? extends IBoundObject> retval = null; 294 for (IBindingMatcher matcher : getBindingMatchers()) { 295 retval = matcher.getBoundClassForJsonName(rootName); 296 if (retval != null) { 297 break; 298 } 299 } 300 return retval; 301 } 302 303 @Override 304 public DefaultBoundLoader newBoundLoader() { 305 return new DefaultBoundLoader(this); 306 } 307 308 @Override 309 public <CLASS extends IBoundObject> CLASS deepCopy(@NonNull CLASS other, IBoundObject parentInstance) 310 throws BindingException { 311 IBoundDefinitionModelComplex definition = getBoundDefinitionForClass(other.getClass()); 312 if (definition == null) { 313 throw new IllegalStateException(String.format("Class '%s' is not bound", other.getClass().getName())); 314 } 315 return ObjectUtils.asType(definition.deepCopyItem(other, parentInstance)); 316 } 317}