/* * Copyright (c) 2010-2015 Evolveum * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.evolveum.midpoint.prism.schema; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.function.Function; import java.util.stream.Collectors; import javax.xml.XMLConstants; import javax.xml.namespace.QName; import javax.xml.transform.Source; import javax.xml.validation.SchemaFactory; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.xml.XmlTypeConverter; import com.evolveum.midpoint.prism.xml.XsdTypeMapper; import com.evolveum.midpoint.util.QNameUtil; import com.evolveum.prism.xml.ns._public.types_3.ObjectType; import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; import org.apache.commons.lang.StringUtils; import org.apache.xml.resolver.Catalog; import org.apache.xml.resolver.CatalogManager; import org.apache.xml.resolver.tools.CatalogResolver; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.springframework.beans.factory.annotation.Autowired; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.xml.sax.EntityResolver; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.xml.DynamicNamespacePrefixMapper; import com.evolveum.midpoint.util.ClassPathUtil; import com.evolveum.midpoint.util.DOMUtil; import com.evolveum.midpoint.util.DebugDumpable; import com.evolveum.midpoint.util.DebugUtil; import com.evolveum.midpoint.util.JAXBUtil; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; /** * Registry and resolver of schema files and resources. * * * @author Radovan Semancik * */ public class SchemaRegistryImpl implements DebugDumpable, SchemaRegistry { private static final QName DEFAULT_XSD_TYPE = DOMUtil.XSD_STRING; private static final String DEFAULT_RUNTIME_CATALOG_RESOURCE = "META-INF/catalog-runtime.xml"; private File[] catalogFiles; // overrides catalog resource name private String catalogResourceName = DEFAULT_RUNTIME_CATALOG_RESOURCE; private javax.xml.validation.SchemaFactory schemaFactory; private javax.xml.validation.Schema javaxSchema; private EntityResolver builtinSchemaResolver; final private List<SchemaDescription> schemaDescriptions = new ArrayList<>(); final private Map<String,SchemaDescription> parsedSchemas = new HashMap<>(); final private Map<QName,ComplexTypeDefinition> extensionSchemas = new HashMap<>(); private boolean initialized = false; private DynamicNamespacePrefixMapper namespacePrefixMapper; private String defaultNamespace; private XmlEntityResolver entityResolver = new XmlEntityResolverImpl(this); @Autowired // TODO does this work? private PrismContext prismContext; private static final Trace LOGGER = TraceManager.getTrace(SchemaRegistry.class); @Override public DynamicNamespacePrefixMapper getNamespacePrefixMapper() { return namespacePrefixMapper; } public void setNamespacePrefixMapper(DynamicNamespacePrefixMapper namespacePrefixMapper) { this.namespacePrefixMapper = namespacePrefixMapper; } @Override public PrismContext getPrismContext() { return prismContext; } public void setPrismContext(PrismContext prismContext) { this.prismContext = prismContext; } public XmlEntityResolver getEntityResolver() { return entityResolver; } public Map<String, SchemaDescription> getParsedSchemas() { return parsedSchemas; } public EntityResolver getBuiltinSchemaResolver() { return builtinSchemaResolver; } public File[] getCatalogFiles() { return catalogFiles; } public void setCatalogFiles(File[] catalogFiles) { this.catalogFiles = catalogFiles; } public String getCatalogResourceName() { return catalogResourceName; } public void setCatalogResourceName(String catalogResourceName) { this.catalogResourceName = catalogResourceName; } @Override public String getDefaultNamespace() { return defaultNamespace; } public void setDefaultNamespace(String defaultNamespace) { this.defaultNamespace = defaultNamespace; } //region Registering resources and initialization /** * Must be called before call to initialize() */ public void registerSchemaResource(String resourcePath, String usualPrefix) throws SchemaException { SchemaDescription desc = SchemaDescription.parseResource(resourcePath); desc.setUsualPrefix(usualPrefix); registerSchemaDescription(desc); } /** * Must be called before call to initialize() */ public void registerPrismSchemaResource(String resourcePath, String usualPrefix) throws SchemaException { SchemaDescription desc = SchemaDescription.parseResource(resourcePath); desc.setUsualPrefix(usualPrefix); desc.setPrismSchema(true); registerSchemaDescription(desc); } public void registerPrismSchemasFromWsdlResource(String resourcePath, List<Package> compileTimeClassesPackages) throws SchemaException { List<SchemaDescription> descriptions = SchemaDescription.parseWsdlResource(resourcePath); Iterator<Package> pkgIterator = null; if (compileTimeClassesPackages != null) { if (descriptions.size() != compileTimeClassesPackages.size()) { throw new SchemaException("Mismatch between the size of compileTimeClassesPackages ("+compileTimeClassesPackages.size() +" and schemas in "+resourcePath+" ("+descriptions.size()+")"); } pkgIterator = compileTimeClassesPackages.iterator(); } for (SchemaDescription desc : descriptions) { desc.setPrismSchema(true); if (pkgIterator != null) { desc.setCompileTimeClassesPackage(pkgIterator.next()); } registerSchemaDescription(desc); } } /** * Must be called before call to initialize() */ public void registerPrismSchemaResource(String resourcePath, String usualPrefix, Package compileTimeClassesPackage) throws SchemaException { registerPrismSchemaResource(resourcePath, usualPrefix, compileTimeClassesPackage, false, false); } /** * Must be called before call to initialize() */ public void registerPrismSchemaResource(String resourcePath, String usualPrefix, Package compileTimeClassesPackage, boolean prefixDeclaredByDefault) throws SchemaException { registerPrismSchemaResource(resourcePath, usualPrefix, compileTimeClassesPackage, false, prefixDeclaredByDefault); } /** * Must be called before call to initialize() */ public void registerPrismDefaultSchemaResource(String resourcePath, String usualPrefix, Package compileTimeClassesPackage) throws SchemaException { registerPrismSchemaResource(resourcePath, usualPrefix, compileTimeClassesPackage, true, true); } /** * Must be called before call to initialize() * * @param prefixDeclaredByDefault Whether this prefix will be declared in top element in all XML serializations (MID-2198) */ public void registerPrismSchemaResource(String resourcePath, String usualPrefix, Package compileTimeClassesPackage, boolean defaultSchema, boolean prefixDeclaredByDefault) throws SchemaException { SchemaDescription desc = SchemaDescription.parseResource(resourcePath); desc.setUsualPrefix(usualPrefix); desc.setPrismSchema(true); desc.setDefault(defaultSchema); desc.setDeclaredByDefault(prefixDeclaredByDefault); desc.setCompileTimeClassesPackage(compileTimeClassesPackage); registerSchemaDescription(desc); } /** * Must be called before call to initialize() * @param node */ public void registerSchema(Node node, String sourceDescription) throws SchemaException { SchemaDescription desc = SchemaDescription.parseNode(node, sourceDescription); registerSchemaDescription(desc); } /** * Must be called before call to initialize() * @param node */ public void registerSchema(Node node, String sourceDescription, String usualPrefix) throws SchemaException { SchemaDescription desc = SchemaDescription.parseNode(node, sourceDescription); desc.setUsualPrefix(usualPrefix); registerSchemaDescription(desc); } public void registerPrismSchemaFile(File file) throws FileNotFoundException, SchemaException { loadPrismSchemaFileDescription(file); } public SchemaDescription loadPrismSchemaFileDescription(File file) throws FileNotFoundException, SchemaException { if (!(file.getName().matches(".*\\.xsd$"))){ LOGGER.trace("Skipping registering {}, because it is not schema definition.", file.getAbsolutePath()); return null; } SchemaDescription desc = SchemaDescription.parseFile(file); desc.setPrismSchema(true); registerSchemaDescription(desc); return desc; } private void registerSchemaDescription(SchemaDescription desc) { if (desc.getUsualPrefix() != null) { namespacePrefixMapper.registerPrefix(desc.getNamespace(), desc.getUsualPrefix(), desc.isDefault()); if (desc.isDeclaredByDefault()) { namespacePrefixMapper.addDeclaredByDefault(desc.getUsualPrefix()); } } parsedSchemas.put(desc.getNamespace(), desc); schemaDescriptions.add(desc); } public void registerPrismSchemasFromDirectory(File directory) throws FileNotFoundException, SchemaException { File[] fileArray = directory.listFiles(); if (fileArray != null) { List<File> files = Arrays.asList(fileArray); // Sort the filenames so we have deterministic order of loading // This is useful in tests but may come handy also during customization Collections.sort(files); for (File file: files) { if (file.getName().startsWith(".")) { // skip dotfiles. this will skip SVN data and similar things continue; } if (file.isDirectory()) { registerPrismSchemasFromDirectory(file); } if (file.isFile()) { registerPrismSchemaFile(file); } } } } /** * This can be used to read additional schemas even after the registry was initialized. */ public void loadPrismSchemasFromDirectory(File directory) throws FileNotFoundException, SchemaException { List<File> files = Arrays.asList(directory.listFiles()); // Sort the filenames so we have deterministic order of loading // This is useful in tests but may come handy also during customization Collections.sort(files); for (File file: files) { if (file.getName().startsWith(".")) { // skip dotfiles. this will skip SVN data and similar things continue; } if (file.isDirectory()) { loadPrismSchemasFromDirectory(file); } if (file.isFile()) { loadPrismSchemaFile(file); } } } public void loadPrismSchemaFile(File file) throws FileNotFoundException, SchemaException { SchemaDescription desc = loadPrismSchemaFileDescription(file); parsePrismSchema(desc, false); } public void loadPrismSchemaResource(String resourcePath) throws SchemaException { SchemaDescription desc = SchemaDescription.parseResource(resourcePath); desc.setPrismSchema(true); registerSchemaDescription(desc); parsePrismSchema(desc, false); } /** * This can be used to read additional schemas even after the registry was initialized. */ @Override public void initialize() throws SAXException, IOException, SchemaException { if (prismContext == null) { throw new IllegalStateException("Prism context not set"); } if (namespacePrefixMapper == null) { throw new IllegalStateException("Namespace prefix mapper not set"); } try { LOGGER.trace("initialize() starting"); // TODO remove (all of these) initResolver(); LOGGER.trace("initResolver() done"); parsePrismSchemas(); LOGGER.trace("parsePrismSchemas() done"); parseJavaxSchema(); LOGGER.trace("parseJavaxSchema() done"); compileCompileTimeClassList(); LOGGER.trace("compileCompileTimeClassList() done"); initialized = true; } catch (SAXException ex) { if (ex instanceof SAXParseException) { SAXParseException sex = (SAXParseException)ex; throw new SchemaException("Error parsing schema "+sex.getSystemId()+" line "+sex.getLineNumber()+": "+sex.getMessage(), sex); } throw ex; } } private void parseJavaxSchema() throws SAXException, IOException { schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); Source[] sources = new Source[schemaDescriptions.size()]; int i = 0; for (SchemaDescription schemaDescription : schemaDescriptions) { Source source = schemaDescription.getSource(); sources[i] = source; i++; } schemaFactory.setResourceResolver(entityResolver); javaxSchema = schemaFactory.newSchema(sources); } private void parsePrismSchemas() throws SchemaException { for (SchemaDescription schemaDescription : schemaDescriptions) { if (schemaDescription.isPrismSchema()) { parsePrismSchema(schemaDescription, true); } } applySchemaExtensions(); for (SchemaDescription schemaDescription : schemaDescriptions) { if (schemaDescription.getSchema() != null) { resolveMissingTypeDefinitionsInGlobalItemDefinitions((PrismSchemaImpl) schemaDescription.getSchema()); } } } // global item definitions may refer to types that are not yet available private void resolveMissingTypeDefinitionsInGlobalItemDefinitions(PrismSchemaImpl schema) throws SchemaException { for (Iterator<DefinitionSupplier> iterator = schema.getDelayedItemDefinitions().iterator(); iterator.hasNext(); ) { DefinitionSupplier definitionSupplier = iterator.next(); Definition definition = definitionSupplier.get(); if (definition != null) { schema.add(definition); } iterator.remove(); } } private void parsePrismSchema(SchemaDescription schemaDescription, boolean allowDelayedItemDefinitions) throws SchemaException { String namespace = schemaDescription.getNamespace(); Element domElement = schemaDescription.getDomElement(); boolean isRuntime = schemaDescription.getCompileTimeClassesPackage() == null; long started = System.currentTimeMillis(); LOGGER.trace("Parsing schema {}, namespace: {}, isRuntime: {}", schemaDescription.getSourceDescription(), namespace, isRuntime); PrismSchema schema = PrismSchemaImpl.parse(domElement, entityResolver, isRuntime, schemaDescription.getSourceDescription(), allowDelayedItemDefinitions, getPrismContext()); if (StringUtils.isEmpty(namespace)) { namespace = schema.getNamespace(); } LOGGER.trace("Parsed schema {}, namespace: {}, isRuntime: {} in {} ms", schemaDescription.getSourceDescription(), namespace, isRuntime, System.currentTimeMillis()-started); schemaDescription.setSchema(schema); detectExtensionSchema(schema); } private void detectExtensionSchema(PrismSchema schema) throws SchemaException { for (ComplexTypeDefinition def: schema.getDefinitions(ComplexTypeDefinition.class)) { QName extType = def.getExtensionForType(); if (extType != null) { if (extensionSchemas.containsKey(extType)) { ComplexTypeDefinition existingExtension = extensionSchemas.get(extType); existingExtension.merge(def); // throw new SchemaException("Duplicate definition of extension for type "+extType+": "+def+" and "+extensionSchemas.get(extType)); } else { extensionSchemas.put(extType, def.clone()); } } } } private void applySchemaExtensions() throws SchemaException { for (Entry<QName,ComplexTypeDefinition> entry: extensionSchemas.entrySet()) { QName typeQName = entry.getKey(); ComplexTypeDefinition extensionCtd = entry.getValue(); ComplexTypeDefinition primaryCtd = findComplexTypeDefinition(typeQName); PrismContainerDefinition extensionContainer = primaryCtd.findContainerDefinition( new QName(primaryCtd.getTypeName().getNamespaceURI(), PrismConstants.EXTENSION_LOCAL_NAME)); if (extensionContainer == null) { throw new SchemaException("Attempt to extend type "+typeQName+" with "+extensionCtd.getTypeClass()+" but the original type does not have extension container"); } ((PrismContainerDefinitionImpl) extensionContainer).setComplexTypeDefinition(extensionCtd.clone()); ((PrismContainerDefinitionImpl) extensionContainer).setTypeName(extensionCtd.getTypeName()); } } private void compileCompileTimeClassList() { for (SchemaDescription schemaDescription : schemaDescriptions) { Package pkg = schemaDescription.getCompileTimeClassesPackage(); if (pkg != null) { Map<QName, Class<?>> map = createXsdTypeMap(pkg); schemaDescription.setXsdTypeTocompileTimeClassMap(map); } } } private void initResolver() throws IOException { CatalogManager catalogManager = new CatalogManager(); catalogManager.setUseStaticCatalog(true); catalogManager.setIgnoreMissingProperties(true); catalogManager.setVerbosity(1); catalogManager.setPreferPublic(true); CatalogResolver catalogResolver = new CatalogResolver(catalogManager); Catalog catalog = catalogResolver.getCatalog(); if (catalogFiles != null && catalogFiles.length > 0) { for (File catalogFile : catalogFiles) { LOGGER.trace("Using catalog file {}", catalogFile); catalog.parseCatalog(catalogFile.getPath()); } } else if (catalogResourceName != null) { LOGGER.trace("Using catalog from resource: {}", catalogResourceName); Enumeration<URL> catalogs = Thread.currentThread().getContextClassLoader().getResources(catalogResourceName); while (catalogs.hasMoreElements()) { URL catalogResourceUrl = catalogs.nextElement(); LOGGER.trace("Parsing catalog from URL: {}", catalogResourceUrl); catalog.parseCatalog(catalogResourceUrl); } } else { throw new IllegalStateException("Catalog is not defined"); } builtinSchemaResolver = catalogResolver; } //endregion //region Schemas and type maps (TODO) @Override public javax.xml.validation.Schema getJavaxSchema() { return javaxSchema; } @Override public Collection<Package> getCompileTimePackages() { Collection<Package> compileTimePackages = new ArrayList<Package>(schemaDescriptions.size()); for (SchemaDescription desc : schemaDescriptions) { if (desc.getCompileTimeClassesPackage() != null) { compileTimePackages.add(desc.getCompileTimeClassesPackage()); } } return compileTimePackages; } private Map<QName, Class<?>> createXsdTypeMap(Package pkg) { Map<QName, Class<?>> map = new HashMap<QName, Class<?>>(); for (Class clazz: ClassPathUtil.listClasses(pkg)) { QName typeName = JAXBUtil.getTypeQName(clazz); if (typeName != null) { map.put(typeName, clazz); } } return map; } private SchemaDescription lookupSchemaDescription(String namespace) { for (SchemaDescription desc : schemaDescriptions) { if (namespace.equals(desc.getNamespace())) { return desc; } } return null; } //endregion @Override public String debugDump() { return debugDump(0); } @Override public String debugDump(int indent) { StringBuilder sb = new StringBuilder(); DebugUtil.indentDebugDump(sb, indent); sb.append("SchemaRegistry:"); sb.append(" Parsed Schemas:"); for (String namespace: parsedSchemas.keySet()) { sb.append("\n"); DebugUtil.indentDebugDump(sb, indent + 1); sb.append(namespace); sb.append(": "); sb.append(parsedSchemas.get(namespace)); } return sb.toString(); } //region applyDefinition(..) methods @Override public <C extends Containerable> void applyDefinition(PrismContainer<C> container, Class<C> type) throws SchemaException { applyDefinition(container, type, true); } @Override public <C extends Containerable> void applyDefinition(PrismContainer<C> container, Class<C> compileTimeClass, boolean force) throws SchemaException { PrismContainerDefinition<C> definition = determineDefinitionFromClass(compileTimeClass); container.applyDefinition(definition, force); } @Override public <O extends Objectable> void applyDefinition(ObjectDelta<O> objectDelta, Class<O> compileTimeClass, boolean force) throws SchemaException { PrismObjectDefinition<O> objectDefinition = determineDefinitionFromClass(compileTimeClass); objectDelta.applyDefinition(objectDefinition, force); } @Override public <C extends Containerable, O extends Objectable> void applyDefinition(PrismContainerValue<C> prismContainerValue, Class<O> compileTimeClass, ItemPath path, boolean force) throws SchemaException { PrismObjectDefinition<O> objectDefinition = determineDefinitionFromClass(compileTimeClass); PrismContainerDefinition<C> containerDefinition = objectDefinition.findContainerDefinition(path); prismContainerValue.applyDefinition(containerDefinition, force); } @Override public <C extends Containerable> void applyDefinition(PrismContainerValue<C> prismContainerValue, QName typeName, ItemPath path, boolean force) throws SchemaException { PrismObjectDefinition objectDefinition = findObjectDefinitionByType(typeName); if (objectDefinition != null) { PrismContainerDefinition<C> containerDefinition = objectDefinition.findContainerDefinition(path); prismContainerValue.applyDefinition(containerDefinition, force); return; } PrismContainerDefinition typeDefinition = findContainerDefinitionByType(typeName); if (typeDefinition != null) { PrismContainerDefinition<C> containerDefinition = typeDefinition.findContainerDefinition(path); prismContainerValue.applyDefinition(containerDefinition, force); return; } ComplexTypeDefinition complexTypeDefinition = findComplexTypeDefinition(typeName); if (complexTypeDefinition != null) { PrismContainerDefinition<C> containerDefinition = complexTypeDefinition.findContainerDefinition(path); prismContainerValue.applyDefinition(containerDefinition, force); return; } throw new SchemaException("No definition for container "+path+" in type "+typeName); } //endregion private boolean namespaceMatches(String namespace, @Nullable List<String> ignoredNamespaces) { if (ignoredNamespaces == null) { return false; } for (String ignored : ignoredNamespaces) { if (namespace.startsWith(ignored)) { return true; } } return false; } //region Finding items (standard cases - core methods) // @Override // public <T extends Objectable> PrismObjectDefinition<T> findObjectDefinitionByType(@NotNull QName typeName) { // if (StringUtils.isEmpty(typeName.getNamespaceURI())) { // // a quick hack (todo do it seriously) // ComplexTypeDefinition ctd = resolveGlobalTypeDefinitionWithoutNamespace(typeName.getLocalPart()); // if (ctd == null) { // return null; // } // typeName = ctd.getTypeName(); // } // PrismSchema schema = findSchemaByNamespace(typeName.getNamespaceURI()); // if (schema == null){ // //TODO: check for confilicted objects // // Iterator<PrismSchema> schemaIterator = getSchemas().iterator(); // // while (schemaIterator.hasNext()){ // // schema = schemaIterator.next(); // // if (schema == null){ // // continue; // // } // // PrismObjectDefinition<T> def = schema.findObjectDefinitionByTypeAssumeNs(typeName); // // if (def != null){ // // return def; // // } // // // // } // return null; // } // return (PrismObjectDefinition) schema.findObjectDefinitionByType(typeName); // } // // @Override // public <T extends Objectable> PrismObjectDefinition<T> findObjectDefinitionByElementName(@NotNull QName elementName) { // if (StringUtils.isEmpty(elementName.getNamespaceURI())) { // return resolveGlobalItemDefinitionWithoutNamespace(elementName.getLocalPart(), PrismObjectDefinition.class); // } // PrismSchema schema = findSchemaByNamespace(elementName.getNamespaceURI()); // if (schema == null) { // return null; // } // return (PrismObjectDefinition) schema.findObjectDefinitionByElementName(elementName); // } // // @Override // public <C extends Containerable> PrismContainerDefinition<C> findContainerDefinitionByType(QName typeName) { // if (StringUtils.isEmpty(typeName.getNamespaceURI())) { // // Maybe not optimal but sufficient way: we resolve complex type definition, and from it we get qualified type name. // // This is then used to find container definition in the traditional way. // ComplexTypeDefinition complexTypeDefinition = resolveGlobalTypeDefinitionWithoutNamespace(typeName.getLocalPart()); // if (complexTypeDefinition == null) { // return null; // } // typeName = complexTypeDefinition.getTypeName(); // } // PrismSchema schema = findSchemaByNamespace(typeName.getNamespaceURI()); // if (schema == null) { // return null; // } // return schema.findContainerDefinitionByType(typeName); // } // // @Override // public <C extends Containerable> PrismContainerDefinition<C> findContainerDefinitionByElementName(QName elementName) { // if (StringUtils.isEmpty(elementName.getNamespaceURI())) { // return resolveGlobalItemDefinitionWithoutNamespace(elementName.getLocalPart(), PrismContainerDefinition.class); // } // PrismSchema schema = findSchemaByNamespace(elementName.getNamespaceURI()); // if (schema == null) { // return null; // } // return schema.findContainerDefinitionByElementName(elementName); // } // @NotNull @Override public <ID extends ItemDefinition> List<ID> findItemDefinitionsByCompileTimeClass( @NotNull Class<?> compileTimeClass, @NotNull Class<ID> definitionClass) { PrismSchema schema = findSchemaByCompileTimeClass(compileTimeClass); if (schema == null) { return Collections.emptyList(); } @SuppressWarnings("unchecked") List<ID> list = schema.findItemDefinitionsByCompileTimeClass(compileTimeClass, definitionClass); return list; } @Nullable @Override public <ID extends ItemDefinition> ID findItemDefinitionByType(@NotNull QName typeName, @NotNull Class<ID> definitionClass) { if (QNameUtil.noNamespace(typeName)) { TypeDefinition td = resolveGlobalTypeDefinitionWithoutNamespace(typeName.getLocalPart(), TypeDefinition.class); if (td == null) { return null; } typeName = td.getTypeName(); } PrismSchema schema = findSchemaByNamespace(typeName.getNamespaceURI()); if (schema == null) { return null; } return schema.findItemDefinitionByType(typeName, definitionClass); } @NotNull @Override public <ID extends ItemDefinition> List<ID> findItemDefinitionsByElementName(@NotNull QName elementName, @NotNull Class<ID> definitionClass) { if (QNameUtil.noNamespace(elementName)) { return resolveGlobalItemDefinitionsWithoutNamespace(elementName.getLocalPart(), definitionClass); } else { PrismSchema schema = findSchemaByNamespace(elementName.getNamespaceURI()); if (schema == null) { return new ArrayList<>(); } return schema.findItemDefinitionsByElementName(elementName, definitionClass); } } @Nullable @Override public <TD extends TypeDefinition> TD findTypeDefinitionByCompileTimeClass(@NotNull Class<?> compileTimeClass, @NotNull Class<TD> definitionClass) { PrismSchema schema = findSchemaByCompileTimeClass(compileTimeClass); if (schema == null) { return null; } return schema.findTypeDefinitionByCompileTimeClass(compileTimeClass, definitionClass); } @Nullable @Override public <TD extends TypeDefinition> TD findTypeDefinitionByType(@NotNull QName typeName, @NotNull Class<TD> definitionClass) { if (QNameUtil.noNamespace(typeName)) { return resolveGlobalTypeDefinitionWithoutNamespace(typeName.getLocalPart(), definitionClass); } PrismSchema schema = findSchemaByNamespace(typeName.getNamespaceURI()); if (schema == null) { return null; } return schema.findTypeDefinitionByType(typeName, definitionClass); } @NotNull @Override public <TD extends TypeDefinition> Collection<? extends TD> findTypeDefinitionsByType(@NotNull QName typeName, @NotNull Class<TD> definitionClass) { if (QNameUtil.noNamespace(typeName)) { return resolveGlobalTypeDefinitionsWithoutNamespace(typeName.getLocalPart(), definitionClass); } PrismSchema schema = findSchemaByNamespace(typeName.getNamespaceURI()); if (schema == null) { return Collections.emptyList(); } return schema.findTypeDefinitionsByType(typeName, definitionClass); } @NotNull @Override public <TD extends TypeDefinition> Collection<TD> findTypeDefinitionsByElementName(@NotNull QName name, @NotNull Class<TD> clazz) { return findItemDefinitionsByElementName(name, ItemDefinition.class).stream() .flatMap(itemDef -> findTypeDefinitionsByType(itemDef.getTypeName(), clazz).stream()) .collect(Collectors.toList()); } //endregion //region Finding items (nonstandard cases) @Override public <T extends ItemDefinition> T findItemDefinitionByFullPath(Class<? extends Objectable> objectClass, Class<T> defClass, QName... itemNames) throws SchemaException { PrismObjectDefinition objectDefinition = findObjectDefinitionByCompileTimeClass(objectClass); if (objectDefinition == null) { throw new SchemaException("No object definition for " + objectClass); } return (T) objectDefinition.findItemDefinition(new ItemPath(itemNames), defClass); } @Override public ItemDefinition findItemDefinitionByElementName(QName elementName, @Nullable List<String> ignoredNamespaces) { if (StringUtils.isEmpty(elementName.getNamespaceURI())) { return resolveGlobalItemDefinitionWithoutNamespace(elementName.getLocalPart(), ItemDefinition.class, true, ignoredNamespaces); } PrismSchema schema = findSchemaByNamespace(elementName.getNamespaceURI()); if (schema == null) { return null; } return schema.findItemDefinitionByElementName(elementName, ItemDefinition.class); } @Override public <T> Class<T> determineCompileTimeClass(QName typeName) { if (QNameUtil.noNamespace(typeName)) { TypeDefinition td = resolveGlobalTypeDefinitionWithoutNamespace(typeName.getLocalPart(), TypeDefinition.class); if (td == null) { return null; } return (Class<T>) td.getCompileTimeClass(); } SchemaDescription desc = findSchemaDescriptionByNamespace(typeName.getNamespaceURI()); if (desc == null) { return null; } Package pkg = desc.getCompileTimeClassesPackage(); if (pkg == null) { return null; } return JAXBUtil.findClassForType(typeName, pkg); } @NotNull public <T> Class<T> determineCompileTimeClassNotNull(QName typeName) { Class<T> clazz = determineCompileTimeClass(typeName); if (clazz != null) { return clazz; } else { throw new IllegalStateException("No class for " + typeName); } } @Override public <T> Class<T> getCompileTimeClass(QName xsdType) { return determineCompileTimeClass(xsdType); // TODO: which one is better (this one or the above)? // SchemaDescription desc = findSchemaDescriptionByNamespace(xsdType.getNamespaceURI()); // if (desc == null) { // return null; // } // Map<QName, Class<?>> map = desc.getXsdTypeTocompileTimeClassMap(); // if (map == null) { // return null; // } // return (Class<T>) map.get(xsdType); } @Override public Class<? extends ObjectType> getCompileTimeClassForObjectType(QName objectType) { PrismObjectDefinition definition = findObjectDefinitionByType(objectType); if (definition == null) { return null; } return definition.getCompileTimeClass(); } @Override public PrismObjectDefinition determineDefinitionFromClass(Class compileTimeClass) { PrismObjectDefinition def = findObjectDefinitionByCompileTimeClass(compileTimeClass); if (def != null) { return def; } Class<?> superclass = compileTimeClass.getSuperclass(); if (superclass == null || superclass == Object.class) { return null; } return determineDefinitionFromClass(superclass); } @Override public <T extends Containerable> ItemDefinition locateItemDefinition(@NotNull QName itemName, @Nullable ComplexTypeDefinition complexTypeDefinition, @Nullable Function<QName, ItemDefinition> dynamicDefinitionResolver) throws SchemaException { ItemDefinition def; if (complexTypeDefinition != null) { def = complexTypeDefinition.findItemDefinition(itemName); if (def != null) { return def; } } if (complexTypeDefinition == null || complexTypeDefinition.isXsdAnyMarker()) { def = resolveGlobalItemDefinition(itemName, complexTypeDefinition); if (def != null) { return def; } } if (dynamicDefinitionResolver != null) { def = dynamicDefinitionResolver.apply(itemName); if (def != null) { return def; } } return null; } //endregion //region Unqualified names resolution // TODO fix this temporary and inefficient implementation @Override public QName resolveUnqualifiedTypeName(QName type) throws SchemaException { QName typeFound = null; for (SchemaDescription desc: schemaDescriptions) { QName typeInSchema = new QName(desc.getNamespace(), type.getLocalPart()); if (desc.getSchema() != null && desc.getSchema().findComplexTypeDefinition(typeInSchema) != null) { if (typeFound != null) { throw new SchemaException("Ambiguous type name: " + type); } else { typeFound = typeInSchema; } } } if (typeFound == null) { throw new SchemaException("Unknown type: " + type); } else { return typeFound; } } @Override public QName qualifyTypeName(QName typeName) throws SchemaException { if (typeName == null || !QNameUtil.isUnqualified(typeName)) { return typeName; } return resolveUnqualifiedTypeName(typeName); } // private class ParentChildPair { // final ComplexTypeDefinition parentDef; // final ItemDefinition childDef; // public ParentChildPair(ComplexTypeDefinition parentDef, ItemDefinition childDef) { // this.parentDef = parentDef; // this.childDef = childDef; // } // } // current implementation tries to find all references to the child CTD and select those that are able to resolve path of 'rest' // fails on ambiguity // it's a bit fragile, as adding new references to child CTD in future may break existing code @Override public ComplexTypeDefinition determineParentDefinition(@NotNull ComplexTypeDefinition child, @NotNull ItemPath rest) { Map<ComplexTypeDefinition, ItemDefinition> found = new HashMap<>(); for (PrismSchema schema : getSchemas()) { if (schema == null) { continue; } for (ComplexTypeDefinition ctd : schema.getComplexTypeDefinitions()) { for (ItemDefinition item : ctd.getDefinitions()) { if (!(item instanceof PrismContainerDefinition)) { continue; } PrismContainerDefinition<?> itemPcd = (PrismContainerDefinition<?>) item; if (itemPcd.getComplexTypeDefinition() == null) { continue; } if (child.getTypeName().equals(itemPcd.getComplexTypeDefinition().getTypeName())) { if (!rest.isEmpty() && ctd.findItemDefinition(rest) == null) { continue; } found.put(ctd, itemPcd); } } } } if (found.isEmpty()) { throw new IllegalStateException("Couldn't find definition for parent for " + child.getTypeName() + ", path=" + rest); } else if (found.size() > 1) { Map<ComplexTypeDefinition, ItemDefinition> notInherited = found.entrySet().stream() .filter(e -> !e.getValue().isInherited()) .collect(Collectors.toMap(Entry::getKey, Entry::getValue)); if (notInherited.isEmpty()) { throw new IllegalStateException( "Couldn't find parent definition for " + child.getTypeName() + ": More than one candidate found: " + notInherited); } else if (notInherited.isEmpty()) { throw new IllegalStateException( "Couldn't find parent definition for " + child.getTypeName() + ": More than one candidate found - and all are inherited: " + found); } else { return notInherited.keySet().iterator().next(); } } else { return found.keySet().iterator().next(); } } @Override public PrismObjectDefinition determineReferencedObjectDefinition(QName targetTypeName, ItemPath rest) { // TEMPORARY HACK -- TODO FIXME PrismObjectDefinition def = findObjectDefinitionByType(targetTypeName); if (def == null) { throw new IllegalStateException("Couldn't find definition for referenced object for " + targetTypeName + ", path=" + rest); } return def; } private <TD extends TypeDefinition> TD resolveGlobalTypeDefinitionWithoutNamespace(String typeLocalName, Class<TD> definitionClass) { TD found = null; for (SchemaDescription schemaDescription : parsedSchemas.values()) { PrismSchema schema = schemaDescription.getSchema(); if (schema == null) { // is this possible? continue; } TD def = schema.findTypeDefinitionByType(new QName(schema.getNamespace(), typeLocalName), definitionClass); if (def != null) { if (found != null) { throw new IllegalArgumentException("Multiple possible resolutions for unqualified type name " + typeLocalName + " (e.g. in " + def.getTypeName() + " and " + found.getTypeName()); } found = def; } } return found; } @NotNull private <TD extends TypeDefinition> Collection<TD> resolveGlobalTypeDefinitionsWithoutNamespace(String typeLocalName, Class<TD> definitionClass) { List<TD> rv = new ArrayList<>(); for (SchemaDescription schemaDescription : parsedSchemas.values()) { PrismSchema schema = schemaDescription.getSchema(); if (schema != null) { rv.addAll(schema.findTypeDefinitionsByType(new QName(schema.getNamespace(), typeLocalName), definitionClass)); } } return rv; } /** * Looks for a top-level definition for the specified element name (in all schemas). */ @Override public ItemDefinition resolveGlobalItemDefinition(QName elementQName) throws SchemaException { return resolveGlobalItemDefinition(elementQName, (ComplexTypeDefinition) null); } @Override public ItemDefinition resolveGlobalItemDefinition(QName elementQName, PrismContainerDefinition<?> containerDefinition) throws SchemaException { return resolveGlobalItemDefinition(elementQName, containerDefinition != null ? containerDefinition.getComplexTypeDefinition() : null); } @Override public ItemDefinition resolveGlobalItemDefinition(QName itemName, @Nullable ComplexTypeDefinition complexTypeDefinition) throws SchemaException { if (QNameUtil.noNamespace(itemName)) { if (complexTypeDefinition != null && complexTypeDefinition.getDefaultNamespace() != null) { itemName = new QName(complexTypeDefinition.getDefaultNamespace(), itemName.getLocalPart()); } else { List<String> ignoredNamespaces = complexTypeDefinition != null ? complexTypeDefinition.getIgnoredNamespaces() : null; return resolveGlobalItemDefinitionWithoutNamespace(itemName.getLocalPart(), ItemDefinition.class, true, ignoredNamespaces); } } PrismSchema schema = findSchemaByNamespace(itemName.getNamespaceURI()); if (schema == null) { return null; } return schema.findItemDefinitionByElementName(itemName, ItemDefinition.class); } // private <T extends ItemDefinition> T resolveGlobalItemDefinitionWithoutNamespace(String localPart, Class<T> definitionClass) { // return resolveGlobalItemDefinitionWithoutNamespace(localPart, definitionClass, true, null); // } private <ID extends ItemDefinition> List<ID> resolveGlobalItemDefinitionsWithoutNamespace(String localPart, Class<ID> definitionClass) { return resolveGlobalItemDefinitionsWithoutNamespace(localPart, definitionClass, null); } private <ID extends ItemDefinition> ID resolveGlobalItemDefinitionWithoutNamespace(String localPart, Class<ID> definitionClass, boolean exceptionIfAmbiguous, @Nullable List<String> ignoredNamespaces) { return DefinitionStoreUtils.getOne( resolveGlobalItemDefinitionsWithoutNamespace(localPart, definitionClass, ignoredNamespaces), exceptionIfAmbiguous, "Multiple possible resolutions for unqualified element name '" + localPart + "'"); } @NotNull private <ID extends ItemDefinition> List<ID> resolveGlobalItemDefinitionsWithoutNamespace(String localPart, Class<ID> definitionClass, @Nullable List<String> ignoredNamespaces) { List<ID> found = new ArrayList<ID>(); for (SchemaDescription schemaDescription : parsedSchemas.values()) { PrismSchema schema = schemaDescription.getSchema(); if (schema == null) { // is this possible? continue; } if (namespaceMatches(schema.getNamespace(), ignoredNamespaces)) { continue; } ItemDefinition def = schema.findItemDefinitionByElementName(new QName(localPart), definitionClass); if (def != null) { found.add((ID) def); } } return found; } // private QName resolveElementNameIfNeeded(QName elementName) { // return resolveElementNameIfNeeded(elementName, true); // } // private QName resolveElementNameIfNeeded(QName elementName, boolean exceptionIfAmbiguous) { // if (StringUtils.isNotEmpty(elementName.getNamespaceURI())) { // return elementName; // } // ItemDefinition itemDef = resolveGlobalItemDefinitionWithoutNamespace(elementName.getLocalPart(), ItemDefinition.class, exceptionIfAmbiguous, null); // if (itemDef != null) { // return itemDef.getName(); // } else { // return null; // } // } //endregion //region Finding schemas @Override public PrismSchema getSchema(String namespace) { return parsedSchemas.get(namespace).getSchema(); } @Override public Collection<PrismSchema> getSchemas() { Collection<PrismSchema> schemas = new ArrayList<PrismSchema>(); for (Entry<String,SchemaDescription> entry: parsedSchemas.entrySet()) { schemas.add(entry.getValue().getSchema()); } return schemas; } @Override public Collection<SchemaDescription> getSchemaDescriptions() { return parsedSchemas.values(); } @Override public PrismSchema findSchemaByCompileTimeClass(@NotNull Class<?> compileTimeClass) { Package compileTimePackage = compileTimeClass.getPackage(); if (compileTimePackage == null) { return null; // e.g. for arrays } for (SchemaDescription desc: schemaDescriptions) { if (compileTimePackage.equals(desc.getCompileTimeClassesPackage())) { return desc.getSchema(); } } return null; } @Override public PrismSchema findSchemaByNamespace(String namespaceURI) { SchemaDescription desc = findSchemaDescriptionByNamespace(namespaceURI); if (desc == null) { return null; } return desc.getSchema(); } @Override public SchemaDescription findSchemaDescriptionByNamespace(String namespaceURI) { for (SchemaDescription desc: schemaDescriptions) { if (namespaceURI.equals(desc.getNamespace())) { return desc; } } return null; } @Override public PrismSchema findSchemaByPrefix(String prefix) { SchemaDescription desc = findSchemaDescriptionByPrefix(prefix); if (desc == null) { return null; } return desc.getSchema(); } @Override public SchemaDescription findSchemaDescriptionByPrefix(String prefix) { for (SchemaDescription desc: schemaDescriptions) { if (prefix.equals(desc.getUsualPrefix())) { return desc; } } return null; } //endregion //region Misc public static ItemDefinition createDefaultItemDefinition(QName itemName, PrismContext prismContext) { PrismPropertyDefinitionImpl propDef = new PrismPropertyDefinitionImpl(itemName, DEFAULT_XSD_TYPE, prismContext); // Set it to multi-value to be on the safe side propDef.setMaxOccurs(-1); propDef.setDynamic(true); return propDef; } //endregion //region Deprecated misc things @Override @Deprecated public <T extends Objectable> PrismObject<T> instantiate(Class<T> compileTimeClass) throws SchemaException { return prismContext.createObject(compileTimeClass); } //endregion //region TODO categorize /** * Returns true if specified element has a definition that matches specified type * in the known schemas. */ // @Override // public boolean hasImplicitTypeDefinitionOld(QName elementName, QName typeName) { // elementName = resolveElementNameIfNeeded(elementName, false); // if (elementName == null) { // return false; // } // PrismSchema schema = findSchemaByNamespace(elementName.getNamespaceURI()); // if (schema == null) { // return false; // } // ItemDefinition itemDefinition = schema.findItemDefinitionByElementName(elementName, ItemDefinition.class); // if (itemDefinition == null) { // return false; // } // return QNameUtil.match(typeName, itemDefinition.getTypeName()); // } /** * Answers the question: "If the receiver would get itemName without any other information, will it be able to * derive suitable typeName from it?" If not, we have to provide explicit type definition for serialization. * * By suitable we mean such that can be used to determine specific object type. */ public boolean hasImplicitTypeDefinition(@NotNull QName itemName, @NotNull QName typeName) { List<ItemDefinition> definitions = findItemDefinitionsByElementName(itemName, ItemDefinition.class); if (definitions.isEmpty() || definitions.size() > 1) { return false; } ItemDefinition definition = definitions.get(0); if (definition.isAbstract()) { return false; } // TODO other conditions? return definition.getTypeName().equals(typeName); } @Override public QName determineTypeForClass(Class<?> clazz) { if (XmlTypeConverter.canConvert(clazz)) { return XsdTypeMapper.toXsdType(clazz); } else { return ((PrismContextImpl) prismContext).getBeanMarshaller().determineTypeForClass(clazz); } } @Override public <T> Class<T> determineClassForType(QName type) { if (XmlTypeConverter.canConvert(type)) { return XsdTypeMapper.toJavaType(type); } else { return determineCompileTimeClass(type); } } @NotNull public <T> Class<T> determineClassForTypeNotNull(QName typeName) { Class<T> clazz = determineClassForType(typeName); if (clazz != null) { return clazz; } else { throw new IllegalStateException("No class for " + typeName); } } @Override public Class<?> determineClassForItemDefinition(ItemDefinition<?> itemDefinition) { if (itemDefinition instanceof PrismContainerDefinition) { Class<?> cls = ((PrismContainerDefinition) itemDefinition).getCompileTimeClass(); if (cls != null) { return cls; } } return determineClassForType(itemDefinition.getTypeName()); } @Override public <ID extends ItemDefinition> ID selectMoreSpecific(ID def1, ID def2) throws SchemaException { if (def1 == null) { return def2; } if (def2 == null) { return def1; } if (QNameUtil.match(def1.getTypeName(), def2.getTypeName())) { return def1; } Class<?> cls1 = determineClassForItemDefinition(def1); Class<?> cls2 = determineClassForItemDefinition(def2); if (cls1 == null || cls2 == null) { throw new SchemaException("Couldn't find more specific type from " + def1.getTypeName() + " (" + cls1 + ") and " + def2.getTypeName() + " (" + cls2 + ")"); } if (cls1.isAssignableFrom(cls2)) { return def2; } if (cls2.isAssignableFrom(cls1)) { return def1; } throw new SchemaException("Couldn't find more specific type from " + def1.getTypeName() + " (" + cls1 + ") and " + def2.getTypeName() + " (" + cls2 + ")"); } @Override public QName selectMoreSpecific(QName type1, QName type2) throws SchemaException { if (type1 == null || QNameUtil.match(type1, DOMUtil.XSD_ANYTYPE)) { return type2; } if (type2 == null || QNameUtil.match(type2, DOMUtil.XSD_ANYTYPE)) { return type1; } if (QNameUtil.match(type1, type2)) { return type1; } Class<?> cls1 = determineClassForType(type1); Class<?> cls2 = determineClassForType(type2); if (cls1 == null || cls2 == null) { throw new SchemaException("Couldn't find more specific type from " + type1 + " (" + cls1 + ") and " + type2 + " (" + cls2 + ")"); } if (cls1.isAssignableFrom(cls2)) { return type2; } if (cls2.isAssignableFrom(cls1)) { return type1; } // poly string vs string if (PolyStringType.class.equals(cls1) || String.class.equals(cls2)) { return type1; } if (PolyStringType.class.equals(cls2) || String.class.equals(cls1)) { return type2; } throw new SchemaException("Couldn't find more specific type from " + type1 + " (" + cls1 + ") and " + type2 + " (" + cls2 + ")"); } // TODO implement more efficiently @Override public boolean areComparable(QName type1, QName type2) throws SchemaException { try { selectMoreSpecific(type1, type2); return true; } catch (SchemaException e) { return false; } } @Override public <ID extends ItemDefinition> ComparisonResult compareDefinitions(@NotNull ID def1, @NotNull ID def2) throws SchemaException { if (QNameUtil.match(def1.getTypeName(), def2.getTypeName())) { return ComparisonResult.EQUAL; } Class<?> cls1 = determineClassForItemDefinition(def1); Class<?> cls2 = determineClassForItemDefinition(def2); if (cls1 == null || cls2 == null) { return ComparisonResult.NO_STATIC_CLASS; } boolean cls1AboveOrEqualCls2 = cls1.isAssignableFrom(cls2); boolean cls2AboveOrEqualCls1 = cls2.isAssignableFrom(cls1); if (cls1AboveOrEqualCls2 && cls2AboveOrEqualCls1) { return ComparisonResult.EQUAL; } else if (cls1AboveOrEqualCls2) { return ComparisonResult.SECOND_IS_CHILD; } else if (cls2AboveOrEqualCls1) { return ComparisonResult.FIRST_IS_CHILD; } else { return ComparisonResult.INCOMPATIBLE; } } @Override public boolean isAssignableFrom(@NotNull QName superType, @NotNull QName subType) { if (QNameUtil.match(superType, subType) || QNameUtil.match(DOMUtil.XSD_ANYTYPE, superType)) { return true; } if (QNameUtil.match(DOMUtil.XSD_ANYTYPE, subType)) { return false; } Class<?> superClass = determineClassForType(superType); Class<?> subClass = determineClassForType(subType); // TODO consider implementing "strict mode" that would throw an exception in the case of nullness return superClass != null && subClass != null && superClass.isAssignableFrom(subClass); } @Override public boolean isContainer(QName typeName) { Class<?> clazz = determineClassForType(typeName); return clazz != null && Containerable.class.isAssignableFrom(clazz); } //endregion }