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 110 String getClassName(@NonNull IModule module) { 111 // TODO: make this configurable 112 return ClassUtils.toClassName(module.getShortName() + "Module"); 113 } 114 115 @Override 116 public List<String> getQualifiedSuperinterfaceClassNames(IModelDefinition definition) { 117 IDefinitionBindingConfiguration config = getBindingConfigurationForDefinition(definition); 118 return config == null 119 ? CollectionUtil.emptyList() 120 : config.getInterfacesToImplement(); 121 } 122 123 /** 124 * Binds an XML namespace, which is normally associated with one or more Module, 125 * with a provided Java package name. 126 * 127 * @param namespace 128 * an XML namespace URI 129 * @param packageName 130 * the package name to associate with the namespace 131 * @throws IllegalStateException 132 * if the binding configuration is changing a previously changed 133 * namespace to package binding 134 */ 135 public void addModelBindingConfig(String namespace, String packageName) { 136 if (namespaceToPackageNameMap.containsKey(namespace)) { 137 String oldPackageName = namespaceToPackageNameMap.get(namespace); 138 if (!oldPackageName.equals(packageName)) { 139 throw new IllegalStateException( 140 String.format("Attempt to redefine existing package name '%s' to '%s' for namespace '%s'", 141 oldPackageName, 142 packageName, 143 namespace)); 144 } // else the same package name, so do nothing 145 } else { 146 namespaceToPackageNameMap.put(namespace, packageName); 147 } 148 } 149 150 /** 151 * Based on the current binding configuration, generate a Java package name for 152 * the provided namespace. If the namespace is already mapped, such as through 153 * the use of {@link #addModelBindingConfig(String, String)}, then the provided 154 * package name will be used. If the namespace is not mapped, then the namespace 155 * URI will be translated into a Java package name. 156 * 157 * @param namespace 158 * the namespace to generate a Java package name for 159 * @return a Java package name 160 */ 161 @NonNull 162 protected String getPackageNameForNamespace(@NonNull String namespace) { 163 String packageName = namespaceToPackageNameMap.get(namespace); 164 if (packageName == null) { 165 packageName = ClassUtils.toPackageName(namespace); 166 } 167 return packageName; 168 } 169 170 /** 171 * Get the binding configuration for the provided Module. 172 * 173 * @param module 174 * the Module module 175 * @return the configuration for the Module or {@code null} if there is no 176 * configuration 177 */ 178 protected MetaschemaBindingConfiguration getMetaschemaBindingConfiguration(@NonNull IModule module) { 179 String moduleUri = ObjectUtils.notNull(module.getLocation().toString()); 180 return getMetaschemaBindingConfiguration(moduleUri); 181 182 } 183 184 /** 185 * Get the binding configuration for the Module modulke located at the provided 186 * {@code moduleUri}. 187 * 188 * @param moduleUri 189 * the location of the Module module 190 * @return the configuration for the Module module or {@code null} if there is 191 * no configuration 192 */ 193 @Nullable 194 protected MetaschemaBindingConfiguration getMetaschemaBindingConfiguration(@NonNull String moduleUri) { 195 return moduleUrlToMetaschemaBindingConfigurationMap.get(moduleUri); 196 } 197 198 /** 199 * Set the binding configuration for the Module module located at the provided 200 * {@code moduleUri}. 201 * 202 * @param moduleUri 203 * the location of the Module module 204 * @param config 205 * the Module binding configuration 206 * @return the old configuration for the Module module or {@code null} if there 207 * was no previous configuration 208 */ 209 public MetaschemaBindingConfiguration addMetaschemaBindingConfiguration( 210 @NonNull String moduleUri, 211 @NonNull MetaschemaBindingConfiguration config) { 212 Objects.requireNonNull(moduleUri, "moduleUri"); 213 Objects.requireNonNull(config, "config"); 214 return moduleUrlToMetaschemaBindingConfigurationMap.put(moduleUri, config); 215 } 216 217 /** 218 * Load the binding configuration from the provided {@code file}. 219 * 220 * @param file 221 * the configuration resource 222 * @throws IOException 223 * if an error occurred while reading the {@code file} 224 */ 225 public void load(Path file) throws IOException { 226 URL resource = file.toAbsolutePath().normalize().toUri().toURL(); 227 load(resource); 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(File file) throws IOException { 239 load(file.toPath()); 240 } 241 242 /** 243 * Load the binding configuration from the provided {@code resource}. 244 * 245 * @param resource 246 * the configuration resource 247 * @throws IOException 248 * if an error occurred while reading the {@code resource} 249 */ 250 public void load(URL resource) throws IOException { 251 MetaschemaBindingsDocument xml; 252 try { 253 xml = MetaschemaBindingsDocument.Factory.parse(resource); 254 } catch (XmlException ex) { 255 throw new IOException(ex); 256 } 257 258 MetaschemaBindingsType bindings = xml.getMetaschemaBindings(); 259 260 for (ModelBindingType model : bindings.getModelBindingList()) { 261 processModelBindingConfig(model); 262 } 263 264 for (MetaschemaBindingType metaschema : bindings.getMetaschemaBindingList()) { 265 try { 266 processMetaschemaBindingConfig(resource, metaschema); 267 } catch (MalformedURLException | URISyntaxException ex) { 268 throw new IOException(ex); 269 } 270 } 271 } 272 273 private void processModelBindingConfig(ModelBindingType model) { 274 String namespace = model.getNamespace(); 275 276 if (model.isSetJava()) { 277 JavaModelBindingType java = model.getJava(); 278 if (java.isSetUsePackageName()) { 279 addModelBindingConfig(namespace, java.getUsePackageName()); 280 } 281 } 282 } 283 284 private void processMetaschemaBindingConfig(URL configResource, MetaschemaBindingType metaschema) 285 throws MalformedURLException, URISyntaxException { 286 String href = metaschema.getHref(); 287 URL moduleUrl = new URL(configResource, href); 288 String moduleUri = ObjectUtils.notNull(moduleUrl.toURI().normalize().toString()); 289 290 MetaschemaBindingConfiguration metaschemaConfig = getMetaschemaBindingConfiguration(moduleUri); 291 if (metaschemaConfig == null) { 292 metaschemaConfig = new MetaschemaBindingConfiguration(); 293 addMetaschemaBindingConfiguration(moduleUri, metaschemaConfig); 294 } 295 for (ObjectDefinitionBindingType assemblyBinding : metaschema.getDefineAssemblyBindingList()) { 296 String name = ObjectUtils.requireNonNull(assemblyBinding.getName()); 297 IDefinitionBindingConfiguration config = metaschemaConfig.getAssemblyDefinitionBindingConfig(name); 298 config = processDefinitionBindingConfiguration(config, assemblyBinding); 299 metaschemaConfig.addAssemblyDefinitionBindingConfig(name, config); 300 } 301 302 for (ObjectDefinitionBindingType fieldBinding : metaschema.getDefineFieldBindingList()) { 303 String name = ObjectUtils.requireNonNull(fieldBinding.getName()); 304 IDefinitionBindingConfiguration config = metaschemaConfig.getFieldDefinitionBindingConfig(name); 305 config = processDefinitionBindingConfiguration(config, fieldBinding); 306 metaschemaConfig.addFieldDefinitionBindingConfig(name, config); 307 } 308 } 309 310 @NonNull 311 private static IMutableDefinitionBindingConfiguration processDefinitionBindingConfiguration( 312 @Nullable IDefinitionBindingConfiguration oldConfig, 313 @NonNull ObjectDefinitionBindingType objectDefinitionBinding) { 314 IMutableDefinitionBindingConfiguration config = oldConfig == null 315 ? new DefaultDefinitionBindingConfiguration() 316 : new DefaultDefinitionBindingConfiguration(oldConfig); 317 318 if (objectDefinitionBinding.isSetJava()) { 319 JavaObjectDefinitionBindingType java = objectDefinitionBinding.getJava(); 320 if (java.isSetUseClassName()) { 321 config.setClassName(ObjectUtils.notNull(java.getUseClassName())); 322 } 323 324 if (java.isSetExtendBaseClass()) { 325 config.setQualifiedBaseClassName(ObjectUtils.notNull(java.getExtendBaseClass())); 326 } 327 328 for (String interfaceName : java.getImplementInterfaceList()) { 329 config.addInterfaceToImplement(ObjectUtils.notNull(interfaceName)); 330 } 331 } 332 return config; 333 } 334 335 public static final class MetaschemaBindingConfiguration { 336 private final Map<String, IDefinitionBindingConfiguration> assemblyBindingConfigs = new ConcurrentHashMap<>(); 337 private final Map<String, IDefinitionBindingConfiguration> fieldBindingConfigs = new ConcurrentHashMap<>(); 338 339 private MetaschemaBindingConfiguration() { 340 } 341 342 /** 343 * Get the binding configuration for the {@link IAssemblyDefinition} with the 344 * provided {@code name}. 345 * 346 * @param name 347 * the definition name 348 * @return the definition's binding configuration or {@code null} if no 349 * configuration is provided 350 */ 351 @Nullable 352 public IDefinitionBindingConfiguration getAssemblyDefinitionBindingConfig(@NonNull String name) { 353 return assemblyBindingConfigs.get(name); 354 } 355 356 /** 357 * Get the binding configuration for the {@link IFieldDefinition} with the 358 * provided {@code name}. 359 * 360 * @param name 361 * the definition name 362 * @return the definition's binding configuration or {@code null} if no 363 * configuration is provided 364 */ 365 @Nullable 366 public IDefinitionBindingConfiguration getFieldDefinitionBindingConfig(@NonNull String name) { 367 return fieldBindingConfigs.get(name); 368 } 369 370 /** 371 * Set the binding configuration for the {@link IAssemblyDefinition} with the 372 * provided {@code name}. 373 * 374 * @param name 375 * the definition name 376 * @param config 377 * the new binding configuration for the definition 378 * @return the definition's old binding configuration or {@code null} if no 379 * configuration was previously provided 380 */ 381 @Nullable 382 public IDefinitionBindingConfiguration addAssemblyDefinitionBindingConfig(@NonNull String name, 383 @NonNull IDefinitionBindingConfiguration config) { 384 return assemblyBindingConfigs.put(name, config); 385 } 386 387 /** 388 * Set the binding configuration for the {@link IFieldDefinition} 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 addFieldDefinitionBindingConfig(@NonNull String name, 400 @NonNull IDefinitionBindingConfiguration config) { 401 return fieldBindingConfigs.put(name, config); 402 } 403 } 404}