001/* 002 * Portions of this software was developed by employees of the National Institute 003 * of Standards and Technology (NIST), an agency of the Federal Government and is 004 * being made available as a public service. Pursuant to title 17 United States 005 * Code Section 105, works of NIST employees are not subject to copyright 006 * protection in the United States. This software may be subject to foreign 007 * copyright. Permission in the United States and in foreign countries, to the 008 * extent that NIST may hold copyright, to use, copy, modify, create derivative 009 * works, and distribute this software and its documentation without fee is hereby 010 * granted on a non-exclusive basis, provided that this notice and disclaimer 011 * of warranty appears in all copies. 012 * 013 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER 014 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY 015 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF 016 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM 017 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE 018 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE. IN NO EVENT 019 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT, 020 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM, 021 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY, 022 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR 023 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT 024 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER. 025 */ 026 027package gov.nist.secauto.metaschema.databind.codegen.config; 028 029import gov.nist.secauto.metaschema.core.model.IAssemblyDefinition; 030import gov.nist.secauto.metaschema.core.model.IFieldDefinition; 031import gov.nist.secauto.metaschema.core.model.IFlagContainer; 032import gov.nist.secauto.metaschema.core.model.IModule; 033import gov.nist.secauto.metaschema.core.util.ObjectUtils; 034import gov.nist.secauto.metaschema.databind.codegen.ClassUtils; 035import gov.nist.secauto.metaschema.databind.codegen.xmlbeans.JavaModelBindingType; 036import gov.nist.secauto.metaschema.databind.codegen.xmlbeans.JavaObjectDefinitionBindingType; 037import gov.nist.secauto.metaschema.databind.codegen.xmlbeans.MetaschemaBindingType; 038import gov.nist.secauto.metaschema.databind.codegen.xmlbeans.MetaschemaBindingsDocument; 039import gov.nist.secauto.metaschema.databind.codegen.xmlbeans.MetaschemaBindingsType; 040import gov.nist.secauto.metaschema.databind.codegen.xmlbeans.ModelBindingType; 041import gov.nist.secauto.metaschema.databind.codegen.xmlbeans.ObjectDefinitionBindingType; 042 043import org.apache.xmlbeans.XmlException; 044 045import java.io.File; 046import java.io.IOException; 047import java.net.MalformedURLException; 048import java.net.URI; 049import java.net.URISyntaxException; 050import java.net.URL; 051import java.nio.file.Path; 052import java.util.Map; 053import java.util.Objects; 054import java.util.concurrent.ConcurrentHashMap; 055 056import edu.umd.cs.findbugs.annotations.NonNull; 057import edu.umd.cs.findbugs.annotations.Nullable; 058 059public class DefaultBindingConfiguration implements IBindingConfiguration { 060 private final Map<String, String> namespaceToPackageNameMap = new ConcurrentHashMap<>(); 061 // metaschema location -> ModelType -> Definition Name -> IBindingConfiguration 062 private final Map<String, MetaschemaBindingConfiguration> moduleUrlToMetaschemaBindingConfigurationMap 063 = new ConcurrentHashMap<>(); 064 065 @Override 066 public String getPackageNameForModule(IModule module) { 067 URI namespace = module.getXmlNamespace(); 068 return getPackageNameForNamespace(ObjectUtils.notNull(namespace.toASCIIString())); 069 } 070 071 /** 072 * Retrieve the binding configuration for the provided {@code definition}. 073 * 074 * @param definition 075 * the definition to get the config for 076 * @return the binding configuration or {@code null} if there is not 077 * configuration 078 */ 079 @Nullable 080 public IDefinitionBindingConfiguration getBindingConfigurationForDefinition( 081 @NonNull IFlagContainer definition) { 082 String moduleUri = ObjectUtils.notNull(definition.getContainingModule().getLocation().toString()); 083 String definitionName = definition.getName(); 084 085 MetaschemaBindingConfiguration metaschemaConfig = getMetaschemaBindingConfiguration(moduleUri); 086 087 IDefinitionBindingConfiguration retval = null; 088 if (metaschemaConfig != null) { 089 switch (definition.getModelType()) { 090 case ASSEMBLY: 091 retval = metaschemaConfig.getAssemblyDefinitionBindingConfig(definitionName); 092 break; 093 case FIELD: 094 retval = metaschemaConfig.getFieldDefinitionBindingConfig(definitionName); 095 break; 096 default: 097 throw new UnsupportedOperationException( 098 String.format("Unsupported definition type '%s'", definition.getModelType())); 099 } 100 } 101 return retval; 102 } 103 104 @Override 105 public String getQualifiedBaseClassName(IFlagContainer definition) { 106 IDefinitionBindingConfiguration config = getBindingConfigurationForDefinition(definition); 107 108 String retval = null; 109 if (config != null) { 110 retval = config.getQualifiedBaseClassName(); 111 } 112 return retval; 113 } 114 115 @Override 116 public String getClassName(IFlagContainer definition) { 117 IDefinitionBindingConfiguration config = getBindingConfigurationForDefinition(definition); 118 119 String retval = null; 120 if (config != null) { 121 retval = config.getClassName(); 122 } 123 124 if (retval == null) { 125 retval = ClassUtils.toClassName(definition.getName()); 126 } 127 return retval; 128 } 129 130 @Override 131 public @NonNull String getClassName(@NonNull IModule module) { 132 // TODO: make this configurable 133 return ClassUtils.toClassName(module.getShortName() + "Module"); 134 } 135 136 /** 137 * Binds an XML namespace, which is normally associated with one or more Module, 138 * with a provided Java package name. 139 * 140 * @param namespace 141 * an XML namespace URI 142 * @param packageName 143 * the package name to associate with the namespace 144 * @throws IllegalStateException 145 * if the binding configuration is changing a previously changed 146 * namespace to package binding 147 */ 148 public void addModelBindingConfig(String namespace, String packageName) { 149 if (namespaceToPackageNameMap.containsKey(namespace)) { 150 String oldPackageName = namespaceToPackageNameMap.get(namespace); 151 if (!oldPackageName.equals(packageName)) { 152 throw new IllegalStateException( 153 String.format("Attempt to redefine existing package name '%s' to '%s' for namespace '%s'", 154 oldPackageName, 155 packageName, 156 namespace)); 157 } // else the same package name, so do nothing 158 } else { 159 namespaceToPackageNameMap.put(namespace, packageName); 160 } 161 } 162 163 /** 164 * Based on the current binding configuration, generate a Java package name for 165 * the provided namespace. If the namespace is already mapped, such as through 166 * the use of {@link #addModelBindingConfig(String, String)}, then the provided 167 * package name will be used. If the namespace is not mapped, then the namespace 168 * URI will be translated into a Java package name. 169 * 170 * @param namespace 171 * the namespace to generate a Java package name for 172 * @return a Java package name 173 */ 174 @NonNull 175 protected String getPackageNameForNamespace(@NonNull String namespace) { 176 String packageName = namespaceToPackageNameMap.get(namespace); 177 if (packageName == null) { 178 packageName = ClassUtils.toPackageName(namespace); 179 } 180 return packageName; 181 } 182 183 /** 184 * Get the binding configuration for the provided Module. 185 * 186 * @param module 187 * the Module module 188 * @return the configuration for the Module or {@code null} if there is no 189 * configuration 190 */ 191 protected MetaschemaBindingConfiguration getMetaschemaBindingConfiguration(@NonNull IModule module) { 192 String moduleUri = ObjectUtils.notNull(module.getLocation().toString()); 193 return getMetaschemaBindingConfiguration(moduleUri); 194 195 } 196 197 /** 198 * Get the binding configuration for the Module modulke located at the provided 199 * {@code moduleUri}. 200 * 201 * @param moduleUri 202 * the location of the Module module 203 * @return the configuration for the Module module or {@code null} if there is 204 * no configuration 205 */ 206 @Nullable 207 protected MetaschemaBindingConfiguration getMetaschemaBindingConfiguration(@NonNull String moduleUri) { 208 return moduleUrlToMetaschemaBindingConfigurationMap.get(moduleUri); 209 } 210 211 /** 212 * Set the binding configuration for the Module module located at the provided 213 * {@code moduleUri}. 214 * 215 * @param moduleUri 216 * the location of the Module module 217 * @param config 218 * the Module binding configuration 219 * @return the old configuration for the Module module or {@code null} if there 220 * was no previous configuration 221 */ 222 public MetaschemaBindingConfiguration addMetaschemaBindingConfiguration( 223 @NonNull String moduleUri, 224 @NonNull MetaschemaBindingConfiguration config) { 225 Objects.requireNonNull(moduleUri, "moduleUri"); 226 Objects.requireNonNull(config, "config"); 227 return moduleUrlToMetaschemaBindingConfigurationMap.put(moduleUri, config); 228 } 229 230 /** 231 * Load the binding configuration from the provided {@code file}. 232 * 233 * @param file 234 * the configuration resource 235 * @throws IOException 236 * if an error occurred while reading the {@code file} 237 */ 238 public void load(Path file) throws IOException { 239 URL resource = file.toUri().toURL(); 240 load(resource); 241 } 242 243 /** 244 * Load the binding configuration from the provided {@code file}. 245 * 246 * @param file 247 * the configuration resource 248 * @throws IOException 249 * if an error occurred while reading the {@code file} 250 */ 251 public void load(File file) throws IOException { 252 URL resource = file.toURI().toURL(); 253 load(resource); 254 } 255 256 /** 257 * Load the binding configuration from the provided {@code resource}. 258 * 259 * @param resource 260 * the configuration resource 261 * @throws IOException 262 * if an error occurred while reading the {@code resource} 263 */ 264 public void load(URL resource) throws IOException { 265 MetaschemaBindingsDocument xml; 266 try { 267 xml = MetaschemaBindingsDocument.Factory.parse(resource); 268 } catch (XmlException ex) { 269 throw new IOException(ex); 270 } 271 272 MetaschemaBindingsType bindings = xml.getMetaschemaBindings(); 273 274 for (ModelBindingType model : bindings.getModelBindingList()) { 275 processModelBindingConfig(model); 276 } 277 278 for (MetaschemaBindingType metaschema : bindings.getMetaschemaBindingList()) { 279 try { 280 processMetaschemaBindingConfig(resource, metaschema); 281 } catch (MalformedURLException | URISyntaxException ex) { 282 throw new IOException(ex); 283 } 284 } 285 } 286 287 private void processModelBindingConfig(ModelBindingType model) { 288 String namespace = model.getNamespace(); 289 290 if (model.isSetJava()) { 291 JavaModelBindingType java = model.getJava(); 292 if (java.isSetUsePackageName()) { 293 addModelBindingConfig(namespace, java.getUsePackageName()); 294 } 295 } 296 } 297 298 private void processMetaschemaBindingConfig(URL configResource, MetaschemaBindingType metaschema) 299 throws MalformedURLException, URISyntaxException { 300 String href = metaschema.getHref(); 301 URL moduleUrl = new URL(configResource, href); 302 String moduleUri = ObjectUtils.notNull(moduleUrl.toURI().toString()); 303 304 MetaschemaBindingConfiguration metaschemaConfig = getMetaschemaBindingConfiguration(moduleUri); 305 if (metaschemaConfig == null) { 306 metaschemaConfig = new MetaschemaBindingConfiguration(); 307 addMetaschemaBindingConfiguration(moduleUri, metaschemaConfig); 308 } 309 for (ObjectDefinitionBindingType assemblyBinding : metaschema.getDefineAssemblyBindingList()) { 310 String name = ObjectUtils.requireNonNull(assemblyBinding.getName()); 311 IDefinitionBindingConfiguration config = metaschemaConfig.getAssemblyDefinitionBindingConfig(name); 312 config = processDefinitionBindingConfiguration(config, assemblyBinding); 313 metaschemaConfig.addAssemblyDefinitionBindingConfig(name, config); 314 } 315 316 for (ObjectDefinitionBindingType fieldBinding : metaschema.getDefineFieldBindingList()) { 317 String name = ObjectUtils.requireNonNull(fieldBinding.getName()); 318 IDefinitionBindingConfiguration config = metaschemaConfig.getFieldDefinitionBindingConfig(name); 319 config = processDefinitionBindingConfiguration(config, fieldBinding); 320 metaschemaConfig.addFieldDefinitionBindingConfig(name, config); 321 } 322 } 323 324 @NonNull 325 private static IMutableDefinitionBindingConfiguration processDefinitionBindingConfiguration( 326 @Nullable IDefinitionBindingConfiguration oldConfig, 327 @NonNull ObjectDefinitionBindingType objectDefinitionBinding) { 328 IMutableDefinitionBindingConfiguration config; 329 if (oldConfig != null) { 330 config = new DefaultDefinitionBindingConfiguration(oldConfig); 331 } else { 332 config = new DefaultDefinitionBindingConfiguration(); 333 } 334 335 if (objectDefinitionBinding.isSetJava()) { 336 JavaObjectDefinitionBindingType java = objectDefinitionBinding.getJava(); 337 if (java.isSetUseClassName()) { 338 config.setClassName(ObjectUtils.notNull(java.getUseClassName())); 339 } 340 341 if (java.isSetExtendBaseClass()) { 342 config.setQualifiedBaseClassName(ObjectUtils.notNull(java.getExtendBaseClass())); 343 } 344 345 for (String interfaceName : java.getImplementInterfaceList()) { 346 config.addInterfaceToImplement(ObjectUtils.notNull(interfaceName)); 347 } 348 } 349 return config; 350 } 351 352 public static final class MetaschemaBindingConfiguration { 353 private final Map<String, IDefinitionBindingConfiguration> assemblyBindingConfigs = new ConcurrentHashMap<>(); 354 private final Map<String, IDefinitionBindingConfiguration> fieldBindingConfigs = new ConcurrentHashMap<>(); 355 356 private MetaschemaBindingConfiguration() { 357 } 358 359 /** 360 * Get the binding configuration for the {@link IAssemblyDefinition} with the 361 * provided {@code name}. 362 * 363 * @param name 364 * the definition name 365 * @return the definition's binding configuration or {@code null} if no 366 * configuration is provided 367 */ 368 @Nullable 369 public IDefinitionBindingConfiguration getAssemblyDefinitionBindingConfig(@NonNull String name) { 370 return assemblyBindingConfigs.get(name); 371 } 372 373 /** 374 * Get the binding configuration for the {@link IFieldDefinition} with the 375 * provided {@code name}. 376 * 377 * @param name 378 * the definition name 379 * @return the definition's binding configuration or {@code null} if no 380 * configuration is provided 381 */ 382 @Nullable 383 public IDefinitionBindingConfiguration getFieldDefinitionBindingConfig(@NonNull String name) { 384 return fieldBindingConfigs.get(name); 385 } 386 387 /** 388 * Set the binding configuration for the {@link IAssemblyDefinition} with the 389 * provided {@code name}. 390 * 391 * @param name 392 * the definition name 393 * @param config 394 * the new binding configuration for the definition 395 * @return the definition's old binding configuration or {@code null} if no 396 * configuration was previously provided 397 */ 398 @Nullable 399 public IDefinitionBindingConfiguration addAssemblyDefinitionBindingConfig(@NonNull String name, 400 @NonNull IDefinitionBindingConfiguration config) { 401 return assemblyBindingConfigs.put(name, config); 402 } 403 404 /** 405 * Set the binding configuration for the {@link IFieldDefinition} with the 406 * provided {@code name}. 407 * 408 * @param name 409 * the definition name 410 * @param config 411 * the new binding configuration for the definition 412 * @return the definition's old binding configuration or {@code null} if no 413 * configuration was previously provided 414 */ 415 @Nullable 416 public IDefinitionBindingConfiguration addFieldDefinitionBindingConfig(@NonNull String name, 417 @NonNull IDefinitionBindingConfiguration config) { 418 return fieldBindingConfigs.put(name, config); 419 } 420 } 421}