org.opencms.cmis.CmsCmisTypeManager.java Source code

Java tutorial

Introduction

Here is the source code for org.opencms.cmis.CmsCmisTypeManager.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.opencms.cmis;

import org.opencms.file.CmsObject;
import org.opencms.file.CmsPropertyDefinition;
import org.opencms.main.CmsException;
import org.opencms.main.CmsLog;
import org.opencms.relations.CmsRelationType;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.chemistry.opencmis.commons.PropertyIds;
import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition;
import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition;
import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionContainer;
import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionList;
import org.apache.chemistry.opencmis.commons.enums.BaseTypeId;
import org.apache.chemistry.opencmis.commons.enums.Cardinality;
import org.apache.chemistry.opencmis.commons.enums.ContentStreamAllowed;
import org.apache.chemistry.opencmis.commons.enums.PropertyType;
import org.apache.chemistry.opencmis.commons.enums.Updatability;
import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException;
import org.apache.chemistry.opencmis.commons.impl.WSConverter;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.AbstractPropertyDefinition;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.AbstractTypeDefinition;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.DocumentTypeDefinitionImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.FolderTypeDefinitionImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyBooleanDefinitionImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyDateTimeDefinitionImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyDecimalDefinitionImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyHtmlDefinitionImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIdDefinitionImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIntegerDefinitionImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyStringDefinitionImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyUriDefinitionImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.RelationshipTypeDefinitionImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.TypeDefinitionContainerImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.TypeDefinitionListImpl;
import org.apache.commons.logging.Log;

/**
 * This class keeps track of all the types which should be available for a {@link I_CmsCmisRepository}.
 */
public class CmsCmisTypeManager {

    /** CMIS type id for documents. */
    public static final String DOCUMENT_TYPE_ID = BaseTypeId.CMIS_DOCUMENT.value();

    /** CMIS type id for folders. */
    public static final String FOLDER_TYPE_ID = BaseTypeId.CMIS_FOLDER.value();

    /** Prefix for inherited properties. */
    public static final String INHERITED_PREFIX = "opencms-inherited:";

    /** CMIS type id for policies. */
    public static final String POLICY_TYPE_ID = BaseTypeId.CMIS_POLICY.value();

    /** The prefix used for normal OpenCms resource properties. */
    public static final String PROPERTY_PREFIX = "opencms:";

    /** Prefix for dynamic properties. */
    public static final String PROPERTY_PREFIX_DYNAMIC = "opencms-dynamic:";

    /** The prefix for special properties. */
    public static final String PROPERTY_PREFIX_SPECIAL = "opencms-special:";

    /** The name of the propery containing the resource type name. */
    public static final String PROPERTY_RESOURCE_TYPE = PROPERTY_PREFIX_SPECIAL + "resource-type";

    /** CMIS type id for relationships. */
    public static final String RELATIONSHIP_TYPE_ID = BaseTypeId.CMIS_RELATIONSHIP.value();

    /** Need to refresh property data after this time. */
    public static final long UPDATE_INTERVAL = 1000 * 60 * 5;

    /** The logger instance for this class. */
    static final Log LOG = CmsLog.getLog(CmsCmisTypeManager.class);

    /** The namespace used for properties. */
    private static final String NAMESPACE = "http://opencms.org/opencms-cmis";

    /** The admin CMS context. */
    private CmsObject m_adminCms;

    /** The list of OpenCms property definitions. */
    private List<CmsPropertyDefinition> m_cmsPropertyDefinitions;

    /** The last update time. */
    private long m_lastUpdate;

    /** List of dynamic property providers. */
    private List<I_CmsPropertyProvider> m_propertyProviders = new ArrayList<I_CmsPropertyProvider>();

    /** The internal list of type definitions. */
    private List<TypeDefinitionContainer> m_typeList;

    /** The internal map of type definitions. */
    private Map<String, TypeDefinitionContainerImpl> m_types;

    /**
     * Creates a new type manager instance.<p>
     *
     * @param adminCms a CMS context with admin privileges
     * @param propertyProviders list which will be filled with property providers
     *
     * @throws CmsException if something goes wrong
     */
    public CmsCmisTypeManager(CmsObject adminCms, List<I_CmsPropertyProvider> propertyProviders)
            throws CmsException {

        m_adminCms = adminCms;
        m_propertyProviders = propertyProviders;
        setup();
    }

    /**
     * Adds the base CMIS property definitions common to folders and documents.<p>
     *
     * @param type the type definition to which the property definitions should be added
     */
    private static void addBasePropertyDefinitions(AbstractTypeDefinition type) {

        type.addPropertyDefinition(createPropDef(PropertyIds.BASE_TYPE_ID, "Base Type Id", "Base Type Id",
                PropertyType.ID, Cardinality.SINGLE, Updatability.READONLY, false, false));

        type.addPropertyDefinition(createPropDef(PropertyIds.OBJECT_ID, "Object Id", "Object Id", PropertyType.ID,
                Cardinality.SINGLE, Updatability.READONLY, false, false));

        type.addPropertyDefinition(createPropDef(PropertyIds.OBJECT_TYPE_ID, "Type Id", "Type Id", PropertyType.ID,
                Cardinality.SINGLE, Updatability.ONCREATE, false, true));

        type.addPropertyDefinition(createPropDef(PropertyIds.NAME, "Name", "Name", PropertyType.STRING,
                Cardinality.SINGLE, Updatability.READWRITE, false, true));

        type.addPropertyDefinition(queryableAndOrderable(createPropDef(PropertyIds.CREATED_BY, "Created By",
                "Created By", PropertyType.STRING, Cardinality.SINGLE, Updatability.READONLY, false, false)));

        type.addPropertyDefinition(queryableAndOrderable(createPropDef(PropertyIds.CREATION_DATE, "Creation Date",
                "Creation Date", PropertyType.DATETIME, Cardinality.SINGLE, Updatability.READONLY, false, false)));

        type.addPropertyDefinition(queryableAndOrderable(
                createPropDef(PropertyIds.LAST_MODIFIED_BY, "Last Modified By", "Last Modified By",
                        PropertyType.STRING, Cardinality.SINGLE, Updatability.READONLY, false, false)));

        type.addPropertyDefinition(queryableAndOrderable(createPropDef(PropertyIds.LAST_MODIFICATION_DATE,
                "Last Modification Date", "Last Modification Date", PropertyType.DATETIME, Cardinality.SINGLE,
                Updatability.READONLY, false, false)));

        type.addPropertyDefinition(createPropDef(PropertyIds.CHANGE_TOKEN, "Change Token", "Change Token",
                PropertyType.STRING, Cardinality.SINGLE, Updatability.READONLY, false, false));

    }

    /**
     * Adds CMIS property definitions for documents.<p>
     *
     * @param type the document type
     */
    private static void addDocumentPropertyDefinitions(DocumentTypeDefinitionImpl type) {

        type.addPropertyDefinition(createPropDef(PropertyIds.IS_IMMUTABLE, "Is Immutable", "Is Immutable",
                PropertyType.BOOLEAN, Cardinality.SINGLE, Updatability.READONLY, false, false));

        type.addPropertyDefinition(
                createPropDef(PropertyIds.IS_LATEST_VERSION, "Is Latest Version", "Is Latest Version",
                        PropertyType.BOOLEAN, Cardinality.SINGLE, Updatability.READONLY, false, false));

        type.addPropertyDefinition(createPropDef(PropertyIds.IS_MAJOR_VERSION, "Is Major Version",
                "Is Major Version", PropertyType.BOOLEAN, Cardinality.SINGLE, Updatability.READONLY, false, false));

        type.addPropertyDefinition(createPropDef(PropertyIds.IS_LATEST_MAJOR_VERSION, "Is Latest Major Version",
                "Is Latest Major Version", PropertyType.BOOLEAN, Cardinality.SINGLE, Updatability.READONLY, false,
                false));

        type.addPropertyDefinition(createPropDef(PropertyIds.VERSION_LABEL, "Version Label", "Version Label",
                PropertyType.STRING, Cardinality.SINGLE, Updatability.READONLY, false, false));

        type.addPropertyDefinition(createPropDef(PropertyIds.VERSION_SERIES_ID, "Version Series Id",
                "Version Series Id", PropertyType.ID, Cardinality.SINGLE, Updatability.READONLY, false, false));

        type.addPropertyDefinition(createPropDef(PropertyIds.IS_VERSION_SERIES_CHECKED_OUT,
                "Is Verison Series Checked Out", "Is Verison Series Checked Out", PropertyType.BOOLEAN,
                Cardinality.SINGLE, Updatability.READONLY, false, false));

        type.addPropertyDefinition(createPropDef(PropertyIds.VERSION_SERIES_CHECKED_OUT_ID,
                "Version Series Checked Out Id", "Version Series Checked Out Id", PropertyType.ID,
                Cardinality.SINGLE, Updatability.READONLY, false, false));

        type.addPropertyDefinition(createPropDef(PropertyIds.VERSION_SERIES_CHECKED_OUT_BY,
                "Version Series Checked Out By", "Version Series Checked Out By", PropertyType.STRING,
                Cardinality.SINGLE, Updatability.READONLY, false, false));

        type.addPropertyDefinition(createPropDef(PropertyIds.CHECKIN_COMMENT, "Checkin Comment", "Checkin Comment",
                PropertyType.STRING, Cardinality.SINGLE, Updatability.READONLY, false, false));

        type.addPropertyDefinition(
                createPropDef(PropertyIds.CONTENT_STREAM_LENGTH, "Content Stream Length", "Content Stream Length",
                        PropertyType.INTEGER, Cardinality.SINGLE, Updatability.READONLY, false, false));

        type.addPropertyDefinition(createPropDef(PropertyIds.CONTENT_STREAM_MIME_TYPE, "MIME Type", "MIME Type",
                PropertyType.STRING, Cardinality.SINGLE, Updatability.READONLY, false, false));

        type.addPropertyDefinition(createPropDef(PropertyIds.CONTENT_STREAM_FILE_NAME, "Filename", "Filename",
                PropertyType.STRING, Cardinality.SINGLE, Updatability.READONLY, false, false));

        type.addPropertyDefinition(createPropDef(PropertyIds.CONTENT_STREAM_ID, "Content Stream Id",
                "Content Stream Id", PropertyType.ID, Cardinality.SINGLE, Updatability.READONLY, false, false));
    }

    /**
     * Adds folder specific CMIS property definitions.<p>
     *
     * @param type the folder type
     */
    private static void addFolderPropertyDefinitions(FolderTypeDefinitionImpl type) {

        type.addPropertyDefinition(createPropDef(PropertyIds.PARENT_ID, "Parent Id", "Parent Id", PropertyType.ID,
                Cardinality.SINGLE, Updatability.READONLY, false, false));

        type.addPropertyDefinition(createPropDef(PropertyIds.ALLOWED_CHILD_OBJECT_TYPE_IDS,
                "Allowed Child Object Type Ids", "Allowed Child Object Type Ids", PropertyType.ID,
                Cardinality.MULTI, Updatability.READONLY, false, false));

        type.addPropertyDefinition(createPropDef(PropertyIds.PATH, "Path", "Path", PropertyType.STRING,
                Cardinality.SINGLE, Updatability.READONLY, false, false));
    }

    /**
     * Helper method to add the property definitions specific to relationship types.<p>
     *
     * @param type the type definition to which the property definitions should be added
     */
    private static void addRelationPropertyDefinitions(RelationshipTypeDefinitionImpl type) {

        type.addPropertyDefinition(createPropDef(PropertyIds.SOURCE_ID, "Source", "Source", PropertyType.ID,
                Cardinality.SINGLE, Updatability.ONCREATE, false, true));

        type.addPropertyDefinition(createPropDef(PropertyIds.TARGET_ID, "Target", "Target", PropertyType.ID,
                Cardinality.SINGLE, Updatability.ONCREATE, false, true));

    }

    /**
     * Copies a type definition.<p>
     *
     * @param type the type definition to copy
     *
     * @return the copied type definition
     */
    private static TypeDefinition copyTypeDefintion(TypeDefinition type) {

        return WSConverter.convert(WSConverter.convert(type));
    }

    /**
     * Creates a property definition.<p>
     *
     * @param id the property definition id
     * @param displayName the property display name
     * @param description the property description
     * @param datatype the property type
     * @param cardinality the property cardinality
     * @param updateability the property updatability
     * @param inherited the property inheritance status
     * @param required true true if the property is required
     *
     * @return the property definition
     */
    private static AbstractPropertyDefinition<?> createPropDef(String id, String displayName, String description,
            PropertyType datatype, Cardinality cardinality, Updatability updateability, boolean inherited,
            boolean required) {

        AbstractPropertyDefinition<?> result = null;

        switch (datatype) {
        case BOOLEAN:
            result = new PropertyBooleanDefinitionImpl();
            break;
        case DATETIME:
            result = new PropertyDateTimeDefinitionImpl();
            break;
        case DECIMAL:
            result = new PropertyDecimalDefinitionImpl();
            break;
        case HTML:
            result = new PropertyHtmlDefinitionImpl();
            break;
        case ID:
            result = new PropertyIdDefinitionImpl();
            break;
        case INTEGER:
            result = new PropertyIntegerDefinitionImpl();
            break;
        case STRING:
            result = new PropertyStringDefinitionImpl();
            break;
        case URI:
            result = new PropertyUriDefinitionImpl();
            break;
        default:
            throw new RuntimeException("Unknown datatype! Spec change?");
        }

        result.setId(id);
        result.setLocalName(id);
        result.setDisplayName(displayName);
        result.setDescription(description);
        result.setPropertyType(datatype);
        result.setCardinality(cardinality);
        result.setUpdatability(updateability);
        result.setIsInherited(Boolean.valueOf(inherited));
        result.setIsRequired(Boolean.valueOf(required));
        result.setIsQueryable(Boolean.FALSE);
        result.setIsOrderable(Boolean.FALSE);
        result.setQueryName(id);
        return result;
    }

    /**
     * Helper method to make a property definition queryable and orderable.<p>
     *
     * @param propDef the property definition
     *
     * @return the modified property definition
     */
    private static AbstractPropertyDefinition<?> queryableAndOrderable(AbstractPropertyDefinition<?> propDef) {

        propDef.setIsQueryable(Boolean.TRUE);
        propDef.setIsOrderable(Boolean.TRUE);
        return propDef;
    }

    /**
     * Gets a list of names of OpenCms property definitions.<p>
     *
     * @return the list of OpenCms property names
     */
    public List<String> getCmsPropertyNames() {

        refresh();
        List<String> result = new ArrayList<String>();
        for (CmsPropertyDefinition propDef : m_cmsPropertyDefinitions) {
            result.add(propDef.getName());
        }
        return result;
    }

    /**
     * Gets the property provider for a given key.<p>
     *
     * @param key the property nme
     *
     * @return the property provider for the given name, or null if there isn't any
     */
    public I_CmsPropertyProvider getPropertyProvider(String key) {

        if (key.startsWith(PROPERTY_PREFIX_DYNAMIC)) {
            key = key.substring(PROPERTY_PREFIX_DYNAMIC.length());
        }
        for (I_CmsPropertyProvider provider : m_propertyProviders) {
            if (provider.getName().equals(key)) {
                return provider;
            }
        }
        return null;
    }

    /**
     * Gets the list of all property providers.<p>
     *
     * @return the list of property providers
     */
    public List<I_CmsPropertyProvider> getPropertyProviders() {

        return Collections.unmodifiableList(m_propertyProviders);
    }

    /**
     * Gets a type definition by id.<p>
     *
     * @param typeId the type id
     * @return the type definition
     */
    public TypeDefinition getType(String typeId) {

        refresh();
        TypeDefinitionContainer tc = m_types.get(typeId);
        if (tc == null) {
            return null;
        }

        return tc.getTypeDefinition();
    }

    /**
     * Collects the children of a type.<p>
     *
     * @param typeId the id of the type
     * @param includePropertyDefinitions true if the property definitions should be included
     * @param maxItems the maximum number of items to return
     * @param skipCount the number of items to skip
     *
     * @return the children of the type
     */
    public TypeDefinitionList getTypeChildren(

            String typeId, boolean includePropertyDefinitions, BigInteger maxItems, BigInteger skipCount) {

        refresh();
        TypeDefinitionListImpl result = new TypeDefinitionListImpl(new ArrayList<TypeDefinition>());

        int skip = (skipCount == null ? 0 : skipCount.intValue());
        if (skip < 0) {
            skip = 0;
        }

        int max = (maxItems == null ? Integer.MAX_VALUE : maxItems.intValue());
        if (max < 1) {
            return result;
        }

        if (typeId == null) {
            if (skip < 1) {
                result.getList().add(copyTypeDefintion(m_types.get(FOLDER_TYPE_ID).getTypeDefinition()));
                max--;
            }
            if ((skip < 2) && (max > 0)) {
                result.getList().add(copyTypeDefintion(m_types.get(DOCUMENT_TYPE_ID).getTypeDefinition()));
                max--;
            }

            result.setHasMoreItems(Boolean.valueOf((result.getList().size() + skip) < 2));
            result.setNumItems(BigInteger.valueOf(2));
        } else {
            TypeDefinitionContainer tc = m_types.get(typeId);
            if ((tc == null) || (tc.getChildren() == null)) {
                return result;
            }

            for (TypeDefinitionContainer child : tc.getChildren()) {
                if (skip > 0) {
                    skip--;
                    continue;
                }

                result.getList().add(copyTypeDefintion(child.getTypeDefinition()));

                max--;
                if (max == 0) {
                    break;
                }
            }

            result.setHasMoreItems(Boolean.valueOf((result.getList().size() + skip) < tc.getChildren().size()));
            result.setNumItems(BigInteger.valueOf(tc.getChildren().size()));
        }

        if (!includePropertyDefinitions) {
            for (TypeDefinition type : result.getList()) {
                type.getPropertyDefinitions().clear();
            }
        }

        return result;
    }

    /**
     * Gets the type definition for a given id in the given call context.<p>
     *
     * @param typeId the type id
     *
     * @return the matching type definition
     */
    public TypeDefinition getTypeDefinition(String typeId) {

        refresh();
        TypeDefinitionContainer tc = m_types.get(typeId);
        if (tc == null) {
            throw new CmisObjectNotFoundException("Type '" + typeId + "' is unknown!");
        }

        return copyTypeDefintion(tc.getTypeDefinition());
    }

    /**
     * Gets the descendants of a type.<p>
     *
     * @param typeId the parent type id
     * @param depth the depth up to which the descendant types should be collected
     * @param includePropertyDefinitions true if the property definitions should be included
     *
     * @return the descendants of the type
     */
    public List<TypeDefinitionContainer> getTypeDescendants(

            String typeId, BigInteger depth, boolean includePropertyDefinitions) {

        refresh();
        List<TypeDefinitionContainer> result = new ArrayList<TypeDefinitionContainer>();

        // check depth
        int d = (depth == null ? -1 : depth.intValue());
        if (d == 0) {
            throw new CmisInvalidArgumentException("Depth must not be 0!");
        }

        if (typeId == null) {
            result.add(getTypeDescendants(d, m_types.get(FOLDER_TYPE_ID), includePropertyDefinitions));
            result.add(getTypeDescendants(d, m_types.get(DOCUMENT_TYPE_ID), includePropertyDefinitions));
            result.add(getTypeDescendants(d, m_types.get(RELATIONSHIP_TYPE_ID), includePropertyDefinitions));
        } else {
            TypeDefinitionContainer tc = m_types.get(typeId);
            if (tc != null) {
                result.add(getTypeDescendants(d, tc, includePropertyDefinitions));
            }
        }

        return result;
    }

    /**
     * Creates the CMIS property definition for an OpenCms resource property definition.<p>
     *
     * @param cmsDef the OpenCms property definition
     *
     * @return the CMIS property definition
     */
    PropertyDefinition<?> createOpenCmsPropertyDefinition(CmsPropertyDefinition cmsDef) {

        return createPropDef(PROPERTY_PREFIX + cmsDef.getName(), cmsDef.getName(), cmsDef.getName(),
                PropertyType.STRING, Cardinality.SINGLE, Updatability.READWRITE, false, false);
    }

    /**
     * Creates the base types.
     *
     * @throws CmsException if something goes wrong
     */
    void setup() throws CmsException {

        m_types = new HashMap<String, TypeDefinitionContainerImpl>();
        m_typeList = new ArrayList<TypeDefinitionContainer>();
        m_cmsPropertyDefinitions = m_adminCms.readAllPropertyDefinitions();

        // folder type
        FolderTypeDefinitionImpl folderType = new FolderTypeDefinitionImpl();
        folderType.setBaseTypeId(BaseTypeId.CMIS_FOLDER);
        folderType.setIsControllableAcl(Boolean.TRUE);
        folderType.setIsControllablePolicy(Boolean.FALSE);
        folderType.setIsCreatable(Boolean.TRUE);
        folderType.setDescription("Folder");
        folderType.setDisplayName("Folder");
        folderType.setIsFileable(Boolean.TRUE);
        folderType.setIsFulltextIndexed(Boolean.FALSE);
        folderType.setIsIncludedInSupertypeQuery(Boolean.TRUE);
        folderType.setLocalName("Folder");
        folderType.setLocalNamespace(NAMESPACE);
        folderType.setIsQueryable(Boolean.TRUE);
        folderType.setQueryName("cmis:folder");
        folderType.setId(FOLDER_TYPE_ID);

        addBasePropertyDefinitions(folderType);
        addFolderPropertyDefinitions(folderType);
        addCmsPropertyDefinitions(folderType);
        addProviderPropertyDefinitions(folderType);

        addTypeInternal(folderType);

        // document type
        DocumentTypeDefinitionImpl documentType = new DocumentTypeDefinitionImpl();
        documentType.setBaseTypeId(BaseTypeId.CMIS_DOCUMENT);
        documentType.setIsControllableAcl(Boolean.TRUE);
        documentType.setIsControllablePolicy(Boolean.FALSE);
        documentType.setIsCreatable(Boolean.TRUE);
        documentType.setDescription("Document");
        documentType.setDisplayName("Document");
        documentType.setIsFileable(Boolean.TRUE);
        documentType.setIsFulltextIndexed(Boolean.FALSE);
        documentType.setIsIncludedInSupertypeQuery(Boolean.TRUE);
        documentType.setLocalName("Document");
        documentType.setLocalNamespace(NAMESPACE);
        documentType.setIsQueryable(Boolean.TRUE);
        documentType.setQueryName("cmis:document");
        documentType.setId(DOCUMENT_TYPE_ID);

        documentType.setIsVersionable(Boolean.FALSE);
        documentType.setContentStreamAllowed(ContentStreamAllowed.REQUIRED);

        addBasePropertyDefinitions(documentType);
        addDocumentPropertyDefinitions(documentType);
        addCmsPropertyDefinitions(documentType);
        addProviderPropertyDefinitions(documentType);

        addTypeInternal(documentType);

        // relationship types
        RelationshipTypeDefinitionImpl relationshipType = new RelationshipTypeDefinitionImpl();
        relationshipType.setBaseTypeId(BaseTypeId.CMIS_RELATIONSHIP);
        relationshipType.setIsControllableAcl(Boolean.FALSE);
        relationshipType.setIsControllablePolicy(Boolean.FALSE);
        relationshipType.setIsCreatable(Boolean.FALSE);
        relationshipType.setDescription("Relationship");
        relationshipType.setDisplayName("Relationship");
        relationshipType.setIsFileable(Boolean.FALSE);
        relationshipType.setIsIncludedInSupertypeQuery(Boolean.TRUE);
        relationshipType.setLocalName("Relationship");
        relationshipType.setLocalNamespace(NAMESPACE);
        relationshipType.setIsQueryable(Boolean.FALSE);
        relationshipType.setQueryName("cmis:relationship");
        relationshipType.setId(RELATIONSHIP_TYPE_ID);
        List<String> typeList = new ArrayList<String>();
        typeList.add("cmis:document");
        typeList.add("cmis:folder");
        relationshipType.setAllowedSourceTypes(typeList);
        relationshipType.setAllowedTargetTypes(typeList);
        addBasePropertyDefinitions(relationshipType);
        addRelationPropertyDefinitions(relationshipType);
        addTypeInternal(relationshipType);

        for (CmsRelationType relType : CmsRelationType.getAll()) {
            createRelationshipType(relType);
        }
        m_lastUpdate = System.currentTimeMillis();
    }

    /**
     * Adds the CMIS property definitions corresponding to the OpenCms property definitions to a CMIS type definition.<p>
     *
     * @param type the type to which the property definitions should be added
     */
    private void addCmsPropertyDefinitions(AbstractTypeDefinition type) {

        for (CmsPropertyDefinition propDef : m_cmsPropertyDefinitions) {
            type.addPropertyDefinition(createOpenCmsPropertyDefinition(propDef));
            type.addPropertyDefinition(
                    createPropDef(INHERITED_PREFIX + propDef.getName(), propDef.getName(), propDef.getName(),
                            PropertyType.STRING, Cardinality.SINGLE, Updatability.READONLY, false, false));

        }
        type.addPropertyDefinition(createPropDef(PROPERTY_RESOURCE_TYPE, "Resource type", "Resource type",
                PropertyType.STRING, Cardinality.SINGLE, Updatability.ONCREATE, false, true));
    }

    /**
     * Helper method for adding property definitions for the dynamic properties.<p>
     *
     * @param type the type definition to which the properties should be added
     */
    private void addProviderPropertyDefinitions(AbstractTypeDefinition type) {

        for (I_CmsPropertyProvider provider : m_propertyProviders) {
            type.addPropertyDefinition(createPropDef(PROPERTY_PREFIX_DYNAMIC + provider.getName(),
                    provider.getName(), provider.getName(), PropertyType.STRING, Cardinality.SINGLE,
                    provider.isWritable() ? Updatability.READWRITE : Updatability.READONLY, false, false));
        }
    }

    /**
     * Adds a type to collection with inheriting base type properties.
     *
     * @param type the type definition to add
     *
     * @return true if the type definition was added
     */
    private boolean addType(TypeDefinition type) {

        if (type == null) {
            return false;
        }

        if (type.getBaseTypeId() == null) {
            return false;
        }

        // find base type
        TypeDefinition baseType = null;
        if (type.getBaseTypeId() == BaseTypeId.CMIS_DOCUMENT) {
            baseType = copyTypeDefintion(m_types.get(DOCUMENT_TYPE_ID).getTypeDefinition());
        } else if (type.getBaseTypeId() == BaseTypeId.CMIS_FOLDER) {
            baseType = copyTypeDefintion(m_types.get(FOLDER_TYPE_ID).getTypeDefinition());
        } else if (type.getBaseTypeId() == BaseTypeId.CMIS_RELATIONSHIP) {
            baseType = copyTypeDefintion(m_types.get(RELATIONSHIP_TYPE_ID).getTypeDefinition());
        } else if (type.getBaseTypeId() == BaseTypeId.CMIS_POLICY) {
            baseType = copyTypeDefintion(m_types.get(POLICY_TYPE_ID).getTypeDefinition());
        } else {
            return false;
        }

        AbstractTypeDefinition newType = (AbstractTypeDefinition) copyTypeDefintion(type);

        // copy property definition
        for (PropertyDefinition<?> propDef : baseType.getPropertyDefinitions().values()) {
            ((AbstractPropertyDefinition<?>) propDef).setIsInherited(Boolean.TRUE);
            newType.addPropertyDefinition(propDef);
        }

        // add it
        addTypeInternal(newType);
        return true;
    }

    /**
     * Internal method which adds a new type, without adding any property definitions automatically.<p>
     *
     * @param type the type to add
     */
    private void addTypeInternal(AbstractTypeDefinition type) {

        if (type == null) {
            return;
        }

        if (m_types.containsKey(type.getId())) {
            // can't overwrite a type
            return;
        }

        TypeDefinitionContainerImpl tc = new TypeDefinitionContainerImpl();
        tc.setTypeDefinition(type);

        // add to parent
        if (type.getParentTypeId() != null) {
            TypeDefinitionContainerImpl tdc = m_types.get(type.getParentTypeId());
            if (tdc != null) {
                if (tdc.getChildren() == null) {
                    tdc.setChildren(new ArrayList<TypeDefinitionContainer>());
                }
                tdc.getChildren().add(tc);
            }
        }

        m_types.put(type.getId(), tc);
        m_typeList.add(tc);
    }

    /**
     * Creates a CMIS relationship subtype for a given OpenCms relation type.<p>
     *
     * @param relType the OpenCms relation type
     */
    private void createRelationshipType(CmsRelationType relType) {

        // relationship types
        RelationshipTypeDefinitionImpl relationshipType = new RelationshipTypeDefinitionImpl();
        relationshipType.setBaseTypeId(BaseTypeId.CMIS_RELATIONSHIP);
        relationshipType.setParentTypeId(RELATIONSHIP_TYPE_ID);
        relationshipType.setIsControllableAcl(Boolean.FALSE);
        relationshipType.setIsControllablePolicy(Boolean.FALSE);
        relationshipType.setIsCreatable(Boolean.valueOf(!relType.isDefinedInContent()));
        relationshipType.setDescription(relType.getName());
        relationshipType.setDisplayName(relType.getName());
        relationshipType.setIsFileable(Boolean.FALSE);
        relationshipType.setIsIncludedInSupertypeQuery(Boolean.TRUE);
        relationshipType.setLocalName(relType.getName());
        relationshipType.setLocalNamespace(NAMESPACE);
        relationshipType.setIsQueryable(Boolean.FALSE);
        String id = "opencms:" + relType.getName().toUpperCase();
        relationshipType.setQueryName(id);
        relationshipType.setId(id);
        List<String> typeList = new ArrayList<String>();
        typeList.add("cmis:document");
        typeList.add("cmis:folder");
        relationshipType.setAllowedSourceTypes(typeList);
        relationshipType.setAllowedTargetTypes(typeList);
        addType(relationshipType);
    }

    /**
     * Collects the descendants of a type.<p>
     *
     * @param depth the depth up to which the descendants should be collected
     * @param tc the parent type
     * @param includePropertyDefinitions true if the property definitions should be included
     *
     * @return the descendants of the type
     */
    private TypeDefinitionContainer getTypeDescendants(int depth, TypeDefinitionContainer tc,
            boolean includePropertyDefinitions) {

        TypeDefinitionContainerImpl result = new TypeDefinitionContainerImpl();

        TypeDefinition type = copyTypeDefintion(tc.getTypeDefinition());
        if (!includePropertyDefinitions) {
            type.getPropertyDefinitions().clear();
        }

        result.setTypeDefinition(type);

        if (depth != 0) {
            if (tc.getChildren() != null) {
                result.setChildren(new ArrayList<TypeDefinitionContainer>());
                for (TypeDefinitionContainer tdc : tc.getChildren()) {
                    result.getChildren()
                            .add(getTypeDescendants(depth < 0 ? -1 : depth - 1, tdc, includePropertyDefinitions));
                }
            }
        }

        return result;
    }

    /**
     * Refreshes the internal data if the last update was longer ago than the udpate interval.<p>
     */
    private synchronized void refresh() {

        try {
            long now = System.currentTimeMillis();
            if ((now - m_lastUpdate) > UPDATE_INTERVAL) {
                setup();
            }
        } catch (CmsException e) {
            LOG.error(e.getLocalizedMessage(), e);
        }
    }

}