001/* 002 * SPDX-FileCopyrightText: none 003 * SPDX-License-Identifier: CC0-1.0 004 */ 005 006package gov.nist.secauto.metaschema.databind.codegen.config; 007 008import gov.nist.secauto.metaschema.core.model.IAssemblyDefinition; 009import gov.nist.secauto.metaschema.core.model.IFieldDefinition; 010import gov.nist.secauto.metaschema.core.model.IModelDefinition; 011import gov.nist.secauto.metaschema.core.model.IModule; 012import gov.nist.secauto.metaschema.core.util.CollectionUtil; 013import gov.nist.secauto.metaschema.core.util.ObjectUtils; 014import gov.nist.secauto.metaschema.databind.codegen.ClassUtils; 015import gov.nist.secauto.metaschema.databind.codegen.xmlbeans.JavaModelBindingType; 016import gov.nist.secauto.metaschema.databind.codegen.xmlbeans.JavaObjectDefinitionBindingType; 017import gov.nist.secauto.metaschema.databind.codegen.xmlbeans.MetaschemaBindingType; 018import gov.nist.secauto.metaschema.databind.codegen.xmlbeans.MetaschemaBindingsDocument; 019import gov.nist.secauto.metaschema.databind.codegen.xmlbeans.MetaschemaBindingsType; 020import gov.nist.secauto.metaschema.databind.codegen.xmlbeans.ModelBindingType; 021import gov.nist.secauto.metaschema.databind.codegen.xmlbeans.ObjectDefinitionBindingType; 022 023import org.apache.xmlbeans.XmlException; 024 025import java.io.File; 026import java.io.IOException; 027import java.net.MalformedURLException; 028import java.net.URI; 029import java.net.URISyntaxException; 030import java.net.URL; 031import java.nio.file.Path; 032import java.util.List; 033import java.util.Map; 034import java.util.Objects; 035import java.util.concurrent.ConcurrentHashMap; 036 037import edu.umd.cs.findbugs.annotations.NonNull; 038import edu.umd.cs.findbugs.annotations.Nullable; 039 040public class DefaultBindingConfiguration implements IBindingConfiguration { 041 private final Map<String, String> namespaceToPackageNameMap = new ConcurrentHashMap<>(); 042 // metaschema location -> ModelType -> Definition name -> IBindingConfiguration 043 private final Map<String, MetaschemaBindingConfiguration> moduleUrlToMetaschemaBindingConfigurationMap 044 = new ConcurrentHashMap<>(); 045 046 @Override 047 public String getPackageNameForModule(IModule module) { 048 URI namespace = module.getXmlNamespace(); 049 return getPackageNameForNamespace(ObjectUtils.notNull(namespace.toASCIIString())); 050 } 051 052 /** 053 * Retrieve the binding configuration for the provided {@code definition}. 054 * 055 * @param definition 056 * the definition to get the config for 057 * @return the binding configuration or {@code null} if there is not 058 * configuration 059 */ 060 @Nullable 061 public IDefinitionBindingConfiguration getBindingConfigurationForDefinition( 062 @NonNull IModelDefinition definition) { 063 String moduleUri = ObjectUtils.notNull(definition.getContainingModule().getLocation().toASCIIString()); 064 String definitionName = definition.getName(); 065 066 MetaschemaBindingConfiguration metaschemaConfig = getMetaschemaBindingConfiguration(moduleUri); 067 068 IDefinitionBindingConfiguration retval = null; 069 if (metaschemaConfig != null) { 070 switch (definition.getModelType()) { 071 case ASSEMBLY: 072 retval = metaschemaConfig.getAssemblyDefinitionBindingConfig(definitionName); 073 break; 074 case FIELD: 075 retval = metaschemaConfig.getFieldDefinitionBindingConfig(definitionName); 076 break; 077 default: 078 throw new UnsupportedOperationException( 079 String.format("Unsupported definition type '%s'", definition.getModelType())); 080 } 081 } 082 return retval; 083 } 084 085 @Override 086 public String getQualifiedBaseClassName(IModelDefinition definition) { 087 IDefinitionBindingConfiguration config = getBindingConfigurationForDefinition(definition); 088 return config == null 089 ? null 090 : config.getQualifiedBaseClassName(); 091 } 092 093 @Override 094 public String getClassName(IModelDefinition definition) { 095 IDefinitionBindingConfiguration config = getBindingConfigurationForDefinition(definition); 096 097 String retval = null; 098 if (config != null) { 099 retval = config.getClassName(); 100 } 101 102 if (retval == null) { 103 retval = ClassUtils.toClassName(definition.getName()); 104 } 105 return retval; 106 } 107 108 @Override 109 public @NonNull String getClassName(@NonNull IModule module) { 110 // TODO: make this configurable 111 return ClassUtils.toClassName(module.getShortName() + "Module"); 112 } 113 114 @Override 115 public List<String> getQualifiedSuperinterfaceClassNames(IModelDefinition definition) { 116 IDefinitionBindingConfiguration config = getBindingConfigurationForDefinition(definition); 117 return config == null 118 ? CollectionUtil.emptyList() 119 : config.getInterfacesToImplement(); 120 } 121 122 /** 123 * Binds an XML namespace, which is normally associated with one or more Module, 124 * with a provided Java package name. 125 * 126 * @param namespace 127 * an XML namespace URI 128 * @param packageName 129 * the package name to associate with the namespace 130 * @throws IllegalStateException 131 * if the binding configuration is changing a previously changed 132 * namespace to package binding 133 */ 134 public void addModelBindingConfig(String namespace, String packageName) { 135 if (namespaceToPackageNameMap.containsKey(namespace)) { 136 String oldPackageName = namespaceToPackageNameMap.get(namespace); 137 if (!oldPackageName.equals(packageName)) { 138 throw new IllegalStateException( 139 String.format("Attempt to redefine existing package name '%s' to '%s' for namespace '%s'", 140 oldPackageName, 141 packageName, 142 namespace)); 143 } // else the same package name, so do nothing 144 } else { 145 namespaceToPackageNameMap.put(namespace, packageName); 146 } 147 } 148 149 /** 150 * Based on the current binding configuration, generate a Java package name for 151 * the provided namespace. If the namespace is already mapped, such as through 152 * the use of {@link #addModelBindingConfig(String, String)}, then the provided 153 * package name will be used. If the namespace is not mapped, then the namespace 154 * URI will be translated into a Java package name. 155 * 156 * @param namespace 157 * the namespace to generate a Java package name for 158 * @return a Java package name 159 */ 160 @NonNull 161 protected String getPackageNameForNamespace(@NonNull String namespace) { 162 String packageName = namespaceToPackageNameMap.get(namespace); 163 if (packageName == null) { 164 packageName = ClassUtils.toPackageName(namespace); 165 } 166 return packageName; 167 } 168 169 /** 170 * Get the binding configuration for the provided Module. 171 * 172 * @param module 173 * the Module module 174 * @return the configuration for the Module or {@code null} if there is no 175 * configuration 176 */ 177 protected MetaschemaBindingConfiguration getMetaschemaBindingConfiguration(@NonNull IModule module) { 178 String moduleUri = ObjectUtils.notNull(module.getLocation().toString()); 179 return getMetaschemaBindingConfiguration(moduleUri); 180 181 } 182 183 /** 184 * Get the binding configuration for the Module modulke located at the provided 185 * {@code moduleUri}. 186 * 187 * @param moduleUri 188 * the location of the Module module 189 * @return the configuration for the Module module or {@code null} if there is 190 * no configuration 191 */ 192 @Nullable 193 protected MetaschemaBindingConfiguration getMetaschemaBindingConfiguration(@NonNull String moduleUri) { 194 return moduleUrlToMetaschemaBindingConfigurationMap.get(moduleUri); 195 } 196 197 /** 198 * Set the binding configuration for the Module module located at the provided 199 * {@code moduleUri}. 200 * 201 * @param moduleUri 202 * the location of the Module module 203 * @param config 204 * the Module binding configuration 205 * @return the old configuration for the Module module or {@code null} if there 206 * was no previous configuration 207 */ 208 public MetaschemaBindingConfiguration addMetaschemaBindingConfiguration( 209 @NonNull String moduleUri, 210 @NonNull MetaschemaBindingConfiguration config) { 211 Objects.requireNonNull(moduleUri, "moduleUri"); 212 Objects.requireNonNull(config, "config"); 213 return moduleUrlToMetaschemaBindingConfigurationMap.put(moduleUri, config); 214 } 215 216 /** 217 * Load the binding configuration from the provided {@code file}. 218 * 219 * @param file 220 * the configuration resource 221 * @throws IOException 222 * if an error occurred while reading the {@code file} 223 */ 224 public void load(Path file) throws IOException { 225 URL resource = file.toAbsolutePath().normalize().toUri().toURL(); 226 load(resource); 227 } 228 229 /** 230 * Load the binding configuration from the provided {@code file}. 231 * 232 * @param file 233 * the configuration resource 234 * @throws IOException 235 * if an error occurred while reading the {@code file} 236 */ 237 public void load(File file) throws IOException { 238 load(file.toPath()); 239 } 240 241 /** 242 * Load the binding configuration from the provided {@code resource}. 243 * 244 * @param resource 245 * the configuration resource 246 * @throws IOException 247 * if an error occurred while reading the {@code resource} 248 */ 249 public void load(URL resource) throws IOException { 250 MetaschemaBindingsDocument xml; 251 try { 252 xml = MetaschemaBindingsDocument.Factory.parse(resource); 253 } catch (XmlException ex) { 254 throw new IOException(ex); 255 } 256 257 MetaschemaBindingsType bindings = xml.getMetaschemaBindings(); 258 259 for (ModelBindingType model : bindings.getModelBindingList()) { 260 processModelBindingConfig(model); 261 } 262 263 for (MetaschemaBindingType metaschema : bindings.getMetaschemaBindingList()) { 264 try { 265 processMetaschemaBindingConfig(resource, metaschema); 266 } catch (MalformedURLException | URISyntaxException ex) { 267 throw new IOException(ex); 268 } 269 } 270 } 271 272 private void processModelBindingConfig(ModelBindingType model) { 273 String namespace = model.getNamespace(); 274 275 if (model.isSetJava()) { 276 JavaModelBindingType java = model.getJava(); 277 if (java.isSetUsePackageName()) { 278 addModelBindingConfig(namespace, java.getUsePackageName()); 279 } 280 } 281 } 282 283 private void processMetaschemaBindingConfig(URL configResource, MetaschemaBindingType metaschema) 284 throws MalformedURLException, URISyntaxException { 285 String href = metaschema.getHref(); 286 URL moduleUrl = new URL(configResource, href); 287 String moduleUri = ObjectUtils.notNull(moduleUrl.toURI().normalize().toString()); 288 289 MetaschemaBindingConfiguration metaschemaConfig = getMetaschemaBindingConfiguration(moduleUri); 290 if (metaschemaConfig == null) { 291 metaschemaConfig = new MetaschemaBindingConfiguration(); 292 addMetaschemaBindingConfiguration(moduleUri, metaschemaConfig); 293 } 294 for (ObjectDefinitionBindingType assemblyBinding : metaschema.getDefineAssemblyBindingList()) { 295 String name = ObjectUtils.requireNonNull(assemblyBinding.getName()); 296 IDefinitionBindingConfiguration config = metaschemaConfig.getAssemblyDefinitionBindingConfig(name); 297 config = processDefinitionBindingConfiguration(config, assemblyBinding); 298 metaschemaConfig.addAssemblyDefinitionBindingConfig(name, config); 299 } 300 301 for (ObjectDefinitionBindingType fieldBinding : metaschema.getDefineFieldBindingList()) { 302 String name = ObjectUtils.requireNonNull(fieldBinding.getName()); 303 IDefinitionBindingConfiguration config = metaschemaConfig.getFieldDefinitionBindingConfig(name); 304 config = processDefinitionBindingConfiguration(config, fieldBinding); 305 metaschemaConfig.addFieldDefinitionBindingConfig(name, config); 306 } 307 } 308 309 @NonNull 310 private static IMutableDefinitionBindingConfiguration processDefinitionBindingConfiguration( 311 @Nullable IDefinitionBindingConfiguration oldConfig, 312 @NonNull ObjectDefinitionBindingType objectDefinitionBinding) { 313 IMutableDefinitionBindingConfiguration config = oldConfig == null 314 ? new DefaultDefinitionBindingConfiguration() 315 : new DefaultDefinitionBindingConfiguration(oldConfig); 316 317 if (objectDefinitionBinding.isSetJava()) { 318 JavaObjectDefinitionBindingType java = objectDefinitionBinding.getJava(); 319 if (java.isSetUseClassName()) { 320 config.setClassName(ObjectUtils.notNull(java.getUseClassName())); 321 } 322 323 if (java.isSetExtendBaseClass()) { 324 config.setQualifiedBaseClassName(ObjectUtils.notNull(java.getExtendBaseClass())); 325 } 326 327 for (String interfaceName : java.getImplementInterfaceList()) { 328 config.addInterfaceToImplement(ObjectUtils.notNull(interfaceName)); 329 } 330 } 331 return config; 332 } 333 334 public static final class MetaschemaBindingConfiguration { 335 private final Map<String, IDefinitionBindingConfiguration> assemblyBindingConfigs = new ConcurrentHashMap<>(); 336 private final Map<String, IDefinitionBindingConfiguration> fieldBindingConfigs = new ConcurrentHashMap<>(); 337 338 private MetaschemaBindingConfiguration() { 339 } 340 341 /** 342 * Get the binding configuration for the {@link IAssemblyDefinition} with the 343 * provided {@code name}. 344 * 345 * @param name 346 * the definition name 347 * @return the definition's binding configuration or {@code null} if no 348 * configuration is provided 349 */ 350 @Nullable 351 public IDefinitionBindingConfiguration getAssemblyDefinitionBindingConfig(@NonNull String name) { 352 return assemblyBindingConfigs.get(name); 353 } 354 355 /** 356 * Get the binding configuration for the {@link IFieldDefinition} with the 357 * provided {@code name}. 358 * 359 * @param name 360 * the definition name 361 * @return the definition's binding configuration or {@code null} if no 362 * configuration is provided 363 */ 364 @Nullable 365 public IDefinitionBindingConfiguration getFieldDefinitionBindingConfig(@NonNull String name) { 366 return fieldBindingConfigs.get(name); 367 } 368 369 /** 370 * Set the binding configuration for the {@link IAssemblyDefinition} with the 371 * provided {@code name}. 372 * 373 * @param name 374 * the definition name 375 * @param config 376 * the new binding configuration for the definition 377 * @return the definition's old binding configuration or {@code null} if no 378 * configuration was previously provided 379 */ 380 @Nullable 381 public IDefinitionBindingConfiguration addAssemblyDefinitionBindingConfig(@NonNull String name, 382 @NonNull IDefinitionBindingConfiguration config) { 383 return assemblyBindingConfigs.put(name, config); 384 } 385 386 /** 387 * Set the binding configuration for the {@link IFieldDefinition} with the 388 * provided {@code name}. 389 * 390 * @param name 391 * the definition name 392 * @param config 393 * the new binding configuration for the definition 394 * @return the definition's old binding configuration or {@code null} if no 395 * configuration was previously provided 396 */ 397 @Nullable 398 public IDefinitionBindingConfiguration addFieldDefinitionBindingConfig(@NonNull String name, 399 @NonNull IDefinitionBindingConfiguration config) { 400 return fieldBindingConfigs.put(name, config); 401 } 402 } 403}