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.IModule; 010import gov.nist.secauto.metaschema.core.util.ObjectUtils; 011import gov.nist.secauto.metaschema.databind.IBindingContext.IBindingMatcher; 012import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelAssembly; 013import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelComplex; 014import gov.nist.secauto.metaschema.databind.model.IBoundModule; 015import gov.nist.secauto.metaschema.databind.model.annotations.MetaschemaAssembly; 016import gov.nist.secauto.metaschema.databind.model.annotations.MetaschemaField; 017import gov.nist.secauto.metaschema.databind.model.annotations.MetaschemaModule; 018import gov.nist.secauto.metaschema.databind.model.annotations.ModelUtil; 019import gov.nist.secauto.metaschema.databind.model.impl.DefinitionAssembly; 020import gov.nist.secauto.metaschema.databind.model.impl.DefinitionField; 021 022import java.util.ArrayList; 023import java.util.Arrays; 024import java.util.HashMap; 025import java.util.List; 026import java.util.Map; 027import java.util.concurrent.ConcurrentHashMap; 028import java.util.concurrent.locks.Lock; 029import java.util.concurrent.locks.ReentrantLock; 030import java.util.stream.Collectors; 031 032import edu.umd.cs.findbugs.annotations.NonNull; 033 034/** 035 * Provides basic module loading capabilities. 036 * 037 * @since 2.0.0 038 */ 039public abstract class AbstractModuleLoaderStrategy implements IBindingContext.IModuleLoaderStrategy { 040 @SuppressWarnings("PMD.UseConcurrentHashMap") 041 @NonNull 042 private final Map<IBoundDefinitionModelAssembly, IBindingMatcher> bindingMatchers = new HashMap<>(); 043 @NonNull 044 private final Map<IModule, IBoundModule> moduleToBoundModuleMap = new ConcurrentHashMap<>(); 045 @SuppressWarnings("PMD.UseConcurrentHashMap") 046 @NonNull 047 private final Map<Class<? extends IBoundModule>, IBoundModule> modulesByClass = new HashMap<>(); 048 049 @NonNull 050 private final Lock modulesLock = new ReentrantLock(); 051 @SuppressWarnings("PMD.UseConcurrentHashMap") 052 @NonNull 053 private final Map<Class<? extends IBoundObject>, IBoundDefinitionModelComplex> definitionsByClass 054 = new HashMap<>(); 055 @NonNull 056 private final Lock definitionsLock = new ReentrantLock(); 057 058 @Override 059 public IBoundModule loadModule( 060 @NonNull Class<? extends IBoundModule> clazz, 061 @NonNull IBindingContext bindingContext) { 062 return lookupInstance(clazz, bindingContext); 063 } 064 065 @Override 066 public IBoundModule registerModule( 067 IModule module, 068 IBindingContext bindingContext) { 069 modulesLock.lock(); 070 try { 071 return ObjectUtils.notNull(moduleToBoundModuleMap.computeIfAbsent(module, key -> { 072 assert key != null; 073 074 IBoundModule boundModule; 075 if (key instanceof IBoundModule) { 076 boundModule = (IBoundModule) key; 077 } else { 078 Class<? extends IBoundModule> moduleClass = handleUnboundModule(key); 079 boundModule = lookupInstance(moduleClass, bindingContext); 080 } 081 082 boundModule.getExportedAssemblyDefinitions().forEach(assembly -> { 083 assert assembly != null; 084 if (assembly.isRoot()) { 085 // force the binding matchers to load 086 registerBindingMatcher(assembly); 087 } 088 }); 089 090 return boundModule; 091 })); 092 } finally { 093 modulesLock.unlock(); 094 } 095 } 096 097 @NonNull 098 protected abstract Class<? extends IBoundModule> handleUnboundModule(@NonNull IModule key); 099 100 /** 101 * Get the Module instance for a given class annotated by the 102 * {@link MetaschemaModule} annotation, instantiating it if needed. 103 * <p> 104 * Will also load any imported Metaschemas. 105 * 106 * 107 * @param moduleClass 108 * the Module class 109 * @param bindingContext 110 * the Metaschema binding context used to lookup binding information 111 * @return the new Module instance 112 */ 113 @NonNull 114 protected IBoundModule lookupInstance( 115 @NonNull Class<? extends IBoundModule> moduleClass, 116 @NonNull IBindingContext bindingContext) { 117 IBoundModule retval; 118 modulesLock.lock(); 119 try { 120 retval = modulesByClass.get(moduleClass); 121 if (retval == null) { 122 if (!moduleClass.isAnnotationPresent(MetaschemaModule.class)) { 123 throw new IllegalStateException(String.format("The class '%s' is missing the '%s' annotation", 124 moduleClass.getCanonicalName(), MetaschemaModule.class.getCanonicalName())); 125 } 126 127 retval = IBoundModule.newInstance(moduleClass, bindingContext, getImportedModules(moduleClass, bindingContext)); 128 modulesByClass.put(moduleClass, retval); 129 } 130 } finally { 131 modulesLock.unlock(); 132 } 133 return retval; 134 } 135 136 @NonNull 137 protected IBindingMatcher registerBindingMatcher(@NonNull IBoundDefinitionModelAssembly definition) { 138 IBindingMatcher retval; 139 modulesLock.lock(); 140 try { 141 retval = bindingMatchers.get(definition); 142 if (retval == null) { 143 if (!definition.isRoot()) { 144 throw new IllegalArgumentException( 145 String.format("The provided definition '%s' is not a root assembly.", 146 definition.getBoundClass().getName())); 147 } 148 149 retval = IBindingMatcher.of(definition); 150 bindingMatchers.put(definition, retval); 151 } 152 } finally { 153 modulesLock.unlock(); 154 } 155 return retval; 156 } 157 158 @Override 159 public final List<IBindingMatcher> getBindingMatchers() { 160 modulesLock.lock(); 161 try { 162 // make a defensive copy 163 return new ArrayList<>(bindingMatchers.values()); 164 } finally { 165 modulesLock.unlock(); 166 } 167 } 168 169 @NonNull 170 private List<IBoundModule> getImportedModules( 171 @NonNull Class<? extends IBoundModule> moduleClass, 172 @NonNull IBindingContext bindingContext) { 173 MetaschemaModule moduleAnnotation = moduleClass.getAnnotation(MetaschemaModule.class); 174 175 return ObjectUtils.notNull(Arrays.stream(moduleAnnotation.imports()) 176 .map(clazz -> lookupInstance(ObjectUtils.requireNonNull(clazz), bindingContext)) 177 .collect(Collectors.toUnmodifiableList())); 178 } 179 180 @Override 181 public IBoundDefinitionModelComplex getBoundDefinitionForClass( 182 @NonNull Class<? extends IBoundObject> clazz, 183 @NonNull IBindingContext bindingContext) { 184 185 IBoundDefinitionModelComplex retval; 186 definitionsLock.lock(); 187 try { 188 retval = definitionsByClass.get(clazz); 189 if (retval == null) { 190 retval = newBoundDefinition(clazz, bindingContext); 191 definitionsByClass.put(clazz, retval); 192 } 193 194 // // force loading of metaschema information to apply constraints 195 // IModule module = retval.getContainingModule(); 196 // registerModule(module, bindingContext); 197 return retval; 198 } finally { 199 definitionsLock.unlock(); 200 } 201 } 202 203 @NonNull 204 private IBoundDefinitionModelComplex newBoundDefinition( 205 @NonNull Class<? extends IBoundObject> clazz, 206 @NonNull IBindingContext bindingContext) { 207 IBoundDefinitionModelComplex retval; 208 if (clazz.isAnnotationPresent(MetaschemaAssembly.class)) { 209 MetaschemaAssembly annotation = ModelUtil.getAnnotation(clazz, MetaschemaAssembly.class); 210 Class<? extends IBoundModule> moduleClass = annotation.moduleClass(); 211 IBoundModule module = loadModule(moduleClass, bindingContext); 212 retval = DefinitionAssembly.newInstance(clazz, annotation, module, bindingContext); 213 } else if (clazz.isAnnotationPresent(MetaschemaField.class)) { 214 MetaschemaField annotation = ModelUtil.getAnnotation(clazz, MetaschemaField.class); 215 Class<? extends IBoundModule> moduleClass = annotation.moduleClass(); 216 IBoundModule module = loadModule(moduleClass, bindingContext); 217 retval = DefinitionField.newInstance(clazz, annotation, module, bindingContext); 218 } else { 219 throw new IllegalArgumentException(String.format("Unable to find bound definition for class '%s'.", 220 clazz.getName())); 221 } 222 return retval; 223 } 224}