com.openkm.cmis.CmisRepository.java Source code

Java tutorial

Introduction

Here is the source code for com.openkm.cmis.CmisRepository.java

Source

/**
 * OpenKM, Open Document Management System (http://www.openkm.com)
 * Copyright (c) 2006-2015 Paco Avila & Josep Llort
 * 
 * No bytes were intentionally harmed during the development of this application.
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

package com.openkm.cmis;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.GregorianCalendar;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;

import org.apache.chemistry.opencmis.commons.PropertyIds;
import org.apache.chemistry.opencmis.commons.data.Ace;
import org.apache.chemistry.opencmis.commons.data.Acl;
import org.apache.chemistry.opencmis.commons.data.AllowableActions;
import org.apache.chemistry.opencmis.commons.data.ContentStream;
import org.apache.chemistry.opencmis.commons.data.FailedToDeleteData;
import org.apache.chemistry.opencmis.commons.data.ObjectData;
import org.apache.chemistry.opencmis.commons.data.ObjectInFolderContainer;
import org.apache.chemistry.opencmis.commons.data.ObjectInFolderData;
import org.apache.chemistry.opencmis.commons.data.ObjectInFolderList;
import org.apache.chemistry.opencmis.commons.data.ObjectParentData;
import org.apache.chemistry.opencmis.commons.data.PermissionMapping;
import org.apache.chemistry.opencmis.commons.data.Properties;
import org.apache.chemistry.opencmis.commons.data.PropertyData;
import org.apache.chemistry.opencmis.commons.data.PropertyDateTime;
import org.apache.chemistry.opencmis.commons.data.PropertyId;
import org.apache.chemistry.opencmis.commons.data.PropertyString;
import org.apache.chemistry.opencmis.commons.data.RepositoryInfo;
import org.apache.chemistry.opencmis.commons.definitions.PermissionDefinition;
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.AclPropagation;
import org.apache.chemistry.opencmis.commons.enums.Action;
import org.apache.chemistry.opencmis.commons.enums.BaseTypeId;
import org.apache.chemistry.opencmis.commons.enums.CapabilityAcl;
import org.apache.chemistry.opencmis.commons.enums.CapabilityChanges;
import org.apache.chemistry.opencmis.commons.enums.CapabilityContentStreamUpdates;
import org.apache.chemistry.opencmis.commons.enums.CapabilityJoin;
import org.apache.chemistry.opencmis.commons.enums.CapabilityQuery;
import org.apache.chemistry.opencmis.commons.enums.CapabilityRenditions;
import org.apache.chemistry.opencmis.commons.enums.SupportedPermissions;
import org.apache.chemistry.opencmis.commons.enums.Updatability;
import org.apache.chemistry.opencmis.commons.enums.VersioningState;
import org.apache.chemistry.opencmis.commons.exceptions.CmisBaseException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisConstraintException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisNameConstraintViolationException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisPermissionDeniedException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisStorageException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisStreamNotSupportedException;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlEntryImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlListImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlPrincipalDataImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.AclCapabilitiesDataImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.AllowableActionsImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.FailedToDeleteDataImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.NewTypeSettableAttributesImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectDataImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectInFolderContainerImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectInFolderDataImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectInFolderListImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectParentDataImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PermissionDefinitionDataImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PermissionMappingDataImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertiesImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyBooleanImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyDateTimeImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyDecimalImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyHtmlImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIdImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIntegerImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyStringImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyUriImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.RepositoryCapabilitiesImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.RepositoryInfoImpl;
import org.apache.chemistry.opencmis.commons.impl.server.ObjectInfoImpl;
import org.apache.chemistry.opencmis.commons.server.CallContext;
import org.apache.chemistry.opencmis.commons.server.ObjectInfoHandler;
import org.apache.chemistry.opencmis.commons.spi.Holder;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.openkm.api.OKMAuth;
import com.openkm.api.OKMDocument;
import com.openkm.api.OKMFolder;
import com.openkm.automation.AutomationException;
import com.openkm.bean.Document;
import com.openkm.bean.Folder;
import com.openkm.bean.Node;
import com.openkm.bean.Permission;
import com.openkm.bean.Repository;
import com.openkm.core.AccessDeniedException;
import com.openkm.core.Config;
import com.openkm.core.DatabaseException;
import com.openkm.core.ItemExistsException;
import com.openkm.core.LockException;
import com.openkm.core.PathNotFoundException;
import com.openkm.core.RepositoryException;
import com.openkm.extension.core.ExtensionException;
import com.openkm.module.db.DbDocumentModule;
import com.openkm.util.PathUtils;
import com.openkm.util.WarUtils;

/**
 * CMIS Service Implementation.
 */
public class CmisRepository {
    private static Logger log = LoggerFactory.getLogger(CmisRepository.class);
    private static final String ROOT_ID = "/" + Repository.ROOT;
    private static final String USER_UNKNOWN = "<unknown>";
    private static final String CMIS_READ = "cmis:read";
    private static final String CMIS_WRITE = "cmis:write";
    private static final String CMIS_DELETE = "cmis:delete";
    private static final String CMIS_ALL = "cmis:all";

    /** Repository id */
    private final String repositoryId;

    /** Types */
    private final CmisTypeManager types;

    /** Repository info */
    private final RepositoryInfoImpl repositoryInfo;

    public CmisRepository(String repositoryId, CmisTypeManager types) {
        // check repository id
        if ((repositoryId == null) || (repositoryId.trim().length() == 0)) {
            throw new IllegalArgumentException("Invalid repository id!");
        }

        this.repositoryId = repositoryId;
        this.types = types;

        repositoryInfo = new RepositoryInfoImpl();

        repositoryInfo.setId("default");
        repositoryInfo.setName("OpenKM");
        repositoryInfo.setDescription("OpenKM CMIS Interface");
        repositoryInfo.setCmisVersionSupported("1.0");
        repositoryInfo.setProductName("OpenKM - Knowledge Management");
        repositoryInfo.setProductVersion(WarUtils.getAppVersion().toString());
        repositoryInfo.setVendorName("OpenKM");
        repositoryInfo.setRootFolder(ROOT_ID);
        repositoryInfo.setThinClientUri(Config.APPLICATION_URL);

        RepositoryCapabilitiesImpl capabilities = new RepositoryCapabilitiesImpl();
        capabilities.setCapabilityAcl(CapabilityAcl.DISCOVER);
        capabilities.setAllVersionsSearchable(false);
        capabilities.setCapabilityJoin(CapabilityJoin.NONE);
        capabilities.setSupportsMultifiling(false);
        capabilities.setSupportsUnfiling(false);
        capabilities.setSupportsVersionSpecificFiling(false);
        capabilities.setIsPwcSearchable(false);
        capabilities.setIsPwcUpdatable(false);
        capabilities.setCapabilityQuery(CapabilityQuery.NONE);
        capabilities.setCapabilityChanges(CapabilityChanges.NONE);
        capabilities.setCapabilityContentStreamUpdates(CapabilityContentStreamUpdates.ANYTIME);
        capabilities.setSupportsGetDescendants(true);
        capabilities.setSupportsGetFolderTree(true);
        capabilities.setCapabilityRendition(CapabilityRenditions.NONE);

        NewTypeSettableAttributesImpl typeSetAttributes = new NewTypeSettableAttributesImpl();
        typeSetAttributes.setCanSetControllableAcl(false);
        typeSetAttributes.setCanSetControllablePolicy(false);
        typeSetAttributes.setCanSetCreatable(false);
        typeSetAttributes.setCanSetDescription(false);
        typeSetAttributes.setCanSetDisplayName(false);
        typeSetAttributes.setCanSetFileable(false);
        typeSetAttributes.setCanSetFulltextIndexed(false);
        typeSetAttributes.setCanSetId(false);
        typeSetAttributes.setCanSetIncludedInSupertypeQuery(false);
        typeSetAttributes.setCanSetLocalName(false);
        typeSetAttributes.setCanSetLocalNamespace(false);
        typeSetAttributes.setCanSetQueryable(false);
        typeSetAttributes.setCanSetQueryName(false);

        capabilities.setNewTypeSettableAttributes(typeSetAttributes);
        repositoryInfo.setCapabilities(capabilities);

        AclCapabilitiesDataImpl aclCapability = new AclCapabilitiesDataImpl();
        aclCapability.setSupportedPermissions(SupportedPermissions.BASIC);
        aclCapability.setAclPropagation(AclPropagation.OBJECTONLY);

        // permissions
        List<PermissionDefinition> permissions = new ArrayList<PermissionDefinition>();
        permissions.add(createPermission(CMIS_READ, "Read"));
        permissions.add(createPermission(CMIS_WRITE, "Write"));
        permissions.add(createPermission(CMIS_DELETE, "Delete"));
        permissions.add(createPermission(CMIS_ALL, "All"));
        aclCapability.setPermissionDefinitionData(permissions);

        // mapping
        List<PermissionMapping> list = new ArrayList<PermissionMapping>();
        list.add(createMapping(PermissionMapping.CAN_CREATE_DOCUMENT_FOLDER, CMIS_WRITE));
        list.add(createMapping(PermissionMapping.CAN_CREATE_FOLDER_FOLDER, CMIS_WRITE));
        list.add(createMapping(PermissionMapping.CAN_DELETE_CONTENT_DOCUMENT, CMIS_WRITE));
        list.add(createMapping(PermissionMapping.CAN_DELETE_OBJECT, CMIS_DELETE));
        list.add(createMapping(PermissionMapping.CAN_DELETE_TREE_FOLDER, CMIS_DELETE));
        list.add(createMapping(PermissionMapping.CAN_GET_ACL_OBJECT, CMIS_READ));
        list.add(createMapping(PermissionMapping.CAN_GET_ALL_VERSIONS_VERSION_SERIES, CMIS_READ));
        list.add(createMapping(PermissionMapping.CAN_GET_CHILDREN_FOLDER, CMIS_READ));
        list.add(createMapping(PermissionMapping.CAN_GET_DESCENDENTS_FOLDER, CMIS_READ));
        list.add(createMapping(PermissionMapping.CAN_GET_FOLDER_PARENT_OBJECT, CMIS_READ));
        list.add(createMapping(PermissionMapping.CAN_GET_PARENTS_FOLDER, CMIS_READ));
        list.add(createMapping(PermissionMapping.CAN_GET_PROPERTIES_OBJECT, CMIS_READ));
        list.add(createMapping(PermissionMapping.CAN_MOVE_OBJECT, CMIS_WRITE));
        list.add(createMapping(PermissionMapping.CAN_MOVE_SOURCE, CMIS_WRITE));
        list.add(createMapping(PermissionMapping.CAN_MOVE_TARGET, CMIS_WRITE));
        list.add(createMapping(PermissionMapping.CAN_SET_CONTENT_DOCUMENT, CMIS_WRITE));
        list.add(createMapping(PermissionMapping.CAN_UPDATE_PROPERTIES_OBJECT, CMIS_WRITE));
        list.add(createMapping(PermissionMapping.CAN_VIEW_CONTENT_OBJECT, CMIS_READ));

        Map<String, PermissionMapping> map = new LinkedHashMap<String, PermissionMapping>();
        for (PermissionMapping pm : list) {
            map.put(pm.getKey(), pm);
        }

        aclCapability.setPermissionMappingData(map);
        repositoryInfo.setAclCapabilities(aclCapability);
    }

    private static PermissionDefinition createPermission(String permission, String description) {
        PermissionDefinitionDataImpl pd = new PermissionDefinitionDataImpl();
        pd.setId(permission);
        pd.setDescription(description);
        return pd;
    }

    private static PermissionMapping createMapping(String key, String permission) {
        PermissionMappingDataImpl pm = new PermissionMappingDataImpl();
        pm.setKey(key);
        pm.setPermissions(Collections.singletonList(permission));
        return pm;
    }

    // --- the public stuff ---

    /**
     * Returns the repository id.
     */
    public String getRepositoryId() {
        return repositoryId;
    }

    /**
     * CMIS getRepositoryInfo.
     */
    public RepositoryInfo getRepositoryInfo(CallContext context) {
        return repositoryInfo;
    }

    /**
     * CMIS getTypesChildren.
     */
    public TypeDefinitionList getTypesChildren(CallContext context, String typeId,
            boolean includePropertyDefinitions, BigInteger maxItems, BigInteger skipCount) {
        log.debug("getTypesChildren");
        return types.getTypesChildren(context, typeId, includePropertyDefinitions, maxItems, skipCount);
    }

    /**
     * CMIS getTypeDefinition.
     */
    public TypeDefinition getTypeDefinition(CallContext context, String typeId) {
        log.debug("getTypeDefinition({})", typeId);
        return types.getTypeDefinition(context, typeId);
    }

    /**
     * CMIS getTypesDescendants.
     */
    public List<TypeDefinitionContainer> getTypesDescendants(CallContext context, String typeId, BigInteger depth,
            Boolean includePropertyDefinitions) {
        log.debug("getTypesDescendants");
        return types.getTypesDescendants(context, typeId, depth, includePropertyDefinitions);
    }

    /**
     * Create* dispatch for AtomPub.
     */
    public ObjectData create(CallContext context, Properties properties, String folderId,
            ContentStream contentStream, VersioningState versioningState, ObjectInfoHandler objectInfos) {
        log.debug("create({}, {})", properties, folderId);

        String typeId = getTypeId(properties);
        TypeDefinition type = types.getType(typeId);
        if (type == null) {
            throw new CmisObjectNotFoundException("Type '" + typeId + "' is unknown!");
        }

        String objectId = null;
        if (type.getBaseTypeId() == BaseTypeId.CMIS_DOCUMENT) {
            objectId = createDocument(context, properties, folderId, contentStream, versioningState);
        } else if (type.getBaseTypeId() == BaseTypeId.CMIS_FOLDER) {
            objectId = createFolder(context, properties, folderId);
        } else {
            throw new CmisObjectNotFoundException("Cannot create object of type '" + typeId + "'!");
        }

        return compileObjectType(context, getNode(objectId), null, false, false, objectInfos);
    }

    /**
     * CMIS createDocument.
     */
    public String createDocument(CallContext context, Properties properties, String folderId,
            ContentStream contentStream, VersioningState versioningState) {
        log.debug("createDocument({}, {})", properties, folderId);

        // check properties
        if ((properties == null) || (properties.getProperties() == null)) {
            throw new CmisInvalidArgumentException("Properties must be set!");
        }

        // check versioning state
        // if (VersioningState.NONE != versioningState) {
        // throw new CmisConstraintException("Versioning not supported!");
        // }

        // check type
        String typeId = getTypeId(properties);
        TypeDefinition type = types.getType(typeId);
        if (type == null) {
            throw new CmisObjectNotFoundException("Type '" + typeId + "' is unknown!");
        }

        // compile the properties
        Properties props = compileProperties(typeId, context.getUsername(),
                millisToCalendar(System.currentTimeMillis()), context.getUsername(), properties);

        // check the name
        String name = getStringProperty(properties, PropertyIds.NAME);
        if (!isValidName(name)) {
            throw new CmisNameConstraintViolationException("Name is not valid!");
        }

        try {
            // get parent Folder
            if (!OKMFolder.getInstance().isValid(null, folderId)) {
                throw new CmisObjectNotFoundException("Parent is not a folder!");
            }

            // create the file
            Document newDoc = OKMDocument.getInstance().createSimple(null, folderId + "/" + name,
                    contentStream.getStream());

            // write properties
            writePropertiesFile(newDoc, props);

            return newDoc.getPath();
        } catch (PathNotFoundException e) {
            throw new CmisObjectNotFoundException("Could not create document!");
        } catch (RepositoryException e) {
            throw new CmisStorageException("Could not create document!");
        } catch (DatabaseException e) {
            throw new CmisStorageException("Could not create document!");
        } catch (ItemExistsException e) {
            throw new CmisNameConstraintViolationException("Document already exists");
        } catch (AccessDeniedException e) {
            throw new CmisPermissionDeniedException("No write permission!");
        } catch (ExtensionException e) {
            throw new CmisStorageException("Could not create document!");
        } catch (AutomationException e) {
            throw new CmisStorageException("Could not create document!");
        } catch (IOException e) {
            throw new CmisStorageException("Could not create document: " + e.getMessage());
        } catch (Exception e) {
            throw new CmisStorageException("Could not create document!");
        }
    }

    /**
     * CMIS createDocumentFromSource.
     */
    public String createDocumentFromSource(CallContext context, String sourceId, Properties properties,
            String folderId, VersioningState versioningState) {
        InputStream is = null;

        try {
            // get parent Folder
            if (!OKMFolder.getInstance().isValid(null, folderId)) {
                throw new CmisObjectNotFoundException("Parent is not a folder!");
            }

            // get source Document
            if (!OKMDocument.getInstance().isValid(null, sourceId)) {
                throw new CmisObjectNotFoundException("Source is not a document!");
            }

            // file name
            Document srcDoc = OKMDocument.getInstance().getProperties(null, sourceId);
            String name = PathUtils.getName(srcDoc.getPath());

            // get properties
            PropertiesImpl sourceProperties = new PropertiesImpl();
            readCustomProperties(srcDoc, sourceProperties, null, new ObjectInfoImpl());

            // get the type id
            String typeId = getIdProperty(sourceProperties, PropertyIds.OBJECT_TYPE_ID);
            if (typeId == null) {
                typeId = CmisTypeManager.DOCUMENT_TYPE_ID;
            }

            // copy properties
            PropertiesImpl newProperties = new PropertiesImpl();
            for (PropertyData<?> prop : sourceProperties.getProperties().values()) {
                if ((prop.getId().equals(PropertyIds.OBJECT_TYPE_ID))
                        || (prop.getId().equals(PropertyIds.CREATED_BY))
                        || (prop.getId().equals(PropertyIds.CREATION_DATE))
                        || (prop.getId().equals(PropertyIds.LAST_MODIFIED_BY))) {
                    continue;
                }

                newProperties.addProperty(prop);
            }

            // replace properties
            if (properties != null) {
                // find new name
                String newName = getStringProperty(properties, PropertyIds.NAME);
                if (newName != null) {
                    if (!isValidName(newName)) {
                        throw new CmisNameConstraintViolationException("Name is not valid!");
                    }
                    name = newName;
                }

                // get the property definitions
                TypeDefinition type = types.getType(typeId);
                if (type == null) {
                    throw new CmisObjectNotFoundException("Type '" + typeId + "' is unknown!");
                }

                // replace with new values
                for (PropertyData<?> prop : properties.getProperties().values()) {
                    PropertyDefinition<?> propType = type.getPropertyDefinitions().get(prop.getId());

                    // do we know that property?
                    if (propType == null) {
                        throw new CmisConstraintException("Property '" + prop.getId() + "' is unknown!");
                    }

                    // can it be set?
                    if ((propType.getUpdatability() != Updatability.READWRITE)) {
                        throw new CmisConstraintException("Property '" + prop.getId() + "' cannot be updated!");
                    }

                    // empty properties are invalid
                    if (isEmptyProperty(prop)) {
                        throw new CmisConstraintException("Property '" + prop.getId() + "' must not be empty!");
                    }

                    newProperties.addProperty(prop);
                }
            }

            addPropertyId(newProperties, typeId, null, PropertyIds.OBJECT_TYPE_ID, typeId);
            addPropertyString(newProperties, typeId, null, PropertyIds.CREATED_BY, context.getUsername());
            addPropertyDateTime(newProperties, typeId, null, PropertyIds.CREATION_DATE,
                    millisToCalendar(System.currentTimeMillis()));
            addPropertyString(newProperties, typeId, null, PropertyIds.LAST_MODIFIED_BY, context.getUsername());

            // create the Document
            is = OKMDocument.getInstance().getContent(null, srcDoc.getUuid(), false);
            Document newDoc = OKMDocument.getInstance().createSimple(null, folderId + "/" + name, is);

            // write properties
            writePropertiesFile(newDoc, newProperties);

            return newDoc.getPath();
        } catch (PathNotFoundException e) {
            throw new CmisObjectNotFoundException("Could not create document!");
        } catch (RepositoryException e) {
            throw new CmisStorageException("Could not create document!");
        } catch (DatabaseException e) {
            throw new CmisStorageException("Could not create document!");
        } catch (ItemExistsException e) {
            throw new CmisNameConstraintViolationException("Document already exists");
        } catch (AccessDeniedException e) {
            throw new CmisPermissionDeniedException("No write permission!");
        } catch (ExtensionException e) {
            throw new CmisStorageException("Could not create document!");
        } catch (AutomationException e) {
            throw new CmisStorageException("Could not create document!");
        } catch (IOException e) {
            throw new CmisStorageException("Could not read or write content: " + e.getMessage(), e);
        } catch (Exception e) {
            throw new CmisStorageException("Could not create document!");
        } finally {
            IOUtils.closeQuietly(is);
        }
    }

    /**
     * CMIS createFolder.
     */
    public String createFolder(CallContext context, Properties properties, String folderId) {
        log.debug("createFolder({}, {})", properties, folderId);

        // check properties
        if ((properties == null) || (properties.getProperties() == null)) {
            throw new CmisInvalidArgumentException("Properties must be set!");
        }

        // check type
        String typeId = getTypeId(properties);
        TypeDefinition type = types.getType(typeId);
        if (type == null) {
            throw new CmisObjectNotFoundException("Type '" + typeId + "' is unknown!");
        }

        // compile the properties
        Properties props = compileProperties(typeId, context.getUsername(),
                millisToCalendar(System.currentTimeMillis()), context.getUsername(), properties);

        // check the name
        String name = getStringProperty(properties, PropertyIds.NAME);
        if (!isValidName(name)) {
            throw new CmisNameConstraintViolationException("Name is not valid.");
        }

        // get parent File
        try {
            if (!OKMFolder.getInstance().isValid(null, folderId)) {
                throw new CmisObjectNotFoundException("Parent is not a folder!");
            }

            // create the folder
            Folder newFld = OKMFolder.getInstance().createSimple(null, folderId + "/" + name);

            // write properties
            writePropertiesFile(newFld, props);

            return newFld.getPath();
        } catch (PathNotFoundException e) {
            throw new CmisObjectNotFoundException("Could not create folder!");
        } catch (RepositoryException e) {
            throw new CmisStorageException("Could not create folder", e);
        } catch (DatabaseException e) {
            throw new CmisStorageException("Could not create folder", e);
        } catch (ItemExistsException e) {
            throw new CmisNameConstraintViolationException("Folder already exists", e);
        } catch (AccessDeniedException e) {
            throw new CmisPermissionDeniedException("No write permission", e);
        } catch (ExtensionException e) {
            throw new CmisStorageException("Could not create folder", e);
        } catch (AutomationException e) {
            throw new CmisStorageException("Could not create folder", e);
        }
    }

    /**
     * CMIS moveObject.
     */
    public ObjectData moveObject(CallContext context, Holder<String> objectId, String targetFolderId,
            ObjectInfoHandler objectInfos) {
        log.debug("moveObject({}, {})", objectId, targetFolderId);
        Node node = null;

        if (objectId == null) {
            throw new CmisInvalidArgumentException("Id is not valid!");
        }

        String newPath = targetFolderId + "/" + PathUtils.getName(objectId.getValue());

        try {
            if (OKMFolder.getInstance().isValid(null, objectId.getValue())) {
                OKMFolder.getInstance().move(null, objectId.getValue(), targetFolderId);
                node = OKMFolder.getInstance().getProperties(null, newPath);
            } else if (OKMDocument.getInstance().isValid(null, objectId.getValue())) {
                OKMDocument.getInstance().move(null, objectId.getValue(), targetFolderId);
                node = OKMDocument.getInstance().getProperties(null, newPath);
            } else {
                throw new CmisObjectNotFoundException(objectId.getValue());
            }

            objectId.setValue(newPath);
            return compileObjectType(context, node, null, false, false, objectInfos);
        } catch (PathNotFoundException e) {
            throw new CmisObjectNotFoundException("Could not move node", e);
        } catch (RepositoryException e) {
            throw new CmisStorageException("Could not move node", e);
        } catch (DatabaseException e) {
            throw new CmisStorageException("Could not move node", e);
        } catch (ItemExistsException e) {
            throw new CmisNameConstraintViolationException("Node already exists", e);
        } catch (AccessDeniedException e) {
            throw new CmisPermissionDeniedException("No write permission", e);
        } catch (ExtensionException e) {
            throw new CmisStorageException("Could not move node", e);
        } catch (AutomationException e) {
            throw new CmisStorageException("Could not move node", e);
        } catch (LockException e) {
            throw new CmisStorageException("Could not move node", e);
        }
    }

    /**
     * CMIS setContentStream and deleteContentStream.
     */
    public void setContentStream(CallContext context, Holder<String> objectId, Boolean overwriteFlag,
            ContentStream contentStream) {
        log.debug("setContentStream({}, {})", objectId, overwriteFlag);

        if (objectId == null) {
            throw new CmisInvalidArgumentException("Id is not valid!");
        }

        // check overwrite
        // boolean owf = (overwriteFlag == null ? true : overwriteFlag.booleanValue());
        // if (!owf && file.length() > 0) {
        // throw new CmisContentAlreadyExistsException("Content already exists!");
        // }

        try {
            if (!OKMDocument.getInstance().isValid(null, objectId.getValue())) {
                throw new CmisStreamNotSupportedException("Not a document");
            }

            if (!OKMDocument.getInstance().isCheckedOut(null, objectId.getValue())) {
                OKMDocument.getInstance().checkout(null, objectId.getValue());
            }

            new DbDocumentModule().checkin(null, objectId.getValue(), contentStream.getStream(),
                    contentStream.getLength(), "CMIS Client", null);
        } catch (Exception e) {
            throw new CmisStorageException("Could not write content: " + e.getMessage(), e);
        }
    }

    /**
     * CMIS deleteObject.
     */
    public void deleteObject(CallContext context, String objectId) {
        log.debug("deleteObject({})", objectId);

        try {
            if (OKMFolder.getInstance().isValid(null, objectId)) {
                OKMFolder.getInstance().delete(null, objectId);
            } else if (OKMDocument.getInstance().isValid(null, objectId)) {
                OKMDocument.getInstance().delete(null, objectId);
            } else {
                throw new CmisObjectNotFoundException(objectId);
            }
        } catch (PathNotFoundException e) {
            throw new CmisObjectNotFoundException(objectId, e);
        } catch (AccessDeniedException e) {
            throw new CmisPermissionDeniedException(e.getMessage(), e);
        } catch (RepositoryException e) {
            throw new CmisStorageException("Deletion failed!", e);
        } catch (DatabaseException e) {
            throw new CmisStorageException("Deletion failed!", e);
        } catch (LockException e) {
            throw new CmisStorageException("Deletion failed!", e);
        } catch (ExtensionException e) {
            throw new CmisStorageException("Deletion failed!", e);
        }
    }

    /**
     * CMIS deleteTree.
     */
    public FailedToDeleteData deleteTree(CallContext context, String folderId, Boolean continueOnFailure) {
        log.debug("deleteTree({})", folderId);
        FailedToDeleteDataImpl result = new FailedToDeleteDataImpl();
        result.setIds(new ArrayList<String>());

        try {
            if (OKMFolder.getInstance().isValid(null, folderId)) {
                OKMFolder.getInstance().delete(null, folderId);
            } else {
                throw new CmisObjectNotFoundException(folderId);
            }
        } catch (PathNotFoundException e) {
            throw new CmisObjectNotFoundException(folderId, e);
        } catch (AccessDeniedException e) {
            throw new CmisPermissionDeniedException(e.getMessage(), e);
        } catch (RepositoryException e) {
            throw new CmisStorageException("Deletion failed!", e);
        } catch (DatabaseException e) {
            throw new CmisStorageException("Deletion failed!", e);
        } catch (LockException e) {
            throw new CmisStorageException("Deletion failed!", e);
        }

        return result;
    }

    /**
     * CMIS updateProperties.
     */
    public ObjectData updateProperties(CallContext context, Holder<String> objectId, Properties properties,
            ObjectInfoHandler objectInfos) {
        log.debug("updateProperties({}, {})", objectId, properties);

        if (objectId == null) {
            throw new CmisInvalidArgumentException("Id is not valid!");
        }

        // get the document or folder
        Node node = getNode(objectId.getValue());

        // get and check the new name
        String newName = getStringProperty(properties, PropertyIds.NAME);
        boolean isRename = (newName != null) && (!PathUtils.getName(node.getPath()).equals(newName));
        if (isRename && !isValidName(newName)) {
            throw new CmisNameConstraintViolationException("Name is not valid!");
        }

        // get old properties
        PropertiesImpl oldProperties = new PropertiesImpl();
        readCustomProperties(node, oldProperties, null, new ObjectInfoImpl());

        // get the type id
        String typeId = getIdProperty(oldProperties, PropertyIds.OBJECT_TYPE_ID);
        if (typeId == null) {
            if (node instanceof Folder) {
                typeId = CmisTypeManager.FOLDER_TYPE_ID;
            } else {
                typeId = CmisTypeManager.DOCUMENT_TYPE_ID;
            }
        }

        // get the creator
        String creator = getStringProperty(oldProperties, PropertyIds.CREATED_BY);
        if (creator == null) {
            creator = context.getUsername();
        }

        // get creation date
        GregorianCalendar creationDate = getDateTimeProperty(oldProperties, PropertyIds.CREATION_DATE);
        if (creationDate == null) {
            creationDate = millisToCalendar(node.getCreated().getTimeInMillis());
        }

        // compile the properties
        Properties props = updateProperties(typeId, creator, creationDate, context.getUsername(), oldProperties,
                properties);

        // write properties
        writePropertiesFile(node, props);

        // rename file or folder if necessary
        Node newNode = node;
        if (isRename) {
            try {
                if (node instanceof Folder) {
                    newNode = OKMFolder.getInstance().rename(null, node.getUuid(), newName);
                } else {
                    newNode = OKMDocument.getInstance().rename(null, node.getUuid(), newName);
                }
            } catch (PathNotFoundException e) {
                throw new CmisObjectNotFoundException(e.getMessage(), e);
            } catch (AccessDeniedException e) {
                throw new CmisPermissionDeniedException(e.getMessage(), e);
            } catch (ItemExistsException e) {
                throw new CmisNameConstraintViolationException(e.getMessage(), e);
            } catch (RepositoryException e) {
                throw new CmisStorageException("Update perties failed!", e);
            } catch (DatabaseException e) {
                throw new CmisStorageException("Update perties failed!", e);
            } catch (LockException e) {
                throw new CmisStorageException("Update perties failed!", e);
            } catch (ExtensionException e) {
                throw new CmisStorageException("Update perties failed!", e);
            }

            // set new id
            objectId.setValue(newNode.getPath());
        }

        return compileObjectType(context, newNode, null, false, false, objectInfos);
    }

    /**
     * CMIS getObject.
     */
    public ObjectData getObject(CallContext context, String objectId, String versionServicesId, String filter,
            Boolean includeAllowableActions, Boolean includeAcl, ObjectInfoHandler objectInfos) {
        log.debug("getObject({}, {}, {})", new Object[] { objectId, versionServicesId, filter });

        // check id
        if ((objectId == null) && (versionServicesId == null)) {
            throw new CmisInvalidArgumentException("Object Id must be set.");
        }

        if (objectId == null) {
            // this works only because there are no versions in a file system
            // and the object id and version series id are the same
            objectId = versionServicesId;
        }

        // get the document or folder
        Node node = getNode(objectId);

        // set defaults if values not set
        boolean iaa = (includeAllowableActions == null ? false : includeAllowableActions.booleanValue());
        boolean iacl = (includeAcl == null ? false : includeAcl.booleanValue());

        // split filter
        Set<String> filterCollection = splitFilter(filter);

        // gather properties
        return compileObjectType(context, node, filterCollection, iaa, iacl, objectInfos);
    }

    /**
     * CMIS getAllowableActions.
     */
    public AllowableActions getAllowableActions(CallContext context, String objectId) {
        log.debug("getAllowableActions({})", objectId);

        // get the document or folder
        Node node = getNode(objectId);

        return compileAllowableActions(node);
    }

    /**
     * CMIS getACL.
     */
    public Acl getAcl(CallContext context, String objectId) {
        log.debug("getAcl({})", objectId);

        // get the document or folder
        Node node = getNode(objectId);

        return compileAcl(node);
    }

    /**
     * CMIS getContentStream.
     */
    public ContentStream getContentStream(CallContext context, String objectId, BigInteger offset,
            BigInteger length) {
        log.debug("getContentStream({}, {}, {})", new Object[] { objectId, offset, length });

        if ((offset != null) || (length != null)) {
            throw new CmisInvalidArgumentException("Offset and Length are not supported!");
        }

        try {
            // get the document
            if (!OKMDocument.getInstance().isValid(null, objectId)) {
                throw new CmisStreamNotSupportedException("Not a file!");
            }

            Document doc = OKMDocument.getInstance().getProperties(null, objectId);
            InputStream is = OKMDocument.getInstance().getContent(null, objectId, false);

            // compile data
            ContentStreamImpl result = new ContentStreamImpl();
            result.setFileName(PathUtils.getName(doc.getPath()));
            result.setLength(BigInteger.valueOf(doc.getActualVersion().getSize()));
            result.setMimeType(doc.getMimeType());
            result.setStream(is);

            return result;
        } catch (PathNotFoundException e) {
            throw new CmisObjectNotFoundException(e.getMessage(), e);
        } catch (AccessDeniedException e) {
            throw new CmisPermissionDeniedException("No read permission!");
        } catch (RepositoryException e) {
            throw new CmisStorageException(e.getMessage(), e);
        } catch (DatabaseException e) {
            throw new CmisStorageException(e.getMessage(), e);
        } catch (IOException e) {
            throw new CmisStorageException(e.getMessage(), e);
        }
    }

    /**
     * CMIS getChildren.
     */
    public ObjectInFolderList getChildren(CallContext context, String folderId, String filter,
            Boolean includeAllowableActions, Boolean includePathSegment, BigInteger maxItems, BigInteger skipCount,
            ObjectInfoHandler objectInfos) {
        log.debug("getChildren({})", folderId);

        // split filter
        Set<String> filterCollection = splitFilter(filter);

        // set defaults if values not set
        boolean iaa = (includeAllowableActions == null ? false : includeAllowableActions.booleanValue());
        boolean ips = (includePathSegment == null ? false : includePathSegment.booleanValue());

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

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

        try {
            if (!OKMFolder.getInstance().isValid(null, folderId)) {
                throw new CmisObjectNotFoundException("Not a folder!");
            }

            // get the folder
            Folder fld = OKMFolder.getInstance().getProperties(null, folderId);

            // set object info of the the folder
            if (context.isObjectInfoRequired()) {
                compileObjectType(context, fld, null, false, false, objectInfos);
            }

            // prepare result
            ObjectInFolderListImpl result = new ObjectInFolderListImpl();
            result.setObjects(new ArrayList<ObjectInFolderData>());
            result.setHasMoreItems(false);
            int count = 0;

            // iterate through children (folders)
            for (Folder child : OKMFolder.getInstance().getChildren(null, fld.getPath())) {
                count++;

                if (skip > 0) {
                    skip--;
                    continue;
                }

                if (result.getObjects().size() >= max) {
                    result.setHasMoreItems(true);
                    continue;
                }

                // build and add child object
                ObjectInFolderDataImpl objectInFolder = new ObjectInFolderDataImpl();
                objectInFolder
                        .setObject(compileObjectType(context, child, filterCollection, iaa, false, objectInfos));

                if (ips) {
                    objectInFolder.setPathSegment(PathUtils.getName(child.getPath()));
                }

                result.getObjects().add(objectInFolder);
            }

            // iterate through children (documents)
            for (Document child : OKMDocument.getInstance().getChildren(null, fld.getPath())) {
                count++;

                if (skip > 0) {
                    skip--;
                    continue;
                }

                if (result.getObjects().size() >= max) {
                    result.setHasMoreItems(true);
                    continue;
                }

                // build and add child object
                ObjectInFolderDataImpl objectInFolder = new ObjectInFolderDataImpl();
                objectInFolder
                        .setObject(compileObjectType(context, child, filterCollection, iaa, false, objectInfos));

                if (ips) {
                    objectInFolder.setPathSegment(PathUtils.getName(child.getPath()));
                }

                result.getObjects().add(objectInFolder);
            }

            result.setNumItems(BigInteger.valueOf(count));
            return result;
        } catch (PathNotFoundException e) {
            throw new CmisObjectNotFoundException(e.getMessage(), e);
        } catch (RepositoryException e) {
            throw new CmisStorageException(e.getMessage(), e);
        } catch (DatabaseException e) {
            throw new CmisStorageException(e.getMessage(), e);
        }
    }

    /**
     * CMIS getDescendants.
     */
    public List<ObjectInFolderContainer> getDescendants(CallContext context, String folderId, BigInteger depth,
            String filter, Boolean includeAllowableActions, Boolean includePathSegment,
            ObjectInfoHandler objectInfos, boolean foldersOnly) {
        log.debug("getDescendants or getFolderTree");

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

        // split filter
        Set<String> filterCollection = splitFilter(filter);

        // set defaults if values not set
        boolean iaa = (includeAllowableActions == null ? false : includeAllowableActions.booleanValue());
        boolean ips = (includePathSegment == null ? false : includePathSegment.booleanValue());

        try {
            if (!OKMFolder.getInstance().isValid(null, folderId)) {
                throw new CmisObjectNotFoundException("Not a folder!");
            }

            // get the folder
            Folder fld = OKMFolder.getInstance().getProperties(null, folderId);

            // set object info of the the folder
            if (context.isObjectInfoRequired()) {
                compileObjectType(context, fld, null, false, false, objectInfos);
            }

            // get the tree
            List<ObjectInFolderContainer> result = new ArrayList<ObjectInFolderContainer>();
            gatherDescendants(context, fld, result, foldersOnly, d, filterCollection, iaa, ips, objectInfos);

            return result;
        } catch (PathNotFoundException e) {
            throw new CmisObjectNotFoundException(e.getMessage(), e);
        } catch (RepositoryException e) {
            throw new CmisStorageException(e.getMessage(), e);
        } catch (DatabaseException e) {
            throw new CmisStorageException(e.getMessage(), e);
        }
    }

    /**
     * CMIS getFolderParent.
     */
    public ObjectData getFolderParent(CallContext context, String folderId, String filter,
            ObjectInfoHandler objectInfos) {
        List<ObjectParentData> parents = getObjectParents(context, folderId, filter, false, false, objectInfos);

        if (parents.size() == 0) {
            throw new CmisInvalidArgumentException("The root folder has no parent!");
        }

        return parents.get(0).getObject();
    }

    /**
     * CMIS getObjectParents.
     */
    public List<ObjectParentData> getObjectParents(CallContext context, String objectId, String filter,
            Boolean includeAllowableActions, Boolean includeRelativePathSegment, ObjectInfoHandler objectInfos) {
        log.debug("getObjectParents({}, {})", objectId, filter);

        // split filter
        Set<String> filterCollection = splitFilter(filter);

        // set defaults if values not set
        boolean iaa = (includeAllowableActions == null ? false : includeAllowableActions.booleanValue());
        boolean irps = (includeRelativePathSegment == null ? false : includeRelativePathSegment.booleanValue());

        // get the document or folder
        Node node = getNode(objectId);

        // don't climb above the root folder
        if (ROOT_ID.equals(node.getPath())) {
            return Collections.emptyList();
        }

        // set object info of the the object
        if (context.isObjectInfoRequired()) {
            compileObjectType(context, node, null, false, false, objectInfos);
        }

        try {
            // get parent folder
            Folder parent = OKMFolder.getInstance().getProperties(null, PathUtils.getParent(node.getPath()));
            ObjectData object = compileObjectType(context, parent, filterCollection, iaa, false, objectInfos);

            ObjectParentDataImpl result = new ObjectParentDataImpl();
            result.setObject(object);
            if (irps) {
                result.setRelativePathSegment(PathUtils.getName(parent.getPath()));
            }

            return Collections.singletonList((ObjectParentData) result);
        } catch (PathNotFoundException e) {
            throw new CmisObjectNotFoundException(e.getMessage(), e);
        } catch (RepositoryException e) {
            throw new CmisStorageException(e.getMessage(), e);
        } catch (DatabaseException e) {
            throw new CmisStorageException(e.getMessage(), e);
        }
    }

    /**
     * CMIS getObjectByPath.
     */
    public ObjectData getObjectByPath(CallContext context, String folderPath, String filter,
            boolean includeAllowableActions, boolean includeACL, ObjectInfoHandler objectInfos) {
        log.debug("getObjectByPath({}, {}, {})", new Object[] { folderPath, filter, includeAllowableActions });

        // split filter
        Set<String> filterCollection = splitFilter(filter);

        // check path
        if (folderPath == null || !folderPath.startsWith("/")) {
            throw new CmisInvalidArgumentException("Invalid folder path!");
        }

        // get the document or folder
        Node node = getNode(folderPath);

        return compileObjectType(context, node, filterCollection, includeAllowableActions, includeACL, objectInfos);
    }

    // --- helper methods ---

    /**
     * Gather the children of a folder.
     */
    private void gatherDescendants(CallContext context, Folder fld, List<ObjectInFolderContainer> list,
            boolean foldersOnly, int depth, Set<String> filter, boolean includeAllowableActions,
            boolean includePathSegments, ObjectInfoHandler objectInfos)
            throws PathNotFoundException, RepositoryException, DatabaseException {
        // iterate through children
        for (Folder child : OKMFolder.getInstance().getChildren(null, fld.getPath())) {
            // add to list
            ObjectInFolderDataImpl objectInFolder = new ObjectInFolderDataImpl();
            objectInFolder.setObject(
                    compileObjectType(context, child, filter, includeAllowableActions, false, objectInfos));

            if (includePathSegments) {
                objectInFolder.setPathSegment(PathUtils.getName(child.getPath()));
            }

            ObjectInFolderContainerImpl container = new ObjectInFolderContainerImpl();
            container.setObject(objectInFolder);

            list.add(container);

            // move to next level
            if (depth != 1) {
                container.setChildren(new ArrayList<ObjectInFolderContainer>());
                gatherDescendants(context, child, container.getChildren(), foldersOnly, depth - 1, filter,
                        includeAllowableActions, includePathSegments, objectInfos);
            }
        }

        // folders only?
        if (!foldersOnly) {
            for (Document child : OKMDocument.getInstance().getChildren(null, fld.getPath())) {
                // add to list
                ObjectInFolderDataImpl objectInFolder = new ObjectInFolderDataImpl();
                objectInFolder.setObject(
                        compileObjectType(context, child, filter, includeAllowableActions, false, objectInfos));

                if (includePathSegments) {
                    objectInFolder.setPathSegment(PathUtils.getName(child.getPath()));
                }

                ObjectInFolderContainerImpl container = new ObjectInFolderContainerImpl();
                container.setObject(objectInFolder);

                list.add(container);
            }
        }
    }

    /**
     * Compiles an object type object from a file or folder.
     */
    private ObjectData compileObjectType(CallContext context, Node node, Set<String> filter,
            boolean includeAllowableActions, boolean includeAcl, ObjectInfoHandler objectInfos) {
        ObjectDataImpl result = new ObjectDataImpl();
        ObjectInfoImpl objectInfo = new ObjectInfoImpl();

        result.setProperties(compileProperties(node, filter, objectInfo));

        if (includeAllowableActions) {
            result.setAllowableActions(compileAllowableActions(node));
        }

        if (includeAcl) {
            result.setAcl(compileAcl(node));
            result.setIsExactAcl(true);
        }

        if (context.isObjectInfoRequired()) {
            objectInfo.setObject(result);
            objectInfos.addObjectInfo(objectInfo);
        }

        return result;
    }

    /**
     * Gathers all base properties of a file or folder.
     */
    private Properties compileProperties(Node node, Set<String> orgfilter, ObjectInfoImpl objectInfo) {
        // copy filter
        Set<String> filter = (orgfilter == null ? null : new HashSet<String>(orgfilter));

        // find base type
        String typeId = null;

        if (node instanceof Folder) {
            typeId = CmisTypeManager.FOLDER_TYPE_ID;
            objectInfo.setBaseType(BaseTypeId.CMIS_FOLDER);
            objectInfo.setTypeId(typeId);
            objectInfo.setContentType(null);
            objectInfo.setFileName(null);
            objectInfo.setHasAcl(true);
            objectInfo.setHasContent(false);
            objectInfo.setVersionSeriesId(null);
            objectInfo.setIsCurrentVersion(true);
            objectInfo.setRelationshipSourceIds(null);
            objectInfo.setRelationshipTargetIds(null);
            objectInfo.setRenditionInfos(null);
            objectInfo.setSupportsDescendants(true);
            objectInfo.setSupportsFolderTree(true);
            objectInfo.setSupportsPolicies(false);
            objectInfo.setSupportsRelationships(false);
            objectInfo.setWorkingCopyId(null);
            objectInfo.setWorkingCopyOriginalId(null);
        } else {
            typeId = CmisTypeManager.DOCUMENT_TYPE_ID;
            objectInfo.setBaseType(BaseTypeId.CMIS_DOCUMENT);
            objectInfo.setTypeId(typeId);
            objectInfo.setHasAcl(true);
            objectInfo.setHasContent(true);
            objectInfo.setHasParent(true);
            objectInfo.setVersionSeriesId(null);
            objectInfo.setIsCurrentVersion(true);
            objectInfo.setRelationshipSourceIds(null);
            objectInfo.setRelationshipTargetIds(null);
            objectInfo.setRenditionInfos(null);
            objectInfo.setSupportsDescendants(false);
            objectInfo.setSupportsFolderTree(false);
            objectInfo.setSupportsPolicies(false);
            objectInfo.setSupportsRelationships(false);
            objectInfo.setWorkingCopyId(null);
            objectInfo.setWorkingCopyOriginalId(null);
        }

        // let's do it
        try {
            PropertiesImpl result = new PropertiesImpl();

            // id
            String id = node.getPath();
            addPropertyId(result, typeId, filter, PropertyIds.OBJECT_ID, id);
            objectInfo.setId(id);

            // name
            String name = PathUtils.getName(node.getPath());
            addPropertyString(result, typeId, filter, PropertyIds.NAME, name);
            objectInfo.setName(name);

            // created and modified by
            addPropertyString(result, typeId, filter, PropertyIds.CREATED_BY, USER_UNKNOWN);
            addPropertyString(result, typeId, filter, PropertyIds.LAST_MODIFIED_BY, USER_UNKNOWN);
            objectInfo.setCreatedBy(USER_UNKNOWN);

            // creation and modification date
            GregorianCalendar lastModified = millisToCalendar(node.getCreated().getTimeInMillis());
            addPropertyDateTime(result, typeId, filter, PropertyIds.CREATION_DATE, lastModified);
            addPropertyDateTime(result, typeId, filter, PropertyIds.LAST_MODIFICATION_DATE, lastModified);
            objectInfo.setCreationDate(lastModified);
            objectInfo.setLastModificationDate(lastModified);

            // change token - always null
            addPropertyString(result, typeId, filter, PropertyIds.CHANGE_TOKEN, null);

            // CMIS 1.1 properties
            addPropertyString(result, typeId, filter, PropertyIds.DESCRIPTION, null);
            addPropertyIdList(result, typeId, filter, PropertyIds.SECONDARY_OBJECT_TYPE_IDS, null);

            // directory or file
            if (node instanceof Folder) {
                // base type and type name
                addPropertyId(result, typeId, filter, PropertyIds.BASE_TYPE_ID, BaseTypeId.CMIS_FOLDER.value());
                addPropertyId(result, typeId, filter, PropertyIds.OBJECT_TYPE_ID, CmisTypeManager.FOLDER_TYPE_ID);
                addPropertyString(result, typeId, filter, PropertyIds.PATH, node.getPath());

                // folder properties
                if (ROOT_ID.equals(node.getPath())) {
                    addPropertyId(result, typeId, filter, PropertyIds.PARENT_ID, null);
                    objectInfo.setHasParent(false);
                } else {
                    addPropertyId(result, typeId, filter, PropertyIds.PARENT_ID,
                            PathUtils.getParent(node.getPath()));
                    objectInfo.setHasParent(true);
                }

                addPropertyIdList(result, typeId, filter, PropertyIds.ALLOWED_CHILD_OBJECT_TYPE_IDS, null);
            } else {
                Document doc = (Document) node;

                // base type and type name
                addPropertyId(result, typeId, filter, PropertyIds.BASE_TYPE_ID, BaseTypeId.CMIS_DOCUMENT.value());
                addPropertyId(result, typeId, filter, PropertyIds.OBJECT_TYPE_ID, CmisTypeManager.DOCUMENT_TYPE_ID);

                // file properties
                addPropertyBoolean(result, typeId, filter, PropertyIds.IS_IMMUTABLE, false);
                addPropertyBoolean(result, typeId, filter, PropertyIds.IS_LATEST_VERSION, true);
                addPropertyBoolean(result, typeId, filter, PropertyIds.IS_MAJOR_VERSION, true);
                addPropertyBoolean(result, typeId, filter, PropertyIds.IS_LATEST_MAJOR_VERSION, true);
                addPropertyString(result, typeId, filter, PropertyIds.VERSION_LABEL,
                        PathUtils.getName(node.getPath()));
                addPropertyId(result, typeId, filter, PropertyIds.VERSION_SERIES_ID, node.getUuid());
                addPropertyBoolean(result, typeId, filter, PropertyIds.IS_VERSION_SERIES_CHECKED_OUT, false);
                addPropertyString(result, typeId, filter, PropertyIds.VERSION_SERIES_CHECKED_OUT_BY, null);
                addPropertyString(result, typeId, filter, PropertyIds.VERSION_SERIES_CHECKED_OUT_ID, null);
                addPropertyString(result, typeId, filter, PropertyIds.CHECKIN_COMMENT, "");

                addPropertyInteger(result, typeId, filter, PropertyIds.CONTENT_STREAM_LENGTH,
                        doc.getActualVersion().getSize());
                addPropertyString(result, typeId, filter, PropertyIds.CONTENT_STREAM_MIME_TYPE, doc.getMimeType());
                addPropertyString(result, typeId, filter, PropertyIds.CONTENT_STREAM_FILE_NAME,
                        PathUtils.getName(doc.getPath()));

                objectInfo.setHasContent(true);
                objectInfo.setContentType(doc.getMimeType());
                objectInfo.setFileName(PathUtils.getName(node.getPath()));

                addPropertyId(result, typeId, filter, PropertyIds.CONTENT_STREAM_ID, null);
            }

            // read custom properties
            // readCustomProperties(node, result, filter, objectInfo);

            if (filter != null) {
                if (!filter.isEmpty()) {
                    log.debug("Unknown filter properties: {}", filter);
                }
            }

            return result;
        } catch (Exception e) {
            if (e instanceof CmisBaseException) {
                throw (CmisBaseException) e;
            }

            throw new CmisRuntimeException(e.getMessage(), e);
        }
    }

    /**
     * Reads and adds properties.
     */
    private void readCustomProperties(Node node, PropertiesImpl properties, Set<String> filter,
            ObjectInfoImpl objectInfo) {
        ObjectData obj = new ObjectDataImpl();

        if (obj.getProperties() != null) {
            // add it to properties
            for (PropertyData<?> prop : obj.getProperties().getPropertyList()) {
                // overwrite object info
                if (prop instanceof PropertyString) {
                    String firstValueStr = ((PropertyString) prop).getFirstValue();
                    if (PropertyIds.NAME.equals(prop.getId())) {
                        objectInfo.setName(firstValueStr);
                    } else if (PropertyIds.OBJECT_TYPE_ID.equals(prop.getId())) {
                        objectInfo.setTypeId(firstValueStr);
                    } else if (PropertyIds.CREATED_BY.equals(prop.getId())) {
                        objectInfo.setCreatedBy(firstValueStr);
                    } else if (PropertyIds.CONTENT_STREAM_MIME_TYPE.equals(prop.getId())) {
                        objectInfo.setContentType(firstValueStr);
                    } else if (PropertyIds.CONTENT_STREAM_FILE_NAME.equals(prop.getId())) {
                        objectInfo.setFileName(firstValueStr);
                    }
                }

                if (prop instanceof PropertyDateTime) {
                    GregorianCalendar firstValueCal = ((PropertyDateTime) prop).getFirstValue();
                    if (PropertyIds.CREATION_DATE.equals(prop.getId())) {
                        objectInfo.setCreationDate(firstValueCal);
                    } else if (PropertyIds.LAST_MODIFICATION_DATE.equals(prop.getId())) {
                        objectInfo.setLastModificationDate(firstValueCal);
                    }
                }

                // check filter
                if (filter != null) {
                    if (!filter.contains(prop.getId())) {
                        continue;
                    } else {
                        filter.remove(prop.getId());
                    }
                }

                // don't overwrite id
                if (PropertyIds.OBJECT_ID.equals(prop.getId())) {
                    continue;
                }

                // don't overwrite base type
                if (PropertyIds.BASE_TYPE_ID.equals(prop.getId())) {
                    continue;
                }

                // add it
                properties.replaceProperty(prop);
            }
        }
    }

    /**
     * Checks and compiles a property set that can be written to disc.
     */
    private Properties compileProperties(String typeId, String creator, GregorianCalendar creationDate,
            String modifier, Properties properties) {
        PropertiesImpl result = new PropertiesImpl();
        Set<String> addedProps = new HashSet<String>();

        if ((properties == null) || (properties.getProperties() == null)) {
            throw new CmisConstraintException("No properties!");
        }

        // get the property definitions
        TypeDefinition type = types.getType(typeId);
        if (type == null) {
            throw new CmisObjectNotFoundException("Type '" + typeId + "' is unknown!");
        }

        // check if all required properties are there
        for (PropertyData<?> prop : properties.getProperties().values()) {
            PropertyDefinition<?> propType = type.getPropertyDefinitions().get(prop.getId());

            // do we know that property?
            if (propType == null) {
                throw new CmisConstraintException("Property '" + prop.getId() + "' is unknown!");
            }

            // can it be set?
            if ((propType.getUpdatability() == Updatability.READONLY)) {
                throw new CmisConstraintException("Property '" + prop.getId() + "' is readonly!");
            }

            // empty properties are invalid
            if (isEmptyProperty(prop)) {
                throw new CmisConstraintException("Property '" + prop.getId() + "' must not be empty!");
            }

            // add it
            result.addProperty(prop);
            addedProps.add(prop.getId());
        }

        // check if required properties are missing
        for (PropertyDefinition<?> propDef : type.getPropertyDefinitions().values()) {
            if (!addedProps.contains(propDef.getId()) && (propDef.getUpdatability() != Updatability.READONLY)) {
                if (!addPropertyDefault(result, propDef) && propDef.isRequired()) {
                    throw new CmisConstraintException("Property '" + propDef.getId() + "' is required!");
                }
            }
        }

        addPropertyId(result, typeId, null, PropertyIds.OBJECT_TYPE_ID, typeId);
        addPropertyString(result, typeId, null, PropertyIds.CREATED_BY, creator);
        addPropertyDateTime(result, typeId, null, PropertyIds.CREATION_DATE, creationDate);
        addPropertyString(result, typeId, null, PropertyIds.LAST_MODIFIED_BY, modifier);

        return result;
    }

    /**
     * Checks and updates a property set that can be written to disc.
     */
    private Properties updateProperties(String typeId, String creator, GregorianCalendar creationDate,
            String modifier, Properties oldProperties, Properties properties) {
        PropertiesImpl result = new PropertiesImpl();

        if (properties == null) {
            throw new CmisConstraintException("No properties!");
        }

        // get the property definitions
        TypeDefinition type = types.getType(typeId);
        if (type == null) {
            throw new CmisObjectNotFoundException("Type '" + typeId + "' is unknown!");
        }

        // copy old properties
        for (PropertyData<?> prop : oldProperties.getProperties().values()) {
            PropertyDefinition<?> propType = type.getPropertyDefinitions().get(prop.getId());

            // do we know that property?
            if (propType == null) {
                throw new CmisConstraintException("Property '" + prop.getId() + "' is unknown!");
            }

            // only add read/write properties
            if ((propType.getUpdatability() != Updatability.READWRITE)) {
                continue;
            }

            result.addProperty(prop);
        }

        // update properties
        for (PropertyData<?> prop : properties.getProperties().values()) {
            PropertyDefinition<?> propType = type.getPropertyDefinitions().get(prop.getId());

            // do we know that property?
            if (propType == null) {
                throw new CmisConstraintException("Property '" + prop.getId() + "' is unknown!");
            }

            // can it be set?
            if ((propType.getUpdatability() == Updatability.READONLY)) {
                throw new CmisConstraintException("Property '" + prop.getId() + "' is readonly!");
            }

            if ((propType.getUpdatability() == Updatability.ONCREATE)) {
                throw new CmisConstraintException("Property '" + prop.getId() + "' can only be set on create!");
            }

            // default or value
            if (isEmptyProperty(prop)) {
                addPropertyDefault(result, propType);
            } else {
                result.addProperty(prop);
            }
        }

        addPropertyId(result, typeId, null, PropertyIds.OBJECT_TYPE_ID, typeId);
        addPropertyString(result, typeId, null, PropertyIds.CREATED_BY, creator);
        addPropertyDateTime(result, typeId, null, PropertyIds.CREATION_DATE, creationDate);
        addPropertyString(result, typeId, null, PropertyIds.LAST_MODIFIED_BY, modifier);

        return result;
    }

    private static boolean isEmptyProperty(PropertyData<?> prop) {
        if ((prop == null) || (prop.getValues() == null)) {
            return true;
        }

        return prop.getValues().isEmpty();
    }

    private void addPropertyId(PropertiesImpl props, String typeId, Set<String> filter, String id, String value) {
        if (!checkAddProperty(props, typeId, filter, id)) {
            return;
        }

        props.addProperty(new PropertyIdImpl(id, value));
    }

    private void addPropertyIdList(PropertiesImpl props, String typeId, Set<String> filter, String id,
            List<String> value) {
        if (!checkAddProperty(props, typeId, filter, id)) {
            return;
        }

        props.addProperty(new PropertyIdImpl(id, value));
    }

    private void addPropertyString(PropertiesImpl props, String typeId, Set<String> filter, String id,
            String value) {
        if (!checkAddProperty(props, typeId, filter, id)) {
            return;
        }

        props.addProperty(new PropertyStringImpl(id, value));
    }

    private void addPropertyInteger(PropertiesImpl props, String typeId, Set<String> filter, String id,
            long value) {
        addPropertyBigInteger(props, typeId, filter, id, BigInteger.valueOf(value));
    }

    private void addPropertyBigInteger(PropertiesImpl props, String typeId, Set<String> filter, String id,
            BigInteger value) {
        if (!checkAddProperty(props, typeId, filter, id)) {
            return;
        }

        props.addProperty(new PropertyIntegerImpl(id, value));
    }

    private void addPropertyBoolean(PropertiesImpl props, String typeId, Set<String> filter, String id,
            boolean value) {
        if (!checkAddProperty(props, typeId, filter, id)) {
            return;
        }

        props.addProperty(new PropertyBooleanImpl(id, value));
    }

    private void addPropertyDateTime(PropertiesImpl props, String typeId, Set<String> filter, String id,
            GregorianCalendar value) {
        if (!checkAddProperty(props, typeId, filter, id)) {
            return;
        }

        props.addProperty(new PropertyDateTimeImpl(id, value));
    }

    private boolean checkAddProperty(Properties properties, String typeId, Set<String> filter, String id) {
        if ((properties == null) || (properties.getProperties() == null)) {
            throw new IllegalArgumentException("Properties must not be null!");
        }

        if (id == null) {
            throw new IllegalArgumentException("Id must not be null!");
        }

        TypeDefinition type = types.getType(typeId);
        if (type == null) {
            throw new IllegalArgumentException("Unknown type: " + typeId);
        }
        if (!type.getPropertyDefinitions().containsKey(id)) {
            throw new IllegalArgumentException("Unknown property: " + id);
        }

        String queryName = type.getPropertyDefinitions().get(id).getQueryName();

        if ((queryName != null) && (filter != null)) {
            if (!filter.contains(queryName)) {
                return false;
            } else {
                filter.remove(queryName);
            }
        }

        return true;
    }

    /**
     * Adds the default value of property if defined.
     */
    @SuppressWarnings("unchecked")
    private static boolean addPropertyDefault(PropertiesImpl props, PropertyDefinition<?> propDef) {
        if ((props == null) || (props.getProperties() == null)) {
            throw new IllegalArgumentException("Props must not be null!");
        }

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

        List<?> defaultValue = propDef.getDefaultValue();
        if ((defaultValue != null) && (!defaultValue.isEmpty())) {
            switch (propDef.getPropertyType()) {
            case BOOLEAN:
                props.addProperty(new PropertyBooleanImpl(propDef.getId(), (List<Boolean>) defaultValue));
                break;
            case DATETIME:
                props.addProperty(
                        new PropertyDateTimeImpl(propDef.getId(), (List<GregorianCalendar>) defaultValue));
                break;
            case DECIMAL:
                props.addProperty(new PropertyDecimalImpl(propDef.getId(), (List<BigDecimal>) defaultValue));
                break;
            case HTML:
                props.addProperty(new PropertyHtmlImpl(propDef.getId(), (List<String>) defaultValue));
                break;
            case ID:
                props.addProperty(new PropertyIdImpl(propDef.getId(), (List<String>) defaultValue));
                break;
            case INTEGER:
                props.addProperty(new PropertyIntegerImpl(propDef.getId(), (List<BigInteger>) defaultValue));
                break;
            case STRING:
                props.addProperty(new PropertyStringImpl(propDef.getId(), (List<String>) defaultValue));
                break;
            case URI:
                props.addProperty(new PropertyUriImpl(propDef.getId(), (List<String>) defaultValue));
                break;
            default:
                throw new RuntimeException("Unknown datatype! Spec change?");
            }

            return true;
        }

        return false;
    }

    /**
     * Compiles the allowable actions for a folder or document.
     */
    private AllowableActions compileAllowableActions(Node node) {
        boolean isRoot = ROOT_ID.equals(node.getPath());
        Set<Action> aas = EnumSet.noneOf(Action.class);

        addAction(aas, Action.CAN_GET_OBJECT_PARENTS, !isRoot);
        addAction(aas, Action.CAN_GET_PROPERTIES, true);
        addAction(aas, Action.CAN_UPDATE_PROPERTIES, checkPermission(node, Permission.WRITE));
        addAction(aas, Action.CAN_MOVE_OBJECT, checkPermission(node, Permission.WRITE) && !isRoot);
        addAction(aas, Action.CAN_DELETE_OBJECT, checkPermission(node, Permission.DELETE) && !isRoot);
        addAction(aas, Action.CAN_GET_ACL, true);

        if (node instanceof Folder) {
            addAction(aas, Action.CAN_GET_DESCENDANTS, true);
            addAction(aas, Action.CAN_GET_CHILDREN, true);
            addAction(aas, Action.CAN_GET_FOLDER_PARENT, !isRoot);
            addAction(aas, Action.CAN_GET_FOLDER_TREE, true);
            addAction(aas, Action.CAN_CREATE_DOCUMENT, checkPermission(node, Permission.WRITE));
            addAction(aas, Action.CAN_CREATE_FOLDER, checkPermission(node, Permission.WRITE));
            addAction(aas, Action.CAN_DELETE_TREE, checkPermission(node, Permission.DELETE));
        } else {
            addAction(aas, Action.CAN_GET_CONTENT_STREAM, true);
            addAction(aas, Action.CAN_SET_CONTENT_STREAM, checkPermission(node, Permission.WRITE));
            addAction(aas, Action.CAN_DELETE_CONTENT_STREAM, checkPermission(node, Permission.WRITE));
            addAction(aas, Action.CAN_GET_ALL_VERSIONS, true);
        }

        AllowableActionsImpl result = new AllowableActionsImpl();
        result.setAllowableActions(aas);

        return result;
    }

    private static boolean checkPermission(Node node, int perm) {
        return (node.getPermissions() & perm) == perm;
    }

    private static boolean checkPermission(int permissions, int perm) {
        return (permissions & perm) == perm;
    }

    private static void addAction(Set<Action> aas, Action action, boolean condition) {
        if (condition) {
            aas.add(action);
        }
    }

    /**
     * Compiles the ACL for a file or folder.
     */
    private Acl compileAcl(Node node) {
        AccessControlListImpl result = new AccessControlListImpl();
        result.setAces(new ArrayList<Ace>());

        try {
            for (Map.Entry<String, Integer> ue : OKMAuth.getInstance().getGrantedUsers(null, node.getUuid())
                    .entrySet()) {
                // create principal
                AccessControlPrincipalDataImpl principal = new AccessControlPrincipalDataImpl();
                principal.setPrincipalId(ue.getKey());

                // create ACE
                AccessControlEntryImpl entry = new AccessControlEntryImpl();
                entry.setPrincipal(principal);
                entry.setPermissions(new ArrayList<String>());
                entry.getPermissions().add(CMIS_READ);

                if (checkPermission(ue.getValue(), Permission.WRITE)) {
                    entry.getPermissions().add(CMIS_WRITE);
                }

                if (checkPermission(ue.getValue(), Permission.DELETE)) {
                    entry.getPermissions().add(CMIS_DELETE);
                }

                entry.setDirect(true);

                // add ACE
                result.getAces().add(entry);
            }

            return result;
        } catch (Exception e) {
            throw new CmisStorageException(e.getMessage(), e);
        }
    }

    /**
     * Writes the properties for a document or folder.
     */
    private static void writePropertiesFile(Node node, Properties properties) {
        // create object
        ObjectDataImpl object = new ObjectDataImpl();
        object.setProperties(properties);

        try {
            // Use setProperties
        } catch (Exception e) {
            throw new CmisStorageException("Couldn't store properties!", e);
        }
    }

    // --- internal stuff ---

    /**
     * Converts milliseconds into a calendar object.
     */
    private static GregorianCalendar millisToCalendar(long millis) {
        GregorianCalendar result = new GregorianCalendar();
        result.setTimeZone(TimeZone.getTimeZone("GMT"));
        result.setTimeInMillis((long) (Math.ceil((double) millis / 1000) * 1000));

        return result;
    }

    /**
     * Checks if the given name is valid for a file system.
     */
    private static boolean isValidName(String name) {
        if ((name == null) || (name.length() == 0) || (name.indexOf(File.separatorChar) != -1)
                || (name.indexOf(File.pathSeparatorChar) != -1)) {
            return false;
        }

        return true;
    }

    /**
     * Splits a filter statement into a collection of properties. If <code>filter</code> is <code>null</code>, empty or
     * one of the properties
     * is '*' , an empty collection will be returned.
     */
    private static Set<String> splitFilter(String filter) {
        if (filter == null) {
            return null;
        }

        if (filter.trim().length() == 0) {
            return null;
        }

        Set<String> result = new HashSet<String>();
        for (String s : filter.split(",")) {
            s = s.trim();
            if (s.equals("*")) {
                return null;
            } else if (s.length() > 0) {
                result.add(s);
            }
        }

        // set a few base properties
        // query name == id (for base type properties)
        result.add(PropertyIds.OBJECT_ID);
        result.add(PropertyIds.OBJECT_TYPE_ID);
        result.add(PropertyIds.BASE_TYPE_ID);

        return result;
    }

    /**
     * Gets the type id from a set of properties.
     */
    private static String getTypeId(Properties properties) {
        PropertyData<?> typeProperty = properties.getProperties().get(PropertyIds.OBJECT_TYPE_ID);
        if (!(typeProperty instanceof PropertyId)) {
            throw new CmisInvalidArgumentException("Type id must be set!");
        }

        String typeId = ((PropertyId) typeProperty).getFirstValue();
        if (typeId == null) {
            throw new CmisInvalidArgumentException("Type id must be set!");
        }

        return typeId;
    }

    /**
     * Returns the first value of an id property.
     */
    private static String getIdProperty(Properties properties, String name) {
        PropertyData<?> property = properties.getProperties().get(name);

        if (!(property instanceof PropertyId)) {
            return null;
        }

        return ((PropertyId) property).getFirstValue();
    }

    /**
     * Returns the first value of an string property.
     */
    private static String getStringProperty(Properties properties, String name) {
        PropertyData<?> property = properties.getProperties().get(name);

        if (!(property instanceof PropertyString)) {
            return null;
        }

        return ((PropertyString) property).getFirstValue();
    }

    /**
     * Returns the first value of an datetime property.
     */
    private static GregorianCalendar getDateTimeProperty(Properties properties, String name) {
        PropertyData<?> property = properties.getProperties().get(name);

        if (!(property instanceof PropertyDateTime)) {
            return null;
        }

        return ((PropertyDateTime) property).getFirstValue();
    }

    /**
     * Returns the Node object by id or throws an appropriate exception.
     */
    private Node getNode(String id) {
        log.debug("getNode({})", id);

        try {
            if (OKMFolder.getInstance().isValid(null, id)) {
                return OKMFolder.getInstance().getProperties(null, id);
            } else if (OKMDocument.getInstance().isValid(null, id)) {
                return OKMDocument.getInstance().getProperties(null, id);
            } else {
                throw new CmisObjectNotFoundException(id);
            }
        } catch (PathNotFoundException e) {
            throw new CmisObjectNotFoundException(e.getMessage(), e);
        } catch (RepositoryException e) {
            throw new CmisStorageException(e.getMessage(), e);
        } catch (DatabaseException e) {
            throw new CmisStorageException(e.getMessage(), e);
        }
    }
}