com.evolveum.midpoint.prism.schema.SchemaRegistry.java Source code

Java tutorial

Introduction

Here is the source code for com.evolveum.midpoint.prism.schema.SchemaRegistry.java

Source

/*
 * 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.BufferedInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
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 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.util.QNameUtil;
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.springframework.beans.factory.annotation.Autowired;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.ls.LSInput;
import org.w3c.dom.ls.LSResourceResolver;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
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 SchemaRegistry implements LSResourceResolver, EntityResolver, DebugDumpable {

    private static final QName DEFAULT_XSD_TYPE = DOMUtil.XSD_STRING;

    private static final String RUNTIME_CATALOG_RESOURCE = "META-INF/catalog-runtime.xml";

    private String catalogResource;

    private javax.xml.validation.SchemaFactory schemaFactory;
    private javax.xml.validation.Schema javaxSchema;
    private EntityResolver builtinSchemaResolver;
    private List<SchemaDescription> schemaDescriptions;
    private Map<String, SchemaDescription> parsedSchemas;
    private Map<QName, ComplexTypeDefinition> extensionSchemas;
    private boolean initialized = false;
    private DynamicNamespacePrefixMapper namespacePrefixMapper;
    private String defaultNamespace;
    @Autowired(required = true)
    private PrismContext prismContext;

    private static final Trace LOGGER = TraceManager.getTrace(SchemaRegistry.class);

    public SchemaRegistry() {
        super();
        this.catalogResource = RUNTIME_CATALOG_RESOURCE;
        this.schemaDescriptions = new ArrayList<SchemaDescription>();
        this.parsedSchemas = new HashMap<String, SchemaDescription>();
        this.extensionSchemas = new HashMap<QName, ComplexTypeDefinition>();
    }

    public DynamicNamespacePrefixMapper getNamespacePrefixMapper() {
        return namespacePrefixMapper;
    }

    public void setNamespacePrefixMapper(DynamicNamespacePrefixMapper namespacePrefixMapper) {
        this.namespacePrefixMapper = namespacePrefixMapper;
    }

    public PrismContext getPrismContext() {
        return prismContext;
    }

    public void setPrismContext(PrismContext prismContext) {
        this.prismContext = prismContext;
    }

    public EntityResolver getBuiltinSchemaResolver() {
        return builtinSchemaResolver;
    }

    public void setBuiltinSchemaResolver(EntityResolver builtinSchemaResolver) {
        this.builtinSchemaResolver = builtinSchemaResolver;
    }

    public String getDefaultNamespace() {
        return defaultNamespace;
    }

    public void setDefaultNamespace(String defaultNamespace) {
        this.defaultNamespace = defaultNamespace;
    }

    /**
     * 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);
    }

    public void loadPrismSchemaResource(String resourcePath) throws SchemaException {
        SchemaDescription desc = SchemaDescription.parseResource(resourcePath);
        desc.setPrismSchema(true);
        registerSchemaDescription(desc);
        parsePrismSchema(desc);
    }

    /**
    * This can be used to read additional schemas even after the registry was initialized.
    */
    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 {
            initResolver();
            parsePrismSchemas();
            parseJavaxSchema();
            compileCompileTimeClassList();
            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());
            }
            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(this);
        javaxSchema = schemaFactory.newSchema(sources);
    }

    private void parsePrismSchemas() throws SchemaException {
        for (SchemaDescription schemaDescription : schemaDescriptions) {
            if (schemaDescription.isPrismSchema()) {
                parsePrismSchema(schemaDescription);
            }
        }
        applySchemaExtensions();
    }

    private void parsePrismSchema(SchemaDescription schemaDescription) throws SchemaException {
        String namespace = schemaDescription.getNamespace();

        Element domElement = schemaDescription.getDomElement();
        boolean isRuntime = schemaDescription.getCompileTimeClassesPackage() == null;
        PrismSchema schema = PrismSchema.parse(domElement, this, isRuntime,
                schemaDescription.getSourceDescription(), getPrismContext());
        if (StringUtils.isEmpty(namespace)) {
            namespace = schema.getNamespace();
        }
        LOGGER.trace("Parsed schema {}, namespace: {}, isRuntime: {}", schemaDescription.getSourceDescription(),
                namespace, isRuntime);
        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");
            }
            extensionContainer.setComplexTypeDefinition(extensionCtd.clone());
            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 (catalogResource == null) {
            throw new IllegalStateException("Catalog is not defined");
        }

        Enumeration<URL> catalogs = Thread.currentThread().getContextClassLoader().getResources(catalogResource);
        while (catalogs.hasMoreElements()) {
            URL catalogURL = catalogs.nextElement();
            catalog.parseCatalog(catalogURL);
        }

        builtinSchemaResolver = catalogResolver;
    }

    public javax.xml.validation.Schema getJavaxSchema() {
        return javaxSchema;
    }

    public PrismSchema getSchema(String namespace) {
        return parsedSchemas.get(namespace).getSchema();
    }

    public Collection<PrismSchema> getSchemas() {
        Collection<PrismSchema> schemas = new ArrayList<PrismSchema>();
        for (Entry<String, SchemaDescription> entry : parsedSchemas.entrySet()) {
            schemas.add(entry.getValue().getSchema());
        }
        return schemas;
    }

    public Collection<SchemaDescription> getSchemaDescriptions() {
        return parsedSchemas.values();
    }

    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;
    }

    /* (non-Javadoc)
     * @see org.xml.sax.EntityResolver#resolveEntity(java.lang.String, java.lang.String)
     */
    @Override
    public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
        // Prefer built-in resolution over the pre-parsed one. This is less efficient, but if the order is swapped
        // the parsers complaints about re-definition of the elements
        //      InputSource inputSource = resolveResourceUsingBuiltinResolver(null, null, publicId, systemId, null);
        InputSource inputSource = resolveResourceFromRegisteredSchemas(null, publicId, publicId, systemId, null);
        if (inputSource == null) {
            inputSource = resolveResourceUsingBuiltinResolver(null, null, publicId, systemId, null);
            //         inputSource = resolveResourceFromRegisteredSchemas(null, publicId, publicId, systemId, null);
        }
        if (inputSource == null) {
            LOGGER.error("Unable to resolve resource with publicID: {}, systemID: {}",
                    new Object[] { publicId, systemId });
            return null;
        }
        LOGGER.trace("Resolved resource with publicID: {}, systemID: {} : {}",
                new Object[] { publicId, systemId, inputSource });
        return inputSource;
    }

    /* (non-Javadoc)
     * @see org.w3c.dom.ls.LSResourceResolver#resolveResource(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String)
     */
    @Override
    public LSInput resolveResource(String type, String namespaceURI, String publicId, String systemId,
            String baseURI) {
        // Prefer built-in resolution over the pre-parsed one. This is less efficient, but if the order is swapped
        // the parsers complaints about re-definition of the elements
        //      InputSource inputSource = resolveResourceUsingBuiltinResolver(type,namespaceURI,publicId,systemId,baseURI);
        InputSource inputSource = resolveResourceFromRegisteredSchemas(type, namespaceURI, publicId, systemId,
                baseURI);
        if (inputSource == null) {
            inputSource = resolveResourceUsingBuiltinResolver(type, namespaceURI, publicId, systemId, baseURI);
            //         inputSource = resolveResourceFromRegisteredSchemas(type,namespaceURI,publicId,systemId,baseURI);
        }
        if (inputSource == null) {
            LOGGER.error(
                    "Unable to resolve resource of type {}, namespaceURI: {}, publicID: {}, systemID: {}, baseURI: {}",
                    new Object[] { type, namespaceURI, publicId, systemId, baseURI });
            return null;
        }
        LOGGER.trace("Resolved resource of type {}, namespaceURI: {}, publicID: {}, systemID: {}, baseURI: {} : {}",
                new Object[] { type, namespaceURI, publicId, systemId, baseURI, inputSource });
        return new Input(publicId, systemId, inputSource.getByteStream());
    }

    private InputSource resolveResourceFromRegisteredSchemas(String type, String namespaceURI, String publicId,
            String systemId, String baseURI) {
        InputSource source = resolveResourceFromRegisteredSchemasByNamespace(namespaceURI);
        if (source == null) {
            source = resolveResourceFromRegisteredSchemasByNamespace(publicId);
        }
        if (source == null) {
            source = resolveResourceFromRegisteredSchemasByNamespace(systemId);
        }
        return source;
    }

    private InputSource resolveResourceFromRegisteredSchemasByNamespace(String namespaceURI) {
        if (namespaceURI != null) {
            if (parsedSchemas.containsKey(namespaceURI)) {
                SchemaDescription schemaDescription = parsedSchemas.get(namespaceURI);
                if (schemaDescription.canInputStream()) {
                    InputStream inputStream = schemaDescription.openInputStream();
                    InputSource source = new InputSource();
                    source.setByteStream(inputStream);
                    //source.setSystemId(schemaDescription.getPath());
                    // Make sure that both publicId and systemId are always set to schema namespace
                    // this helps to avoid double processing of the schemas
                    source.setSystemId(namespaceURI);
                    source.setPublicId(namespaceURI);
                    return source;
                } else {
                    throw new IllegalStateException("Requested resolution of schema "
                            + schemaDescription.getSourceDescription() + " that does not support input stream");
                }
            }
        }
        return null;
    }

    public InputSource resolveResourceUsingBuiltinResolver(String type, String namespaceURI, String publicId,
            String systemId, String baseURI) {
        InputSource inputSource = null;
        try {
            if (namespaceURI != null) {
                // The systemId will be populated by schema location, not namespace URI.
                // As we use catalog resolver as the default one, we need to pass it the namespaceURI in place of systemId
                inputSource = builtinSchemaResolver.resolveEntity(publicId, namespaceURI);
            }
            // however, there are cases where systemId differs from namespaceURI (e.g. when namespace ends with '#')
            // so, give systemId a try...
            if (inputSource == null) {
                inputSource = builtinSchemaResolver.resolveEntity(publicId, systemId);
            }
        } catch (SAXException e) {
            LOGGER.error(
                    "XML parser error resolving reference of type {}, namespaceURI: {}, publicID: {}, systemID: {}, baseURI: {}: {}",
                    new Object[] { type, namespaceURI, publicId, systemId, baseURI, e.getMessage(), e });
            // TODO: better error handling
            return null;
        } catch (IOException e) {
            LOGGER.error(
                    "IO error resolving reference of type {}, namespaceURI: {}, publicID: {}, systemID: {}, baseURI: {}: {}",
                    new Object[] { type, namespaceURI, publicId, systemId, baseURI, e.getMessage(), e });
            // TODO: better error handling
            return null;
        }
        return inputSource;
    }

    // TODO fix this temporary and inefficient implementation
    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;
        }
    }

    public QName qualifyTypeName(QName typeName) throws SchemaException {
        if (typeName == null || !QNameUtil.isUnqualified(typeName)) {
            return typeName;
        }
        return resolveUnqualifiedTypeName(typeName);
    }

    public ComplexTypeDefinition determineParentDefinition(ComplexTypeDefinition complexTypeDefinition,
            ItemPath rest) {
        ComplexTypeDefinition def = findComplexTypeDefinition(
                new QName("http://midpoint.evolveum.com/xml/ns/public/common/common-3", "ObjectType")); // FIXME BRUTAL HACK
        if (def == null) {
            throw new IllegalStateException("Couldn't find definition for parent for "
                    + complexTypeDefinition.getTypeName() + ", path=" + rest);
        }
        return def;
    }

    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;
    }

    class Input implements LSInput {

        private String publicId;
        private String systemId;
        private BufferedInputStream inputStream;

        public String getPublicId() {
            return publicId;
        }

        public void setPublicId(String publicId) {
            this.publicId = publicId;
        }

        public String getBaseURI() {
            return null;
        }

        public InputStream getByteStream() {
            return null;
        }

        public boolean getCertifiedText() {
            return false;
        }

        public Reader getCharacterStream() {
            return null;
        }

        public String getEncoding() {
            return null;
        }

        public String getStringData() {
            synchronized (inputStream) {
                try {
                    byte[] input = new byte[inputStream.available()];
                    inputStream.read(input);
                    String contents = new String(input);
                    return contents;
                } catch (IOException e) {
                    LOGGER.error("IO error creating LSInput for publicID: {}, systemID: {}: {}",
                            new Object[] { publicId, systemId, e.getMessage(), e });
                    // TODO: better error handling
                    return null;
                }
            }
        }

        public void setBaseURI(String baseURI) {
        }

        public void setByteStream(InputStream byteStream) {
        }

        public void setCertifiedText(boolean certifiedText) {
        }

        public void setCharacterStream(Reader characterStream) {
        }

        public void setEncoding(String encoding) {
        }

        public void setStringData(String stringData) {
        }

        public String getSystemId() {
            return systemId;
        }

        public void setSystemId(String systemId) {
            this.systemId = systemId;
        }

        public BufferedInputStream getInputStream() {
            return inputStream;
        }

        public void setInputStream(BufferedInputStream inputStream) {
            this.inputStream = inputStream;
        }

        public Input(String publicId, String sysId, InputStream input) {
            this.publicId = publicId;
            this.systemId = sysId;
            this.inputStream = new BufferedInputStream(input);
        }
    }

    @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();
    }

    public Class<?> determineCompileTimeClass(QName elementName, ComplexTypeDefinition complexTypeDefinition) {
        return determineCompileTimeClass(complexTypeDefinition.getTypeName());
    }

    public Class<?> determineCompileTimeClass(ComplexTypeDefinition complexTypeDefinition) {
        return determineCompileTimeClass(complexTypeDefinition.getTypeName());
    }

    public <T> Class<T> determineCompileTimeClass(QName typeName) {
        if (StringUtils.isEmpty(typeName.getNamespaceURI())) {
            ComplexTypeDefinition ctd = resolveGlobalTypeDefinitionWithoutNamespace(typeName.getLocalPart());
            return (Class) ctd.getCompileTimeClass();
        }
        SchemaDescription desc = findSchemaDescriptionByNamespace(typeName.getNamespaceURI());
        if (desc == null) {
            return null;
        }
        Package pkg = desc.getCompileTimeClassesPackage();
        if (pkg == null) {
            return null;
        }
        Class<T> compileTimeClass = JAXBUtil.findClassForType(typeName, pkg);
        return compileTimeClass;
    }

    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);
    }

    public PrismSchema findSchemaByCompileTimeClass(Class<?> compileTimeClass) {
        Package compileTimePackage = compileTimeClass.getPackage();
        for (SchemaDescription desc : schemaDescriptions) {
            if (compileTimePackage.equals(desc.getCompileTimeClassesPackage())) {
                PrismSchema schema = desc.getSchema();
                return schema;
            }
        }
        return null;
    }

    public <O extends Objectable> PrismObjectDefinition<O> findObjectDefinitionByCompileTimeClass(
            Class<O> compileTimeClass) {
        PrismSchema schema = findSchemaByCompileTimeClass(compileTimeClass);
        if (schema == null) {
            return null;
        }
        return schema.findObjectDefinitionByCompileTimeClass(compileTimeClass);
    }

    public <O extends Objectable> void applyDefinition(PrismObject<O> prismObject, Class<O> type)
            throws SchemaException {
        applyDefinition(prismObject, type, true);
    }

    /**
     * This method will try to locate the appropriate object definition and apply it.
     */
    public <O extends Objectable> void applyDefinition(PrismObject<O> prismObject, Class<O> type, boolean force)
            throws SchemaException {
        PrismObjectDefinition<O> objectDefinition = determineDefinitionFromClass(type);
        prismObject.applyDefinition(objectDefinition, force);
    }

    /**
     * This method will try to locate the appropriate object definition and apply it.
     */
    public <T extends Objectable> void applyDefinition(ObjectDelta<T> objectDelta, Class<T> type, boolean force)
            throws SchemaException {
        PrismObjectDefinition<T> objectDefinition = determineDefinitionFromClass(type);
        objectDelta.applyDefinition(objectDefinition, force);
    }

    public <C extends Containerable, O extends Objectable> void applyDefinition(
            PrismContainerValue<C> prismContainerValue, Class<O> type, ItemPath path, boolean force)
            throws SchemaException {
        PrismObjectDefinition<O> objectDefinition = determineDefinitionFromClass(type);
        PrismContainerDefinition<C> containerDefinition = objectDefinition.findContainerDefinition(path);
        prismContainerValue.applyDefinition(containerDefinition, force);
    }

    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);
    }

    public <T extends Objectable> PrismObjectDefinition<T> findObjectDefinitionByType(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 schema.findObjectDefinitionByType(typeName);
    }

    public <T extends Objectable> PrismObjectDefinition<T> findObjectDefinitionByElementName(QName elementName) {
        if (StringUtils.isEmpty(elementName.getNamespaceURI())) {
            return resolveGlobalItemDefinitionWithoutNamespace(elementName.getLocalPart(),
                    PrismObjectDefinition.class);
        }
        PrismSchema schema = findSchemaByNamespace(elementName.getNamespaceURI());
        if (schema == null) {
            return null;
        }
        return schema.findObjectDefinitionByElementName(elementName);
    }

    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);
    }

    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);
    }

    public <C extends Containerable> PrismContainerDefinition<C> findContainerDefinitionByCompileTimeClass(
            Class<C> compileTimeClass) {
        PrismSchema schema = findSchemaByCompileTimeClass(compileTimeClass);
        if (schema == null) {
            return null;
        }
        return schema.findContainerDefinitionByCompileTimeClass(compileTimeClass);
    }

    public <C extends Containerable> ComplexTypeDefinition findComplexTypeDefinitionByCompileTimeClass(
            Class<C> compileTimeClass) {
        PrismSchema schema = findSchemaByCompileTimeClass(compileTimeClass);
        if (schema == null) {
            return null;
        }
        return schema.findComplexTypeDefinitionByCompileTimeClass(compileTimeClass);
    }

    public ItemDefinition findItemDefinitionByElementName(QName elementName) {
        if (StringUtils.isEmpty(elementName.getNamespaceURI())) {
            return resolveGlobalItemDefinitionWithoutNamespace(elementName.getLocalPart(), ItemDefinition.class);
        }
        PrismSchema schema = findSchemaByNamespace(elementName.getNamespaceURI());
        if (schema == null) {
            return null;
        }
        return schema.findItemDefinition(elementName, ItemDefinition.class);
    }

    public ItemDefinition findItemDefinitionByType(QName typeName) {
        if (StringUtils.isEmpty(typeName.getNamespaceURI())) {
            ComplexTypeDefinition ctd = resolveGlobalTypeDefinitionWithoutNamespace(typeName.getLocalPart());
            typeName = ctd.getTypeName();
        }
        PrismSchema schema = findSchemaByNamespace(typeName.getNamespaceURI());
        if (schema == null) {
            return null;
        }
        return schema.findItemDefinitionByType(typeName, ItemDefinition.class);
    }

    public PrismPropertyDefinition findPropertyDefinitionByElementName(QName elementName) {
        if (StringUtils.isEmpty(elementName.getNamespaceURI())) {
            return resolveGlobalItemDefinitionWithoutNamespace(elementName.getLocalPart(),
                    PrismPropertyDefinition.class);
        }
        PrismSchema schema = findSchemaByNamespace(elementName.getNamespaceURI());
        if (schema == null) {
            return null;
        }
        return schema.findPropertyDefinitionByElementName(elementName);
    }

    public PrismReferenceDefinition findReferenceDefinitionByElementName(QName elementName) {
        if (StringUtils.isEmpty(elementName.getNamespaceURI())) {
            return resolveGlobalItemDefinitionWithoutNamespace(elementName.getLocalPart(),
                    PrismReferenceDefinition.class);
        }
        PrismSchema schema = findSchemaByNamespace(elementName.getNamespaceURI());
        if (schema == null) {
            return null;
        }
        return schema.findReferenceDefinitionByElementName(elementName);
    }

    public ComplexTypeDefinition findComplexTypeDefinition(QName typeName) {
        if (StringUtils.isEmpty(typeName.getNamespaceURI())) {
            return resolveGlobalTypeDefinitionWithoutNamespace(typeName.getLocalPart());
        }
        PrismSchema schema = findSchemaByNamespace(typeName.getNamespaceURI());
        if (schema == null) {
            return null;
        }
        return schema.findComplexTypeDefinition(typeName);
    }

    public PrismSchema findSchemaByNamespace(String namespaceURI) {
        SchemaDescription desc = findSchemaDescriptionByNamespace(namespaceURI);
        if (desc == null) {
            return null;
        }
        return desc.getSchema();
    }

    public SchemaDescription findSchemaDescriptionByNamespace(String namespaceURI) {
        for (SchemaDescription desc : schemaDescriptions) {
            if (namespaceURI.equals(desc.getNamespace())) {
                return desc;
            }
        }
        return null;
    }

    public PrismSchema findSchemaByPrefix(String prefix) {
        SchemaDescription desc = findSchemaDescriptionByPrefix(prefix);
        if (desc == null) {
            return null;
        }
        return desc.getSchema();
    }

    public SchemaDescription findSchemaDescriptionByPrefix(String prefix) {
        for (SchemaDescription desc : schemaDescriptions) {
            if (prefix.equals(desc.getUsualPrefix())) {
                return desc;
            }
        }
        return null;
    }

    public PrismObjectDefinition determineDefinitionFromClass(Class type) {
        PrismObjectDefinition def = findObjectDefinitionByCompileTimeClass(type);
        if (def != null) {
            return def;
        }
        Class<?> superclass = type.getSuperclass();
        if (superclass == Object.class) {
            return null;
        }
        return determineDefinitionFromClass(superclass);
    }

    /**
     * Returns true if specified element has a definition that matches specified type
     * in the known schemas.
     */
    public boolean hasImplicitTypeDefinition(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.findItemDefinition(elementName, ItemDefinition.class);
        if (itemDefinition == null) {
            return false;
        }
        return QNameUtil.match(typeName, itemDefinition.getTypeName());
    }

    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);
        if (itemDef != null) {
            return itemDef.getName();
        } else {
            return null;
        }
    }

    public static ItemDefinition createDefaultItemDefinition(QName itemName, PrismContext prismContext) {
        PrismPropertyDefinition propDef = new PrismPropertyDefinition(itemName, DEFAULT_XSD_TYPE, prismContext);
        // Set it to multi-value to be on the safe side
        propDef.setMaxOccurs(-1);
        propDef.setDynamic(true);
        return propDef;
    }

    /**
     * Looks for a top-level definition for the specified element name (in all schemas). 
     */
    public ItemDefinition resolveGlobalItemDefinition(QName elementQName) throws SchemaException {
        String elementNamespace = elementQName.getNamespaceURI();
        if (StringUtils.isEmpty(elementNamespace)) {
            return resolveGlobalItemDefinitionWithoutNamespace(elementQName.getLocalPart(), ItemDefinition.class);
        }
        PrismSchema schema = findSchemaByNamespace(elementNamespace);
        if (schema == null) {
            return null;
        }
        ItemDefinition itemDefinition = schema.findItemDefinition(elementQName, ItemDefinition.class);
        return itemDefinition;
    }

    private <T extends ItemDefinition> T resolveGlobalItemDefinitionWithoutNamespace(String localPart,
            Class<T> definitionClass) {
        return resolveGlobalItemDefinitionWithoutNamespace(localPart, definitionClass, true);
    }

    private <T extends ItemDefinition> T resolveGlobalItemDefinitionWithoutNamespace(String localPart,
            Class<T> definitionClass, boolean exceptionIfAmbiguous) {
        ItemDefinition found = null;
        for (SchemaDescription schemaDescription : parsedSchemas.values()) {
            PrismSchema schema = schemaDescription.getSchema();
            if (schema == null) { // is this possible?
                continue;
            }
            ItemDefinition def = schema.findItemDefinition(localPart, definitionClass);
            if (def != null) {
                if (found != null) {
                    // todo change to SchemaException
                    if (exceptionIfAmbiguous) {
                        throw new IllegalArgumentException(
                                "Multiple possible resolutions for unqualified element name " + localPart
                                        + " (e.g. in " + def.getNamespace() + " and " + found.getNamespace());
                    } else {
                        return null;
                    }
                }
                found = def;
            }
        }
        return (T) found;
    }

    private ComplexTypeDefinition resolveGlobalTypeDefinitionWithoutNamespace(String typeLocalName) {
        ComplexTypeDefinition found = null;
        for (SchemaDescription schemaDescription : parsedSchemas.values()) {
            PrismSchema schema = schemaDescription.getSchema();
            if (schema == null) { // is this possible?
                continue;
            }
            ComplexTypeDefinition def = schema
                    .findComplexTypeDefinition(new QName(schema.getNamespace(), typeLocalName));
            if (def != null) {
                if (found != null) {
                    // TODO change to SchemaException
                    throw new IllegalArgumentException("Multiple possible resolutions for unqualified type name "
                            + typeLocalName + " (e.g. in " + def.getTypeName() + " and " + found.getTypeName());
                }
                found = def;
            }
        }
        return found;
    }

    public <T extends Objectable> PrismObject<T> instantiate(Class<T> compileTimeClass) throws SchemaException {
        PrismObjectDefinition<T> objDef = findObjectDefinitionByCompileTimeClass(compileTimeClass);
        if (objDef == null) {
            throw new SchemaException("No definition for compile time class " + compileTimeClass);
        }
        return objDef.instantiate();
    }
}