org.nuxeo.ecm.core.schema.SchemaManagerImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.nuxeo.ecm.core.schema.SchemaManagerImpl.java

Source

/*
 * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Bogdan Stefanescu
 *     Florent Guillaume
 */

package org.nuxeo.ecm.core.schema;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.common.utils.FileUtils;
import org.nuxeo.ecm.core.schema.types.AnyType;
import org.nuxeo.ecm.core.schema.types.CompositeType;
import org.nuxeo.ecm.core.schema.types.CompositeTypeImpl;
import org.nuxeo.ecm.core.schema.types.Field;
import org.nuxeo.ecm.core.schema.types.QName;
import org.nuxeo.ecm.core.schema.types.Schema;
import org.nuxeo.ecm.core.schema.types.Type;
import org.nuxeo.ecm.core.schema.types.TypeException;
import org.nuxeo.runtime.api.Framework;
import org.xml.sax.SAXException;

/**
 * Schema Manager implementation.
 * <p>
 * Holds basic types (String, Integer, etc.), schemas, document types and
 * facets.
 */
public class SchemaManagerImpl implements SchemaManager {

    private static final Log log = LogFactory.getLog(SchemaManagerImpl.class);

    /**
     * Whether there have been changes to the registered schemas, facets or
     * document types that require recomputation of the effective ones.
     */
    // volatile to use double-check idiom
    protected volatile boolean dirty = true;

    /** Basic type registry. */
    protected Map<String, Type> types = new HashMap<String, Type>();

    /** All the registered configurations (prefetch). */
    protected List<TypeConfiguration> allConfigurations = new ArrayList<TypeConfiguration>();

    /** All the registered schemas. */
    protected List<SchemaBindingDescriptor> allSchemas = new ArrayList<SchemaBindingDescriptor>();

    /** All the registered facets. */
    protected List<FacetDescriptor> allFacets = new ArrayList<FacetDescriptor>();

    /** All the registered document types. */
    protected List<DocumentTypeDescriptor> allDocumentTypes = new ArrayList<DocumentTypeDescriptor>();

    /** All the registered proxy descriptors. */
    protected List<ProxiesDescriptor> allProxies = new ArrayList<ProxiesDescriptor>();

    /** Effective prefetch info. */
    protected PrefetchInfo prefetchInfo;

    /** Effective schemas. */
    protected Map<String, Schema> schemas = new HashMap<String, Schema>();

    protected final Map<String, Schema> uriToSchema = new HashMap<String, Schema>();

    protected final Map<String, Schema> prefixToSchema = new HashMap<String, Schema>();

    /** Effective facets. */
    protected Map<String, CompositeType> facets = new HashMap<String, CompositeType>();

    protected Set<String> noPerDocumentQueryFacets = new HashSet<String>();

    /** Effective document types. */
    protected Map<String, DocumentTypeImpl> documentTypes = new HashMap<String, DocumentTypeImpl>();

    protected Map<String, Set<String>> documentTypesExtending = new HashMap<String, Set<String>>();

    protected Map<String, Set<String>> documentTypesForFacet = new HashMap<String, Set<String>>();

    /** Effective proxy schemas. */
    protected List<Schema> proxySchemas = new ArrayList<Schema>();

    /** Effective proxy schema names. */
    protected Set<String> proxySchemaNames = new HashSet<String>();

    /** Fields computed lazily. */
    private Map<String, Field> fields = new ConcurrentHashMap<String, Field>();

    private File schemaDir;

    public static final String SCHEMAS_DIR_NAME = "schemas";

    public SchemaManagerImpl() {
        schemaDir = new File(Framework.getRuntime().getHome(), SCHEMAS_DIR_NAME);
        if (!schemaDir.isDirectory()) {
            schemaDir.mkdirs();
        }
        registerBuiltinTypes();
    }

    public File getSchemasDir() {
        return schemaDir;
    }

    protected void registerBuiltinTypes() {
        for (Type type : XSDTypes.getTypes()) {
            registerType(type);
        }
        registerType(AnyType.INSTANCE);
    }

    protected void registerType(Type type) {
        types.put(type.getName(), type);
    }

    // called by XSDLoader
    protected Type getType(String name) {
        return types.get(name);
    }

    // for tests
    protected Collection<Type> getTypes() {
        return types.values();
    }

    public synchronized void registerConfiguration(TypeConfiguration config) {
        allConfigurations.add(config);
        dirty = true;
        log.info("Registered global prefetch: " + config.prefetchInfo);
    }

    public synchronized void unregisterConfiguration(TypeConfiguration config) {
        if (allConfigurations.remove(config)) {
            dirty = true;
            log.info("Unregistered global prefetch: " + config.prefetchInfo);
        } else {
            log.error("Unregistering unknown prefetch: " + config.prefetchInfo);

        }
    }

    public synchronized void registerSchema(SchemaBindingDescriptor sd) {
        allSchemas.add(sd);
        dirty = true;
        log.info("Registered schema: " + sd.name); // TODO from foo.xsd
    }

    public synchronized void unregisterSchema(SchemaBindingDescriptor sd) {
        if (allSchemas.remove(sd)) {
            dirty = true;
            log.info("Unregistered schema: " + sd.name);
        } else {
            log.error("Unregistering unknown schema: " + sd.name);
        }
    }

    public synchronized void registerFacet(FacetDescriptor fd) {
        allFacets.add(fd);
        dirty = true;
        log.info("Registered facet: " + fd.name);
    }

    public synchronized void unregisterFacet(FacetDescriptor fd) {
        if (allFacets.remove(fd)) {
            dirty = true;
            log.info("Unregistered facet: " + fd.name);
        } else {
            log.error("Unregistering unknown facet: " + fd.name);
        }
    }

    public synchronized void registerDocumentType(DocumentTypeDescriptor dtd) {
        allDocumentTypes.add(dtd);
        dirty = true;
        log.info("Registered document type: " + dtd.name);
    }

    public synchronized void unregisterDocumentType(DocumentTypeDescriptor dtd) {
        if (allDocumentTypes.remove(dtd)) {
            dirty = true;
            log.info("Unregistered document type: " + dtd.name);
        } else {
            log.error("Unregistering unknown document type: " + dtd.name);
        }
    }

    // for tests
    public DocumentTypeDescriptor getDocumentTypeDescriptor(String name) {
        DocumentTypeDescriptor last = null;
        for (DocumentTypeDescriptor dtd : allDocumentTypes) {
            if (dtd.name.equals(name)) {
                last = dtd;
            }
        }
        return last;
    }

    public synchronized void registerProxies(ProxiesDescriptor pd) {
        allProxies.add(pd);
        dirty = true;
        log.info("Registered proxies descriptor for schemas: " + pd.getSchemas());
    }

    public synchronized void unregisterProxies(ProxiesDescriptor pd) {
        if (allProxies.remove(pd)) {
            dirty = true;
            log.info("Unregistered proxies descriptor for schemas: " + pd.getSchemas());
        } else {
            log.error("Unregistering unknown proxies descriptor for schemas: " + pd.getSchemas());
        }
    }

    /**
     * Checks if something has to be recomputed if a dynamic register/unregister
     * happened.
     */
    protected void checkDirty() {
        // variant of double-check idiom
        if (!dirty) {
            return;
        }
        synchronized (this) {
            if (!dirty) {
                return;
            }
            // call recompute() synchronized
            recompute();
            dirty = false;
        }
    }

    /**
     * Recomputes effective registries for schemas, facets and document types.
     */
    protected void recompute() {
        recomputeConfiguration();
        recomputeSchemas();
        recomputeFacets(); // depend on schemas
        recomputeDocumentTypes(); // depend on schemas and facets
        recomputeProxies(); // depend on schemas
        fields.clear(); // re-filled lazily
    }

    /*
     * ===== Configuration =====
     */

    protected void recomputeConfiguration() {
        if (allConfigurations.isEmpty()) {
            prefetchInfo = null;
        } else {
            TypeConfiguration last = allConfigurations.get(allConfigurations.size() - 1);
            prefetchInfo = new PrefetchInfo(last.prefetchInfo);
        }
    }

    /*
     * ===== Schemas =====
     */

    protected void recomputeSchemas() {
        schemas.clear();
        uriToSchema.clear();
        prefixToSchema.clear();
        RuntimeException errors = new RuntimeException("Cannot load schemas");
        for (SchemaBindingDescriptor sd : allSchemas) {
            try {
                copySchema(sd);
            } catch (Exception error) {
                if (error instanceof InterruptedException) {
                    // restore interrupted status
                    Thread.currentThread().interrupt();
                }
                errors.addSuppressed(error);
            }
        }
        for (SchemaBindingDescriptor sd : allSchemas) {
            try {
                loadSchema(sd);
            } catch (Exception error) {
                if (error instanceof InterruptedException) {
                    // restore interrupted status
                    Thread.currentThread().interrupt();
                }
                errors.addSuppressed(error);
            }
        }
        if (errors.getSuppressed().length > 0) {
            throw errors;
        }
    }

    protected void copySchema(SchemaBindingDescriptor sd) throws IOException, SAXException, TypeException {
        if (sd.src == null || sd.src.length() == 0) {
            // log.error("INLINE Schemas ARE NOT YET IMPLEMENTED!");
            return;
        }
        URL url = sd.context.getLocalResource(sd.src);
        if (url == null) {
            // try asking the class loader
            url = sd.context.getResource(sd.src);
        }
        if (url == null) {
            log.error("XSD Schema not found: " + sd.src);
            return;
        }
        InputStream in = url.openStream();
        try {
            sd.file = new File(schemaDir, sd.name + ".xsd");
            FileUtils.copyToFile(in, sd.file); // may overwrite
        } finally {
            in.close();
        }
    }

    protected void loadSchema(SchemaBindingDescriptor sd) throws IOException, SAXException, TypeException {
        if (sd.file == null) {
            // log.error("INLINE Schemas ARE NOT YET IMPLEMENTED!");
            return;
        }
        // loadSchema calls this.registerSchema
        XSDLoader schemaLoader = new XSDLoader(this, sd);
        Schema oldschema = schemas.get(sd.name);
        schemaLoader.loadSchema(sd.name, sd.prefix, sd.file, sd.override, sd.xsdRootElement);
        if (oldschema == null) {
            log.info("Registered schema: " + sd.name + " from " + sd.file);
        } else {
            log.info("Reregistered schema: " + sd.name);
        }

    }

    // called from XSDLoader, does not do the checkDirty call
    protected Schema getSchemaInternal(String name) {
        return schemas.get(name);
    }

    // called from XSDLoader
    protected void registerSchema(Schema schema) {
        schemas.put(schema.getName(), schema);
        Namespace ns = schema.getNamespace();
        uriToSchema.put(ns.uri, schema);
        if (!StringUtils.isBlank(ns.prefix)) {
            prefixToSchema.put(ns.prefix, schema);
        }
    }

    @Override
    public Schema[] getSchemas() {
        checkDirty();
        return new ArrayList<Schema>(schemas.values()).toArray(new Schema[0]);
    }

    @Override
    public Schema getSchema(String name) {
        checkDirty();
        return schemas.get(name);
    }

    @Override
    public Schema getSchemaFromPrefix(String schemaPrefix) {
        checkDirty();
        return prefixToSchema.get(schemaPrefix);
    }

    @Override
    public Schema getSchemaFromURI(String schemaURI) {
        checkDirty();
        return uriToSchema.get(schemaURI);
    }

    /*
     * ===== Facets =====
     */

    protected void recomputeFacets() {
        facets.clear();
        noPerDocumentQueryFacets.clear();
        for (FacetDescriptor fd : allFacets) {
            recomputeFacet(fd);
        }
    }

    protected void recomputeFacet(FacetDescriptor fd) {
        Set<String> schemas = SchemaDescriptor.getSchemaNames(fd.schemas);
        registerFacet(fd.name, schemas);
        if (Boolean.FALSE.equals(fd.perDocumentQuery)) {
            noPerDocumentQueryFacets.add(fd.name);
        }
    }

    // also called when a document type references an unknown facet (WARN)
    protected CompositeType registerFacet(String name, Set<String> schemaNames) {
        List<Schema> facetSchemas = new ArrayList<Schema>(schemaNames.size());
        for (String schemaName : schemaNames) {
            Schema schema = schemas.get(schemaName);
            if (schema == null) {
                log.error("Facet: " + name + " uses unknown schema: " + schemaName);
                continue;
            }
            facetSchemas.add(schema);
        }
        CompositeType ct = new CompositeTypeImpl(null, SchemaNames.FACETS, name, facetSchemas);
        facets.put(name, ct);
        return ct;
    }

    @Override
    public CompositeType[] getFacets() {
        checkDirty();
        return new ArrayList<CompositeType>(facets.values()).toArray(new CompositeType[facets.size()]);
    }

    @Override
    public CompositeType getFacet(String name) {
        checkDirty();
        return facets.get(name);
    }

    @Override
    public Set<String> getNoPerDocumentQueryFacets() {
        checkDirty();
        return Collections.unmodifiableSet(noPerDocumentQueryFacets);
    }

    /*
     * ===== Document types =====
     */

    protected void recomputeDocumentTypes() {
        // effective descriptors with override
        // linked hash map to keep order for reproducibility
        Map<String, DocumentTypeDescriptor> dtds = new LinkedHashMap<String, DocumentTypeDescriptor>();
        for (DocumentTypeDescriptor dtd : allDocumentTypes) {
            String name = dtd.name;
            DocumentTypeDescriptor newDtd = dtd;
            if (dtd.append && dtds.containsKey(dtd.name)) {
                newDtd = mergeDocumentTypeDescriptors(dtd, dtds.get(name));
            }
            dtds.put(name, newDtd);
        }
        // recompute all types, parents first
        documentTypes.clear();
        documentTypesExtending.clear();
        registerDocumentType(new DocumentTypeImpl(TypeConstants.DOCUMENT)); // Document
        for (String name : dtds.keySet()) {
            LinkedHashSet<String> stack = new LinkedHashSet<String>();
            recomputeDocumentType(name, stack, dtds);
        }

        // document types having a given facet
        documentTypesForFacet.clear();
        for (DocumentType docType : documentTypes.values()) {
            for (String facet : docType.getFacets()) {
                Set<String> set = documentTypesForFacet.get(facet);
                if (set == null) {
                    documentTypesForFacet.put(facet, set = new HashSet<String>());
                }
                set.add(docType.getName());
            }
        }

    }

    protected DocumentTypeDescriptor mergeDocumentTypeDescriptors(DocumentTypeDescriptor src,
            DocumentTypeDescriptor dst) {
        return dst.clone().merge(src);
    }

    protected DocumentType recomputeDocumentType(String name, Set<String> stack,
            Map<String, DocumentTypeDescriptor> dtds) {
        DocumentTypeImpl docType = documentTypes.get(name);
        if (docType != null) {
            // already done
            return docType;
        }
        if (stack.contains(name)) {
            log.error("Document type: " + name + " used in parent inheritance loop: " + stack);
            return null;
        }
        DocumentTypeDescriptor dtd = dtds.get(name);
        if (dtd == null) {
            log.error("Document type: " + name + " does not exist, used as parent by type: " + stack);
            return null;
        }

        // find and recompute the parent first
        DocumentType parent;
        String parentName = dtd.superTypeName;
        if (parentName == null) {
            parent = null;
        } else {
            parent = documentTypes.get(parentName);
            if (parent == null) {
                stack.add(name);
                parent = recomputeDocumentType(parentName, stack, dtds);
                stack.remove(name);
            }
        }

        // what it extends
        for (Type p = parent; p != null; p = p.getSuperType()) {
            Set<String> set = documentTypesExtending.get(p.getName());
            set.add(name);
        }

        return recomputeDocumentType(name, dtd, parent);
    }

    protected DocumentType recomputeDocumentType(String name, DocumentTypeDescriptor dtd, DocumentType parent) {
        // find the facets and schemas names
        Set<String> facetNames = new HashSet<String>();
        Set<String> schemaNames = SchemaDescriptor.getSchemaNames(dtd.schemas);
        facetNames.addAll(Arrays.asList(dtd.facets));

        // inherited
        if (parent != null) {
            facetNames.addAll(parent.getFacets());
            schemaNames.addAll(Arrays.asList(parent.getSchemaNames()));
        }

        // add schemas names from facets
        for (String facetName : facetNames) {
            CompositeType ct = facets.get(facetName);
            if (ct == null) {
                log.warn("Undeclared facet: " + facetName + " used in document type: " + name);
                // register it with no schemas
                ct = registerFacet(facetName, Collections.<String>emptySet());
            }
            schemaNames.addAll(Arrays.asList(ct.getSchemaNames()));
        }

        // find the schemas
        List<Schema> docTypeSchemas = new ArrayList<Schema>();
        for (String schemaName : schemaNames) {
            Schema schema = schemas.get(schemaName);
            if (schema == null) {
                log.error("Document type: " + name + " uses unknown schema: " + schemaName);
                continue;
            }
            docTypeSchemas.add(schema);
        }

        // create doctype
        PrefetchInfo prefetch = dtd.prefetch == null ? prefetchInfo : new PrefetchInfo(dtd.prefetch);
        DocumentTypeImpl docType = new DocumentTypeImpl(name, parent, docTypeSchemas, facetNames, prefetch);
        registerDocumentType(docType);

        return docType;
    }

    protected void registerDocumentType(DocumentTypeImpl docType) {
        String name = docType.getName();
        documentTypes.put(name, docType);
        documentTypesExtending.put(name, new HashSet<String>(Collections.singleton(name)));
    }

    @Override
    public DocumentType getDocumentType(String name) {
        checkDirty();
        return documentTypes.get(name);
    }

    @Override
    public Set<String> getDocumentTypeNamesForFacet(String facet) {
        checkDirty();
        return documentTypesForFacet.get(facet);
    }

    @Override
    public Set<String> getDocumentTypeNamesExtending(String docTypeName) {
        checkDirty();
        return documentTypesExtending.get(docTypeName);
    }

    @Override
    public DocumentType[] getDocumentTypes() {
        checkDirty();
        return new ArrayList<DocumentType>(documentTypes.values()).toArray(new DocumentType[0]);
    }

    @Override
    public int getDocumentTypesCount() {
        checkDirty();
        return documentTypes.size();
    }

    @Override
    public boolean hasSuperType(String docType, String superType) {
        if (docType == null || superType == null) {
            return false;
        }
        Set<String> types = getDocumentTypeNamesExtending(superType);
        return types != null && types.contains(docType);
    }

    /*
     * ===== Proxies =====
     */

    protected void recomputeProxies() {
        List<Schema> list = new ArrayList<Schema>();
        Set<String> nameSet = new HashSet<String>();
        for (ProxiesDescriptor pd : allProxies) {
            if (!pd.getType().equals("*")) {
                log.error("Proxy descriptor for specific type not supported: " + pd);
            }
            for (String schemaName : pd.getSchemas()) {
                if (nameSet.contains(schemaName)) {
                    continue;
                }
                Schema schema = schemas.get(schemaName);
                if (schema == null) {
                    log.error("Proxy schema uses unknown schema: " + schemaName);
                    continue;
                }
                list.add(schema);
                nameSet.add(schemaName);
            }
        }
        proxySchemas = list;
        proxySchemaNames = nameSet;
    }

    @Override
    public List<Schema> getProxySchemas(String docType) {
        // docType unused for now
        checkDirty();
        return new ArrayList<Schema>(proxySchemas);
    }

    @Override
    public boolean isProxySchema(String schema, String docType) {
        // docType unused for now
        checkDirty();
        return proxySchemaNames.contains(schema);
    }

    /*
     * ===== Fields =====
     */

    @Override
    public Field getField(String prefixedName) {
        checkDirty();
        Field field = fields.get(prefixedName);
        if (field == null) {
            QName qname = QName.valueOf(prefixedName);
            String prefix = qname.getPrefix();
            Schema schema = getSchemaFromPrefix(prefix);
            if (schema == null) {
                // try using the name
                schema = getSchema(prefix);
            }
            if (schema != null) {
                field = schema.getField(qname.getLocalName());
                if (field != null) {
                    // map is concurrent so parallelism is ok
                    fields.put(prefixedName, field);
                }
            }
        }
        return field;
    }

    public void flushPendingsRegistration() {
        checkDirty();
    }

}