You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@chemistry.apache.org by dc...@apache.org on 2010/02/16 17:04:07 UTC

svn commit: r910572 [21/36] - in /incubator/chemistry/trunk/opencmis: ./ _dev/ opencmis-client/ opencmis-client/opencmis-client-api/ opencmis-client/opencmis-client-api/src/ opencmis-client/opencmis-client-api/src/main/ opencmis-client/opencmis-client-...

Added: incubator/chemistry/trunk/opencmis/opencmis-server/opencmis-server-fileshare/src/main/java/org/apache/opencmis/fileshare/FileShareRepository.java
URL: http://svn.apache.org/viewvc/incubator/chemistry/trunk/opencmis/opencmis-server/opencmis-server-fileshare/src/main/java/org/apache/opencmis/fileshare/FileShareRepository.java?rev=910572&view=auto
==============================================================================
--- incubator/chemistry/trunk/opencmis/opencmis-server/opencmis-server-fileshare/src/main/java/org/apache/opencmis/fileshare/FileShareRepository.java (added)
+++ incubator/chemistry/trunk/opencmis/opencmis-server/opencmis-server-fileshare/src/main/java/org/apache/opencmis/fileshare/FileShareRepository.java Tue Feb 16 16:03:38 2010
@@ -0,0 +1,2146 @@
+/*
+ * 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.apache.opencmis.fileshare;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TimeZone;
+
+import javax.xml.bind.JAXBElement;
+import javax.xml.bind.Marshaller;
+import javax.xml.bind.Unmarshaller;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.opencmis.commons.PropertyIds;
+import org.apache.opencmis.commons.api.PropertyDefinition;
+import org.apache.opencmis.commons.api.TypeDefinition;
+import org.apache.opencmis.commons.api.TypeDefinitionContainer;
+import org.apache.opencmis.commons.api.TypeDefinitionList;
+import org.apache.opencmis.commons.enums.AclPropagation;
+import org.apache.opencmis.commons.enums.BaseObjectTypeIds;
+import org.apache.opencmis.commons.enums.CapabilityAcl;
+import org.apache.opencmis.commons.enums.CapabilityChanges;
+import org.apache.opencmis.commons.enums.CapabilityContentStreamUpdates;
+import org.apache.opencmis.commons.enums.CapabilityJoin;
+import org.apache.opencmis.commons.enums.CapabilityQuery;
+import org.apache.opencmis.commons.enums.CapabilityRendition;
+import org.apache.opencmis.commons.enums.SupportedPermissions;
+import org.apache.opencmis.commons.enums.Updatability;
+import org.apache.opencmis.commons.enums.VersioningState;
+import org.apache.opencmis.commons.exceptions.CmisBaseException;
+import org.apache.opencmis.commons.exceptions.CmisConstraintException;
+import org.apache.opencmis.commons.exceptions.CmisContentAlreadyExistsException;
+import org.apache.opencmis.commons.exceptions.CmisInvalidArgumentException;
+import org.apache.opencmis.commons.exceptions.CmisNameConstraintViolationException;
+import org.apache.opencmis.commons.exceptions.CmisObjectNotFoundException;
+import org.apache.opencmis.commons.exceptions.CmisPermissionDeniedException;
+import org.apache.opencmis.commons.exceptions.CmisRuntimeException;
+import org.apache.opencmis.commons.exceptions.CmisStorageException;
+import org.apache.opencmis.commons.exceptions.CmisStreamNotSupportedException;
+import org.apache.opencmis.commons.exceptions.CmisUpdateConflictException;
+import org.apache.opencmis.commons.impl.Converter;
+import org.apache.opencmis.commons.impl.JaxBHelper;
+import org.apache.opencmis.commons.impl.dataobjects.AccessControlEntryImpl;
+import org.apache.opencmis.commons.impl.dataobjects.AccessControlListImpl;
+import org.apache.opencmis.commons.impl.dataobjects.AccessControlPrincipalDataImpl;
+import org.apache.opencmis.commons.impl.dataobjects.AclCapabilitiesDataImpl;
+import org.apache.opencmis.commons.impl.dataobjects.AllowableActionsDataImpl;
+import org.apache.opencmis.commons.impl.dataobjects.ContentStreamDataImpl;
+import org.apache.opencmis.commons.impl.dataobjects.FailedToDeleteDataImpl;
+import org.apache.opencmis.commons.impl.dataobjects.ObjectDataImpl;
+import org.apache.opencmis.commons.impl.dataobjects.ObjectInFolderContainerImpl;
+import org.apache.opencmis.commons.impl.dataobjects.ObjectInFolderDataImpl;
+import org.apache.opencmis.commons.impl.dataobjects.ObjectInFolderListImpl;
+import org.apache.opencmis.commons.impl.dataobjects.ObjectParentDataImpl;
+import org.apache.opencmis.commons.impl.dataobjects.PermissionDefinitionDataImpl;
+import org.apache.opencmis.commons.impl.dataobjects.PermissionMappingDataImpl;
+import org.apache.opencmis.commons.impl.dataobjects.PropertiesDataImpl;
+import org.apache.opencmis.commons.impl.dataobjects.PropertyBooleanDataImpl;
+import org.apache.opencmis.commons.impl.dataobjects.PropertyDateTimeDataImpl;
+import org.apache.opencmis.commons.impl.dataobjects.PropertyDecimalDataImpl;
+import org.apache.opencmis.commons.impl.dataobjects.PropertyHtmlDataImpl;
+import org.apache.opencmis.commons.impl.dataobjects.PropertyIdDataImpl;
+import org.apache.opencmis.commons.impl.dataobjects.PropertyIntegerDataImpl;
+import org.apache.opencmis.commons.impl.dataobjects.PropertyStringDataImpl;
+import org.apache.opencmis.commons.impl.dataobjects.PropertyUriDataImpl;
+import org.apache.opencmis.commons.impl.dataobjects.RepositoryCapabilitiesDataImpl;
+import org.apache.opencmis.commons.impl.dataobjects.RepositoryInfoDataImpl;
+import org.apache.opencmis.commons.impl.jaxb.CmisObjectType;
+import org.apache.opencmis.commons.impl.jaxb.CmisProperty;
+import org.apache.opencmis.commons.provider.AccessControlEntry;
+import org.apache.opencmis.commons.provider.AccessControlList;
+import org.apache.opencmis.commons.provider.AllowableActionsData;
+import org.apache.opencmis.commons.provider.ContentStreamData;
+import org.apache.opencmis.commons.provider.FailedToDeleteData;
+import org.apache.opencmis.commons.provider.Holder;
+import org.apache.opencmis.commons.provider.ObjectData;
+import org.apache.opencmis.commons.provider.ObjectInFolderContainer;
+import org.apache.opencmis.commons.provider.ObjectInFolderData;
+import org.apache.opencmis.commons.provider.ObjectInFolderList;
+import org.apache.opencmis.commons.provider.ObjectParentData;
+import org.apache.opencmis.commons.provider.PermissionDefinitionData;
+import org.apache.opencmis.commons.provider.PermissionMappingData;
+import org.apache.opencmis.commons.provider.PropertiesData;
+import org.apache.opencmis.commons.provider.PropertyData;
+import org.apache.opencmis.commons.provider.PropertyDateTimeData;
+import org.apache.opencmis.commons.provider.PropertyIdData;
+import org.apache.opencmis.commons.provider.PropertyStringData;
+import org.apache.opencmis.commons.provider.RepositoryInfoData;
+import org.apache.opencmis.server.spi.CallContext;
+import org.apache.opencmis.server.spi.ObjectInfoHolder;
+import org.apache.opencmis.server.spi.ObjectInfoImpl;
+
+/**
+ * File system back-end for CMIS server.
+ * 
+ * @author <a href="mailto:fmueller@opentext.com">Florian M&uuml;ller</a>
+ * 
+ */
+public class FileShareRepository {
+
+  private static final String ROOT_ID = "@root@";
+  private static final String SHADOW_EXT = ".cmis.xml";
+  private static final String SHADOW_FOLDER = "cmis.xml";
+
+  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_ALL = "cmis:all";
+
+  private static final int BUFFER_SIZE = 64 * 1024;
+
+  private static final Log log = LogFactory.getLog(FileShareRepository.class);
+
+  /** Repository id */
+  private String fRepositoryId;
+  /** Root directory */
+  private File fRoot;
+  /** Types */
+  private TypeManager fTypes;
+  /** User table */
+  private Map<String, Boolean> fUserMap;
+  /** Repository info */
+  private RepositoryInfoDataImpl fRepositoryInfo;
+
+  /**
+   * Constructor.
+   * 
+   * @param repId
+   *          CMIS repository id
+   * @param root
+   *          root folder
+   * @param types
+   *          type manager object
+   */
+  public FileShareRepository(String repId, String root, TypeManager types) {
+    // check repository id
+    if ((repId == null) || (repId.trim().length() == 0)) {
+      throw new IllegalArgumentException("Invalid repository id!");
+    }
+
+    fRepositoryId = repId;
+
+    // check root folder
+    if ((root == null) || (root.trim().length() == 0)) {
+      throw new IllegalArgumentException("Invalid root folder!");
+    }
+
+    fRoot = new File(root);
+    if (!fRoot.isDirectory()) {
+      throw new IllegalArgumentException("Root is not a directory!");
+    }
+
+    // set types
+    fTypes = types;
+
+    // set up user table
+    fUserMap = new HashMap<String, Boolean>();
+
+    // compile repository info
+    fRepositoryInfo = new RepositoryInfoDataImpl();
+
+    fRepositoryInfo.setRepositoryId(fRepositoryId);
+    fRepositoryInfo.setRepositoryName(fRepositoryId);
+    fRepositoryInfo.setRepositoryDescription(fRepositoryId);
+
+    fRepositoryInfo.setCmisVersionSupported("1.0");
+
+    fRepositoryInfo.setProductName("OpenCMIS FileShare");
+    fRepositoryInfo.setProductVersion("0.1");
+    fRepositoryInfo.setVendorName("OpenCMIS");
+
+    fRepositoryInfo.setRootFolder(ROOT_ID);
+
+    fRepositoryInfo.setThinClientUri("");
+
+    RepositoryCapabilitiesDataImpl capabilities = new RepositoryCapabilitiesDataImpl();
+    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(CapabilityRendition.NONE);
+
+    fRepositoryInfo.setRepositoryCapabilities(capabilities);
+
+    AclCapabilitiesDataImpl aclCapability = new AclCapabilitiesDataImpl();
+    aclCapability.setSupportedPermissions(SupportedPermissions.BASIC);
+    aclCapability.setAclPropagation(AclPropagation.OBJECTONLY);
+
+    // permissions
+    List<PermissionDefinitionData> permissions = new ArrayList<PermissionDefinitionData>();
+    permissions.add(createPermission(CMIS_READ, "Read"));
+    permissions.add(createPermission(CMIS_WRITE, "Write"));
+    permissions.add(createPermission(CMIS_ALL, "All"));
+    aclCapability.setPermissionDefinitionData(permissions);
+
+    // mapping
+    List<PermissionMappingData> mappings = new ArrayList<PermissionMappingData>();
+    mappings.add(createMapping(PermissionMappingData.KEY_CAN_CREATE_DOCUMENT_FOLDER, CMIS_READ));
+    mappings.add(createMapping(PermissionMappingData.KEY_CAN_CREATE_FOLDER_FOLDER, CMIS_READ));
+    mappings.add(createMapping(PermissionMappingData.KEY_CAN_DELETE_CONTENT_DOCUMENT, CMIS_WRITE));
+    mappings.add(createMapping(PermissionMappingData.KEY_CAN_DELETE_OBJECT, CMIS_ALL));
+    mappings.add(createMapping(PermissionMappingData.KEY_CAN_DELETE_TREE_FOLDER, CMIS_ALL));
+    mappings.add(createMapping(PermissionMappingData.KEY_CAN_GET_ACL_OBJECT, CMIS_READ));
+    mappings.add(createMapping(PermissionMappingData.KEY_CAN_GET_ALL_VERSIONS_VERSION_SERIES,
+        CMIS_READ));
+    mappings.add(createMapping(PermissionMappingData.KEY_CAN_GET_CHILDREN_FOLDER, CMIS_READ));
+    mappings.add(createMapping(PermissionMappingData.KEY_CAN_GET_DESCENDENTS_FOLDER, CMIS_READ));
+    mappings.add(createMapping(PermissionMappingData.KEY_CAN_GET_FOLDER_PARENT_OBJECT, CMIS_READ));
+    mappings.add(createMapping(PermissionMappingData.KEY_CAN_GET_PARENTS_FOLDER, CMIS_READ));
+    mappings.add(createMapping(PermissionMappingData.KEY_CAN_GET_PROPERTIES_OBJECT, CMIS_READ));
+    mappings.add(createMapping(PermissionMappingData.KEY_CAN_MOVE_OBJECT, CMIS_WRITE));
+    mappings.add(createMapping(PermissionMappingData.KEY_CAN_MOVE_SOURCE, CMIS_READ));
+    mappings.add(createMapping(PermissionMappingData.KEY_CAN_MOVE_TARGET, CMIS_WRITE));
+    mappings.add(createMapping(PermissionMappingData.KEY_CAN_SET_CONTENT_DOCUMENT, CMIS_WRITE));
+    mappings.add(createMapping(PermissionMappingData.KEY_CAN_UPDATE_PROPERTIES_OBJECT, CMIS_WRITE));
+    mappings.add(createMapping(PermissionMappingData.KEY_CAN_VIEW_CONTENT_OBJECT, CMIS_READ));
+    aclCapability.setPermissionMappingData(mappings);
+
+    fRepositoryInfo.setAclCapabilities(aclCapability);
+  }
+
+  private PermissionDefinitionData createPermission(String permission, String description) {
+    PermissionDefinitionDataImpl pd = new PermissionDefinitionDataImpl();
+    pd.setPermission(permission);
+    pd.setDescription(description);
+
+    return pd;
+  }
+
+  private PermissionMappingData createMapping(String key, String permission) {
+    PermissionMappingDataImpl pm = new PermissionMappingDataImpl();
+    pm.setKey(key);
+    pm.setPermissions(Collections.singletonList(permission));
+
+    return pm;
+  }
+
+  /**
+   * Adds a user to the repository.
+   */
+  public void addUser(String user, boolean readOnly) {
+    if ((user == null) || (user.length() == 0)) {
+      return;
+    }
+
+    fUserMap.put(user, readOnly);
+  }
+
+  // --- the public stuff ---
+
+  /**
+   * Returns the repository id.
+   */
+  public String getRepositoryId() {
+    return fRepositoryId;
+  }
+
+  /**
+   * CMIS getRepositoryInfo.
+   */
+  public RepositoryInfoData getRepositoryInfo(CallContext context) {
+    debug("getRepositoryInfo");
+    checkUser(context, false);
+
+    return fRepositoryInfo;
+  }
+
+  /**
+   * CMIS getTypesChildren.
+   */
+  public TypeDefinitionList getTypesChildren(CallContext context, String typeId,
+      boolean includePropertyDefinitions, BigInteger maxItems, BigInteger skipCount) {
+    debug("getTypesChildren");
+    checkUser(context, false);
+
+    return fTypes
+        .getTypesChildren(context, typeId, includePropertyDefinitions, maxItems, skipCount);
+  }
+
+  /**
+   * CMIS getTypeDefinition.
+   */
+  public TypeDefinition getTypeDefinition(CallContext context, String typeId) {
+    debug("getTypeDefinition");
+    checkUser(context, false);
+
+    return fTypes.getTypeDefinition(context, typeId);
+  }
+
+  /**
+   * CMIS getTypesDescendants.
+   */
+  public List<TypeDefinitionContainer> getTypesDescendants(CallContext context, String typeId,
+      BigInteger depth, Boolean includePropertyDefinitions) {
+    debug("getTypesDescendants");
+    checkUser(context, false);
+
+    return fTypes.getTypesDescendants(context, typeId, depth, includePropertyDefinitions);
+  }
+
+  /**
+   * Create* dispatch for AtomPub.
+   */
+  public ObjectData create(CallContext context, PropertiesData properties, String folderId,
+      ContentStreamData contentStream, VersioningState versioningState, ObjectInfoHolder objectInfos) {
+    debug("create");
+    boolean userReadOnly = checkUser(context, true);
+
+    String typeId = getTypeId(properties);
+    TypeDefinition type = fTypes.getType(typeId);
+    if (type == null) {
+      throw new CmisObjectNotFoundException("Type '" + typeId + "' is unknown!");
+    }
+
+    String objectId = null;
+    if (type.getBaseId() == BaseObjectTypeIds.CMIS_DOCUMENT) {
+      objectId = createDocument(context, properties, folderId, contentStream, versioningState);
+    }
+    else if (type.getBaseId() == BaseObjectTypeIds.CMIS_FOLDER) {
+      objectId = createFolder(context, properties, folderId);
+    }
+    else {
+      throw new CmisObjectNotFoundException("Cannot create object of type '" + typeId + "'!");
+    }
+
+    return compileObjectType(getFile(objectId), null, false, false, userReadOnly, objectInfos);
+  }
+
+  /**
+   * CMIS createDocument.
+   */
+  public String createDocument(CallContext context, PropertiesData properties, String folderId,
+      ContentStreamData contentStream, VersioningState versioningState) {
+    debug("createDocument");
+    checkUser(context, true);
+
+    // 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 = fTypes.getType(typeId);
+    if (type == null) {
+      throw new CmisObjectNotFoundException("Type '" + typeId + "' is unknown!");
+    }
+
+    // compile the properties
+    PropertiesData props = compileProperties(typeId, context.getUsername(), millisToCalendar(System
+        .currentTimeMillis()), context.getUsername(), properties);
+
+    // check the name
+    String name = getStringProperty(properties, PropertyIds.CMIS_NAME);
+    if (!isValidName(name)) {
+      throw new CmisNameConstraintViolationException("Name is not valid!");
+    }
+
+    // get parent File
+    File parent = getFile(folderId);
+    if (!parent.isDirectory()) {
+      throw new CmisObjectNotFoundException("Parent is not a folder!");
+    }
+
+    // check the file
+    File newFile = new File(parent, name);
+    if (newFile.exists()) {
+      throw new CmisNameConstraintViolationException("Document already exists!");
+    }
+
+    // create the file
+    try {
+      newFile.createNewFile();
+    }
+    catch (IOException e) {
+      throw new CmisStorageException("Could not create file: " + e.getMessage());
+    }
+
+    // write content, if available
+    if ((contentStream != null) && (contentStream.getStream() != null)) {
+      try {
+        OutputStream out = new BufferedOutputStream(new FileOutputStream(newFile), BUFFER_SIZE);
+        InputStream in = new BufferedInputStream(contentStream.getStream(), BUFFER_SIZE);
+
+        byte[] buffer = new byte[BUFFER_SIZE];
+        int b;
+        while ((b = in.read(buffer)) > -1) {
+          out.write(buffer, 0, b);
+        }
+
+        out.flush();
+        out.close();
+        in.close();
+      }
+      catch (Exception e) {
+        throw new CmisStorageException("Could not write content: " + e.getMessage(), e);
+      }
+    }
+
+    // write properties
+    writePropertiesFile(newFile, props);
+
+    return getId(newFile);
+  }
+
+  /**
+   * CMIS createDocumentFromSource.
+   */
+  public String createDocumentFromSource(CallContext context, String sourceId,
+      PropertiesData properties, String folderId, VersioningState versioningState) {
+
+    // check versioning state
+    if (VersioningState.NONE != versioningState) {
+      throw new CmisConstraintException("Versioning not supported!");
+    }
+
+    // get parent File
+    File parent = getFile(folderId);
+    if (!parent.isDirectory()) {
+      throw new CmisObjectNotFoundException("Parent is not a folder!");
+    }
+
+    // get source File
+    File source = getFile(sourceId);
+    if (!source.isFile()) {
+      throw new CmisObjectNotFoundException("Source is not a document!");
+    }
+
+    // file name
+    String name = source.getName();
+
+    // get properties
+    PropertiesDataImpl sourceProperties = new PropertiesDataImpl();
+    readCustomProperties(source, sourceProperties, null, new ObjectInfoImpl());
+
+    // get the type id
+    String typeId = getIdProperty(sourceProperties, PropertyIds.CMIS_OBJECT_TYPE_ID);
+    if (typeId == null) {
+      typeId = TypeManager.DOCUMENT_TYPE_ID;
+    }
+
+    // copy properties
+    PropertiesDataImpl newProperties = new PropertiesDataImpl();
+    for (PropertyData<?> prop : sourceProperties.getProperties().values()) {
+      if ((prop.getId().equals(PropertyIds.CMIS_OBJECT_TYPE_ID))
+          || (prop.getId().equals(PropertyIds.CMIS_CREATED_BY))
+          || (prop.getId().equals(PropertyIds.CMIS_CREATION_DATE))
+          || (prop.getId().equals(PropertyIds.CMIS_LAST_MODIFIED_BY))) {
+        continue;
+      }
+
+      newProperties.addProperty(prop);
+    }
+
+    // replace properties
+    if (properties != null) {
+      // find new name
+      String newName = getStringProperty(properties, PropertyIds.CMIS_NAME);
+      if (newName != null) {
+        if (!isValidName(newName)) {
+          throw new CmisNameConstraintViolationException("Name is not valid!");
+        }
+        name = newName;
+      }
+
+      // get the property definitions
+      TypeDefinition type = fTypes.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.CMIS_OBJECT_TYPE_ID, typeId);
+    addPropertyString(newProperties, typeId, null, PropertyIds.CMIS_CREATED_BY, context
+        .getUsername());
+    addPropertyDateTime(newProperties, typeId, null, PropertyIds.CMIS_CREATION_DATE,
+        millisToCalendar(System.currentTimeMillis()));
+    addPropertyString(newProperties, typeId, null, PropertyIds.CMIS_LAST_MODIFIED_BY, context
+        .getUsername());
+
+    // check the file
+    File newFile = new File(parent, name);
+    if (newFile.exists()) {
+      throw new CmisNameConstraintViolationException("Document already exists.");
+    }
+
+    // create the file
+    try {
+      newFile.createNewFile();
+    }
+    catch (IOException e) {
+      throw new CmisStorageException("Could not create file: " + e.getMessage(), e);
+    }
+
+    // copy content
+    try {
+      OutputStream out = new BufferedOutputStream(new FileOutputStream(newFile));
+      InputStream in = new BufferedInputStream(new FileInputStream(source));
+
+      byte[] buffer = new byte[BUFFER_SIZE];
+      int b;
+      while ((b = in.read(buffer)) > -1) {
+        out.write(buffer, 0, b);
+      }
+
+      out.flush();
+      out.close();
+      in.close();
+    }
+    catch (Exception e) {
+      throw new CmisStorageException("Could not roead or write content: " + e.getMessage(), e);
+    }
+
+    // write properties
+    writePropertiesFile(newFile, newProperties);
+
+    return getId(newFile);
+  }
+
+  /**
+   * CMIS createFolder.
+   */
+  public String createFolder(CallContext context, PropertiesData properties, String folderId) {
+    debug("createFolder");
+    checkUser(context, true);
+
+    // check properties
+    if ((properties == null) || (properties.getProperties() == null)) {
+      throw new CmisInvalidArgumentException("Properties must be set!");
+    }
+
+    // check type
+    String typeId = getTypeId(properties);
+    TypeDefinition type = fTypes.getType(typeId);
+    if (type == null) {
+      throw new CmisObjectNotFoundException("Type '" + typeId + "' is unknown!");
+    }
+
+    // compile the properties
+    PropertiesData props = compileProperties(typeId, context.getUsername(), millisToCalendar(System
+        .currentTimeMillis()), context.getUsername(), properties);
+
+    // check the name
+    String name = getStringProperty(properties, PropertyIds.CMIS_NAME);
+    if (!isValidName(name)) {
+      throw new CmisNameConstraintViolationException("Name is not valid.");
+    }
+
+    // get parent File
+    File parent = getFile(folderId);
+    if (!parent.isDirectory()) {
+      throw new CmisObjectNotFoundException("Parent is not a folder!");
+    }
+
+    // create the folder
+    File newFolder = new File(parent, name);
+    if (!newFolder.mkdir()) {
+      throw new CmisStorageException("Could not create folder!");
+    }
+
+    // write properties
+    writePropertiesFile(newFolder, props);
+
+    return getId(newFolder);
+  }
+
+  /**
+   * CMIS moveObject.
+   */
+  public ObjectData moveObject(CallContext context, Holder<String> objectId, String targetFolderId,
+      ObjectInfoHolder objectInfos) {
+    debug("moveObject");
+    boolean userReadOnly = checkUser(context, true);
+
+    if (objectId == null) {
+      throw new CmisInvalidArgumentException("Id is not valid!");
+    }
+
+    // get the file and parent
+    File file = getFile(objectId.getValue());
+    File parent = getFile(targetFolderId);
+
+    // build new path
+    File newFile = new File(parent, file.getName());
+    if (newFile.exists()) {
+      throw new CmisStorageException("Object already exists!");
+    }
+
+    // move it
+    if (!file.renameTo(newFile)) {
+      throw new CmisStorageException("Move failed!");
+    }
+    else {
+      // set new id
+      objectId.setValue(getId(newFile));
+      
+      // if it is a file, move properties file too
+      if (newFile.isFile()) {
+        File propFile = getPropertiesFile(file);
+        if (propFile.exists()) {
+          File newPropFile = new File(parent, propFile.getName());
+          propFile.renameTo(newPropFile);
+        }
+      }
+    }
+
+    return compileObjectType(newFile, null, false, false, userReadOnly, objectInfos);
+  }
+
+  /**
+   * CMIS setContentStream and deleteContentStream.
+   */
+  public void setContentStream(CallContext context, Holder<String> objectId, Boolean overwriteFlag,
+      ContentStreamData contentStream) {
+    debug("setContentStream or deleteContentStream");
+    checkUser(context, true);
+
+    if (objectId == null) {
+      throw new CmisInvalidArgumentException("Id is not valid!");
+    }
+
+    // get the file
+    File file = getFile(objectId.getValue());
+    if (!file.isFile()) {
+      throw new CmisStreamNotSupportedException("Not a file!");
+    }
+
+    // check overwrite
+    boolean owf = (overwriteFlag == null ? true : overwriteFlag.booleanValue());
+    if (!owf && file.length() > 0) {
+      throw new CmisContentAlreadyExistsException("Content already exists!");
+    }
+
+    try {
+      OutputStream out = new BufferedOutputStream(new FileOutputStream(file), BUFFER_SIZE);
+
+      if ((contentStream == null) || (contentStream.getStream() == null)) {
+        // delete content
+        out.write(new byte[0]);
+      }
+      else {
+        // set content
+        InputStream in = new BufferedInputStream(contentStream.getStream(), BUFFER_SIZE);
+
+        byte[] buffer = new byte[BUFFER_SIZE];
+        int b;
+        while ((b = in.read(buffer)) > -1) {
+          out.write(buffer, 0, b);
+        }
+
+        in.close();
+      }
+
+      out.close();
+    }
+    catch (Exception e) {
+      throw new CmisStorageException("Could not write content: " + e.getMessage(), e);
+    }
+  }
+
+  /**
+   * CMIS deleteObject.
+   */
+  public void deleteObject(CallContext context, String objectId) {
+    debug("deleteObject");
+    checkUser(context, true);
+
+    // get the file or folder
+    File file = getFile(objectId);
+    if (!file.exists()) {
+      throw new CmisObjectNotFoundException("Object not found!");
+    }
+
+    // check if it is a folder and if it is empty
+    if (!isFolderEmpty(file)) {
+      throw new CmisConstraintException("Folder is not empty!");
+    }
+
+    // delete properties and actual file
+    getPropertiesFile(file).delete();
+    if (!file.delete()) {
+      throw new CmisStorageException("Deletion failed!");
+    }
+  }
+
+  /**
+   * CMIS deleteTree.
+   */
+  public FailedToDeleteData deleteTree(CallContext context, String folderId,
+      Boolean continueOnFailure) {
+    debug("deleteTree");
+    checkUser(context, true);
+
+    boolean cof = (continueOnFailure == null ? false : continueOnFailure.booleanValue());
+
+    // get the file or folder
+    File file = getFile(folderId);
+
+    FailedToDeleteDataImpl result = new FailedToDeleteDataImpl();
+    result.setIds(new ArrayList<String>());
+
+    // if it is a folder, remove it recursively
+    if (file.isDirectory()) {
+      deleteFolder(file, cof, result);
+    }
+    else {
+      getPropertiesFile(file).delete();
+      if (!file.delete()) {
+        result.getIds().add(getId(file));
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * CMIS updateProperties.
+   */
+  public ObjectData updateProperties(CallContext context, Holder<String> objectId,
+      PropertiesData properties, ObjectInfoHolder objectInfos) {
+    debug("updateProperties");
+    boolean userReadOnly = checkUser(context, true);
+
+    if (objectId == null) {
+      throw new CmisInvalidArgumentException("Id is not valid!");
+    }
+
+    // get the file or folder
+    File file = getFile(objectId.getValue());
+
+    // get and check the new name
+    String newName = getStringProperty(properties, PropertyIds.CMIS_NAME);
+    boolean isRename = (newName != null) && (!file.getName().equals(newName));
+    if (isRename && !isValidName(newName)) {
+      throw new CmisNameConstraintViolationException("Name is not valid!");
+    }
+
+    // get old properties
+    PropertiesDataImpl oldProperties = new PropertiesDataImpl();
+    readCustomProperties(file, oldProperties, null, new ObjectInfoImpl());
+
+    // get the type id
+    String typeId = getIdProperty(oldProperties, PropertyIds.CMIS_OBJECT_TYPE_ID);
+    if (typeId == null) {
+      typeId = (file.isDirectory() ? TypeManager.FOLDER_TYPE_ID : TypeManager.DOCUMENT_TYPE_ID);
+    }
+
+    // get the creator
+    String creator = getStringProperty(oldProperties, PropertyIds.CMIS_CREATED_BY);
+    if (creator == null) {
+      creator = context.getUsername();
+    }
+
+    // get creation date
+    GregorianCalendar creationDate = getDateTimeProperty(oldProperties,
+        PropertyIds.CMIS_CREATION_DATE);
+    if (creationDate == null) {
+      creationDate = millisToCalendar(file.lastModified());
+    }
+
+    // compile the properties
+    PropertiesData props = updateProperties(typeId, creator, creationDate, context.getUsername(),
+        oldProperties, properties);
+
+    // write properties
+    writePropertiesFile(file, props);
+
+    // rename file or folder if necessary
+    File newFile = file;
+    if (isRename) {
+      File parent = file.getParentFile();
+      File propFile = getPropertiesFile(file);
+      newFile = new File(parent, newName);
+      if (!file.renameTo(newFile)) {
+        // if something went wrong, throw an exception
+        throw new CmisUpdateConflictException("Could not rename object!");
+      }
+      else {
+        // set new id
+        objectId.setValue(getId(newFile));
+
+        // if it is a file, rename properties file too
+        if (newFile.isFile()) {
+          if (propFile.exists()) {
+            File newPropFile = new File(parent, newName + SHADOW_EXT);
+            propFile.renameTo(newPropFile);
+          }
+        }
+      }
+    }
+
+    return compileObjectType(newFile, null, false, false, userReadOnly, objectInfos);
+  }
+
+  /**
+   * CMIS getObject.
+   */
+  public ObjectData getObject(CallContext context, String objectId, String filter,
+      Boolean includeAllowableActions, Boolean includeAcl, ObjectInfoHolder objectInfos) {
+    debug("getObject");
+    boolean userReadOnly = checkUser(context, false);
+
+    // check id
+    if (objectId == null) {
+      throw new CmisInvalidArgumentException("Object Id must be set.");
+    }
+
+    // get the file or folder
+    File file = getFile(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(file, filterCollection, iaa, iacl, userReadOnly, objectInfos);
+  }
+
+  /**
+   * CMIS getAllowableActions.
+   */
+  public AllowableActionsData getAllowableActions(CallContext context, String objectId) {
+    debug("getAllowableActions");
+    boolean userReadOnly = checkUser(context, false);
+
+    File file = getFile(objectId);
+    if (!file.exists()) {
+      throw new CmisObjectNotFoundException("Object not found!");
+    }
+
+    return compileAllowableActions(file, userReadOnly);
+  }
+
+  /**
+   * CMIS getACL.
+   */
+  public AccessControlList getAcl(CallContext context, String repositoryId, String objectId) {
+    debug("getAcl");
+    checkUser(context, false);
+
+    // get the file or folder
+    File file = getFile(objectId);
+    if (!file.exists()) {
+      throw new CmisObjectNotFoundException("Object not found!");
+    }
+
+    return compileAcl(file);
+  }
+
+  /**
+   * CMIS getContentStream.
+   */
+  public ContentStreamData getContentStream(CallContext context, String objectId,
+      BigInteger offset, BigInteger length) {
+    debug("getContentStream");
+    checkUser(context, false);
+
+    if ((offset != null) || (length != null)) {
+      throw new CmisInvalidArgumentException("Offset and Length are not supported!");
+    }
+
+    // get the file
+    final File file = getFile(objectId);
+    if (!file.isFile()) {
+      throw new CmisStreamNotSupportedException("Not a file!");
+    }
+
+    InputStream stream = null;
+    try {
+      stream = new BufferedInputStream(new FileInputStream(file), 4 * 1024);
+    }
+    catch (FileNotFoundException e) {
+      throw new CmisObjectNotFoundException(e.getMessage(), e);
+    }
+
+    // compile data
+    ContentStreamDataImpl result = new ContentStreamDataImpl();
+    result.setFilename(file.getName());
+    result.setLength(BigInteger.valueOf(file.length()));
+    result.setMimeType(MIMETypes.getMIMEType(file));
+    result.setStream(stream);
+
+    return result;
+  }
+
+  /**
+   * CMIS getChildren.
+   */
+  public ObjectInFolderList getChildren(CallContext context, String folderId, String filter,
+      Boolean includeAllowableActions, Boolean includePathSegment, BigInteger maxItems,
+      BigInteger skipCount, ObjectInfoHolder objectInfos) {
+    debug("getChildren");
+    boolean userReadOnly = checkUser(context, false);
+
+    // 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;
+    }
+
+    // get the folder
+    File folder = getFile(folderId);
+    if (!folder.isDirectory()) {
+      throw new CmisObjectNotFoundException("Not a folder!");
+    }
+
+    // set object info of the the folder
+    if (objectInfos != null) {
+      compileObjectType(folder, null, false, false, userReadOnly, objectInfos);
+    }
+
+    // prepare result
+    ObjectInFolderListImpl result = new ObjectInFolderListImpl();
+    result.setObjects(new ArrayList<ObjectInFolderData>());
+    result.setHasMoreItems(false);
+    int count = 0;
+
+    // iterate through children
+    for (File child : folder.listFiles()) {
+      // skip hidden and shadow files
+      if (child.isHidden() || child.getName().equals(SHADOW_FOLDER)
+          || child.getPath().endsWith(SHADOW_EXT)) {
+        continue;
+      }
+
+      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(child, filterCollection, iaa, false, userReadOnly,
+          objectInfos));
+      if (ips) {
+        objectInFolder.setPathSegment(child.getName());
+      }
+
+      result.getObjects().add(objectInFolder);
+    }
+
+    result.setNumItems(BigInteger.valueOf(count));
+
+    return result;
+  }
+
+  /**
+   * CMIS getDescendants.
+   */
+  public List<ObjectInFolderContainer> getDescendants(CallContext context, String folderId,
+      BigInteger depth, String filter, Boolean includeAllowableActions, Boolean includePathSegment,
+      ObjectInfoHolder objectInfos, boolean foldersOnly) {
+    debug("getDescendants or getFolderTree");
+    boolean userReadOnly = checkUser(context, false);
+
+    // 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());
+
+    // get the folder
+    File folder = getFile(folderId);
+    if (!folder.isDirectory()) {
+      throw new CmisObjectNotFoundException("Not a folder!");
+    }
+
+    // set object info of the the folder
+    if (objectInfos != null) {
+      compileObjectType(folder, null, false, false, userReadOnly, objectInfos);
+    }
+
+    // get the tree
+    List<ObjectInFolderContainer> result = new ArrayList<ObjectInFolderContainer>();
+    gatherDescendants(folder, result, foldersOnly, d, filterCollection, iaa, ips, userReadOnly,
+        objectInfos);
+
+    return result;
+  }
+
+  /**
+   * CMIS getFolderParent.
+   */
+  public ObjectData getFolderParent(CallContext context, String folderId, String filter,
+      ObjectInfoHolder 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,
+      ObjectInfoHolder objectInfos) {
+    debug("getObjectParents");
+    boolean userReadOnly = checkUser(context, false);
+
+    // 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 file or folder
+    File file = getFile(objectId);
+
+    // don't climb above the root folder
+    if (fRoot.equals(file)) {
+      return Collections.emptyList();
+    }
+
+    // set object info of the the object
+    if (objectInfos != null) {
+      compileObjectType(file, null, false, false, userReadOnly, objectInfos);
+    }
+
+    // get parent folder
+    File parent = file.getParentFile();
+    ObjectData object = compileObjectType(parent, filterCollection, iaa, false, userReadOnly,
+        objectInfos);
+
+    ObjectParentDataImpl result = new ObjectParentDataImpl();
+    result.setObject(object);
+    if (irps) {
+      result.setRelativePathSegment(file.getName());
+    }
+
+    return Collections.singletonList((ObjectParentData) result);
+  }
+
+  /**
+   * CMIS getObjectByPath.
+   */
+  public ObjectData getObjectByPath(CallContext context, String folderPath, String filter,
+      boolean includeAllowableActions, boolean includeACL, ObjectInfoHolder objectInfos) {
+    debug("getObjectByPath");
+    boolean userReadOnly = checkUser(context, false);
+
+    // split filter
+    Set<String> filterCollection = splitFilter(filter);
+
+    // check path
+    if ((folderPath == null) || (!folderPath.startsWith("/"))) {
+      throw new CmisInvalidArgumentException("Invalid folder path!");
+    }
+
+    // get the file or folder
+    File file = null;
+    if (folderPath.length() == 1) {
+      file = fRoot;
+    }
+    else {
+      String path = folderPath.replace('/', File.separatorChar).substring(1);
+      file = new File(fRoot, path);
+    }
+
+    if (!file.exists()) {
+      throw new CmisObjectNotFoundException("Path doesn't exist.");
+    }
+
+    return compileObjectType(file, filterCollection, includeAllowableActions, includeACL,
+        userReadOnly, objectInfos);
+  }
+
+  // --- helper methods ---
+
+  /**
+   * Gather the children of a folder.
+   */
+  private void gatherDescendants(File folder, List<ObjectInFolderContainer> list,
+      boolean foldersOnly, int depth, Set<String> filter, boolean includeAllowableActions,
+      boolean includePathSegments, boolean userReadOnly, ObjectInfoHolder objectInfos) {
+    // iterate through children
+    for (File child : folder.listFiles()) {
+      // skip hidden and shadow files
+      if (child.isHidden() || child.getName().equals(SHADOW_FOLDER)
+          || child.getPath().endsWith(SHADOW_EXT)) {
+        continue;
+      }
+
+      // folders only?
+      if (foldersOnly && !child.isDirectory()) {
+        continue;
+      }
+
+      // add to list
+      ObjectInFolderDataImpl objectInFolder = new ObjectInFolderDataImpl();
+      objectInFolder.setObject(compileObjectType(child, filter, includeAllowableActions, false,
+          userReadOnly, objectInfos));
+      if (includePathSegments) {
+        objectInFolder.setPathSegment(child.getName());
+      }
+
+      ObjectInFolderContainerImpl container = new ObjectInFolderContainerImpl();
+      container.setObject(objectInFolder);
+
+      list.add(container);
+
+      // move to next level
+      if ((depth != 1) && child.isDirectory()) {
+        container.setChildren(new ArrayList<ObjectInFolderContainer>());
+        gatherDescendants(child, container.getChildren(), foldersOnly, depth - 1, filter,
+            includeAllowableActions, includePathSegments, userReadOnly, objectInfos);
+      }
+    }
+  }
+
+  /**
+   * Removes a folder and its content.
+   * 
+   * @throws
+   */
+  private boolean deleteFolder(File folder, boolean continueOnFailure, FailedToDeleteDataImpl ftd) {
+    boolean success = true;
+
+    for (File file : folder.listFiles()) {
+      if (file.isDirectory()) {
+        if (!deleteFolder(file, continueOnFailure, ftd)) {
+          if (!continueOnFailure) {
+            return false;
+          }
+          success = false;
+        }
+      }
+      else {
+        if (!file.delete()) {
+          ftd.getIds().add(getId(file));
+          if (!continueOnFailure) {
+            return false;
+          }
+          success = false;
+        }
+      }
+    }
+
+    if (!folder.delete()) {
+      ftd.getIds().add(getId(folder));
+      success = false;
+    }
+
+    return success;
+  }
+
+  /**
+   * Checks if the given name is valid for a file system.
+   * 
+   * @param name
+   *          the name to check
+   * 
+   * @return <code>true</code> if the name is valid, <code>false</code> otherwise
+   */
+  private boolean isValidName(String name) {
+    if ((name == null) || (name.length() == 0) || (name.indexOf(File.separatorChar) != -1)
+        || (name.indexOf(File.pathSeparatorChar) != -1)) {
+      return false;
+    }
+
+    return true;
+  }
+
+  /**
+   * Checks if a folder is empty. A folder is considered as empty if no files or only the shadow
+   * file reside in the folder.
+   * 
+   * @param folder
+   *          the folder
+   * 
+   * @return <code>true</code> if the folder is empty.
+   */
+  private boolean isFolderEmpty(File folder) {
+    if (!folder.isDirectory()) {
+      return true;
+    }
+
+    String[] fileNames = folder.list();
+
+    if ((fileNames == null) || (fileNames.length == 0)) {
+      return true;
+    }
+
+    if ((fileNames.length == 1) && (fileNames[0].equals(SHADOW_FOLDER))) {
+      return true;
+    }
+
+    return false;
+  }
+
+  /**
+   * Compiles an object type object from a file or folder.�
+   */
+  private ObjectData compileObjectType(File file, Set<String> filter,
+      boolean includeAllowableActions, boolean includeAcl, boolean userReadOnly,
+      ObjectInfoHolder objectInfos) {
+    ObjectDataImpl result = new ObjectDataImpl();
+
+    result.setProperties(compileProperties(file, filter, objectInfos));
+
+    if (includeAllowableActions) {
+      result.setAllowableActions(compileAllowableActions(file, userReadOnly));
+    }
+
+    if (includeAcl) {
+      result.setAcl(compileAcl(file));
+      result.setIsExactAcl(true);
+    }
+
+    return result;
+  }
+
+  /**
+   * Gathers all base properties of a file or folder.
+   */
+  private PropertiesData compileProperties(File file, Set<String> orgfilter,
+      ObjectInfoHolder objectInfos) {
+    if (file == null) {
+      throw new IllegalArgumentException("File must not be null!");
+    }
+
+    // we can gather properties if the file or folder doesn't exist
+    if (!file.exists()) {
+      throw new CmisObjectNotFoundException("Object not found!");
+    }
+
+    // copy filter
+    Set<String> filter = (orgfilter == null ? null : new HashSet<String>(orgfilter));
+
+    // find base type
+    String typeId = null;
+    ObjectInfoImpl objectInfo = null;
+
+    if (file.isDirectory()) {
+      typeId = TypeManager.FOLDER_TYPE_ID;
+      objectInfo = new FolderInfo();
+      objectInfo.setTypeId(typeId);
+    }
+    else {
+      typeId = TypeManager.DOCUMENT_TYPE_ID;
+      objectInfo = new DocumentInfo();
+      objectInfo.setTypeId(typeId);
+    }
+
+    // let's do it
+    try {
+      PropertiesDataImpl result = new PropertiesDataImpl();
+
+      // id
+      String id = fileToId(file);
+      addPropertyId(result, typeId, filter, PropertyIds.CMIS_OBJECT_ID, id);
+      objectInfo.setId(id);
+
+      // name
+      String name = file.getName();
+      addPropertyString(result, typeId, filter, PropertyIds.CMIS_NAME, name);
+      objectInfo.setName(name);
+
+      // created and modified by
+      addPropertyString(result, typeId, filter, PropertyIds.CMIS_CREATED_BY, USER_UNKNOWN);
+      addPropertyString(result, typeId, filter, PropertyIds.CMIS_LAST_MODIFIED_BY, USER_UNKNOWN);
+      objectInfo.setCreatedBy(USER_UNKNOWN);
+
+      // creation and modification date
+      GregorianCalendar lastModified = millisToCalendar(file.lastModified());
+      addPropertyDateTime(result, typeId, filter, PropertyIds.CMIS_CREATION_DATE, lastModified);
+      addPropertyDateTime(result, typeId, filter, PropertyIds.CMIS_LAST_MODIFICATION_DATE,
+          lastModified);
+      objectInfo.setCreationDate(lastModified);
+      objectInfo.setLastModificationDate(lastModified);
+
+      // directory or file
+      if (file.isDirectory()) {
+        // base type and type name
+        addPropertyId(result, typeId, filter, PropertyIds.CMIS_BASE_TYPE_ID,
+            BaseObjectTypeIds.CMIS_FOLDER.value());
+        addPropertyId(result, typeId, filter, PropertyIds.CMIS_OBJECT_TYPE_ID,
+            TypeManager.FOLDER_TYPE_ID);
+        String path = getRepositoryPath(file);
+        addPropertyString(result, typeId, filter, PropertyIds.CMIS_PATH, (path.length() == 0 ? "/"
+            : path));
+
+        // folder properties
+        if (!fRoot.equals(file)) {
+          addPropertyId(result, typeId, filter, PropertyIds.CMIS_PARENT_ID, (fRoot.equals(file
+              .getParentFile()) ? ROOT_ID : fileToId(file.getParentFile())));
+          objectInfo.setHasParent(true);
+        }
+        else {
+          objectInfo.setHasParent(false);
+        }
+      }
+      else {
+        // base type and type name
+        addPropertyId(result, typeId, filter, PropertyIds.CMIS_BASE_TYPE_ID,
+            BaseObjectTypeIds.CMIS_DOCUMENT.value());
+        addPropertyId(result, typeId, filter, PropertyIds.CMIS_OBJECT_TYPE_ID,
+            TypeManager.DOCUMENT_TYPE_ID);
+
+        // file properties
+        addPropertyBoolean(result, typeId, filter, PropertyIds.CMIS_IS_IMMUTABLE, false);
+        addPropertyBoolean(result, typeId, filter, PropertyIds.CMIS_IS_LATEST_VERSION, true);
+        addPropertyBoolean(result, typeId, filter, PropertyIds.CMIS_IS_MAJOR_VERSION, true);
+        addPropertyBoolean(result, typeId, filter, PropertyIds.CMIS_IS_LATEST_MAJOR_VERSION, true);
+        addPropertyString(result, typeId, filter, PropertyIds.CMIS_VERSION_LABEL, file.getName());
+        addPropertyId(result, typeId, filter, PropertyIds.CMIS_VERSION_SERIES_ID, fileToId(file));
+        addPropertyString(result, typeId, filter, PropertyIds.CMIS_CHECKIN_COMMENT, "");
+        addPropertyInteger(result, typeId, filter, PropertyIds.CMIS_CONTENT_STREAM_LENGTH, file
+            .length());
+        addPropertyString(result, typeId, filter, PropertyIds.CMIS_CONTENT_STREAM_MIME_TYPE,
+            MIMETypes.getMIMEType(file));
+        addPropertyString(result, typeId, filter, PropertyIds.CMIS_CONTENT_STREAM_FILE_NAME, file
+            .getName());
+
+        objectInfo.setContentType(MIMETypes.getMIMEType(file));
+        objectInfo.setFileName(file.getName());
+      }
+
+      // read custom properties
+      readCustomProperties(file, result, filter, objectInfo);
+
+      if (filter != null) {
+        if (!filter.isEmpty()) {
+          debug("Unknown filter properties: " + filter.toString(), null);
+        }
+      }
+
+      if (objectInfos != null) {
+        objectInfos.addObjectInfo(objectInfo);
+      }
+
+      return result;
+    }
+    catch (Exception e) {
+      if (e instanceof CmisBaseException) {
+        throw (CmisBaseException) e;
+      }
+      throw new CmisRuntimeException(e.getMessage());
+    }
+  }
+
+  /**
+   * Reads and adds properties.
+   */
+  @SuppressWarnings("unchecked")
+  private void readCustomProperties(File file, PropertiesDataImpl properties, Set<String> filter,
+      ObjectInfoImpl objectInfo) {
+    File propFile = getPropertiesFile(file);
+
+    // if it doesn't exists, ignore it
+    if (!propFile.exists()) {
+      return;
+    }
+
+    // parse it
+    JAXBElement<CmisObjectType> obj = null;
+    try {
+      Unmarshaller u = JaxBHelper.createUnmarshaller();
+      obj = (JAXBElement<CmisObjectType>) u.unmarshal(propFile);
+    }
+    catch (Exception e) {
+      warn("Unvalid CMIS properties: " + propFile.getAbsolutePath(), e);
+    }
+
+    if ((obj == null) || (obj.getValue() == null) || (obj.getValue().getProperties() == null)) {
+      return;
+    }
+
+    // add it to properties
+    for (CmisProperty cmisProp : obj.getValue().getProperties().getProperty()) {
+      PropertyData<?> prop = Converter.convert(cmisProp);
+
+      // overwrite object info
+      if (prop instanceof PropertyStringData) {
+        String firstValueStr = ((PropertyStringData) prop).getFirstValue();
+        if (PropertyIds.CMIS_NAME.equals(prop.getId())) {
+          objectInfo.setName(firstValueStr);
+        }
+        else if (PropertyIds.CMIS_OBJECT_TYPE_ID.equals(prop.getId())) {
+          objectInfo.setTypeId(firstValueStr);
+        }
+        else if (PropertyIds.CMIS_CREATED_BY.equals(prop.getId())) {
+          objectInfo.setCreatedBy(firstValueStr);
+        }
+        else if (PropertyIds.CMIS_CONTENT_STREAM_MIME_TYPE.equals(prop.getId())) {
+          objectInfo.setContentType(firstValueStr);
+        }
+        else if (PropertyIds.CMIS_CONTENT_STREAM_FILE_NAME.equals(prop.getId())) {
+          objectInfo.setFileName(firstValueStr);
+        }
+      }
+
+      if (prop instanceof PropertyDateTimeData) {
+        GregorianCalendar firstValueCal = ((PropertyDateTimeData) prop).getFirstValue();
+        if (PropertyIds.CMIS_CREATION_DATE.equals(prop.getId())) {
+          objectInfo.setCreationDate(firstValueCal);
+        }
+        else if (PropertyIds.CMIS_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.CMIS_OBJECT_ID.equals(prop.getId())) {
+        continue;
+      }
+
+      // don't overwrite base type
+      if (PropertyIds.CMIS_BASE_TYPE_ID.equals(prop.getId())) {
+        continue;
+      }
+
+      // add it
+      properties.addProperty(prop);
+    }
+  }
+
+  /**
+   * Checks and compiles a property set that can be written to disc.
+   */
+  private PropertiesData compileProperties(String typeId, String creator,
+      GregorianCalendar creationDate, String modifier, PropertiesData properties) {
+    PropertiesDataImpl result = new PropertiesDataImpl();
+    Set<String> addedProps = new HashSet<String>();
+
+    if ((properties == null) || (properties.getProperties() == null)) {
+      throw new CmisConstraintException("No properties!");
+    }
+
+    // get the property definitions
+    TypeDefinition type = fTypes.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!");
+      }
+
+      // skip type id
+      if (propType.getId().equals(PropertyIds.CMIS_OBJECT_TYPE_ID)) {
+        continue;
+      }
+
+      // 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.CMIS_OBJECT_TYPE_ID, typeId);
+    addPropertyString(result, typeId, null, PropertyIds.CMIS_CREATED_BY, creator);
+    addPropertyDateTime(result, typeId, null, PropertyIds.CMIS_CREATION_DATE, creationDate);
+    addPropertyString(result, typeId, null, PropertyIds.CMIS_LAST_MODIFIED_BY, modifier);
+
+    return result;
+  }
+
+  /**
+   * Checks and updates a property set that can be written to disc.
+   */
+  private PropertiesData updateProperties(String typeId, String creator,
+      GregorianCalendar creationDate, String modifier, PropertiesData oldProperties,
+      PropertiesData properties) {
+    PropertiesDataImpl result = new PropertiesDataImpl();
+
+    if (properties == null) {
+      throw new CmisConstraintException("No properties!");
+    }
+
+    // get the property definitions
+    TypeDefinition type = fTypes.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.CMIS_OBJECT_TYPE_ID, typeId);
+    addPropertyString(result, typeId, null, PropertyIds.CMIS_CREATED_BY, creator);
+    addPropertyDateTime(result, typeId, null, PropertyIds.CMIS_CREATION_DATE, creationDate);
+    addPropertyString(result, typeId, null, PropertyIds.CMIS_LAST_MODIFIED_BY, modifier);
+
+    return result;
+  }
+
+  private boolean isEmptyProperty(PropertyData<?> prop) {
+    if ((prop == null) || (prop.getValues() == null)) {
+      return true;
+    }
+
+    return prop.getValues().isEmpty();
+  }
+
+  private void addPropertyId(PropertiesDataImpl props, String typeId, Set<String> filter,
+      String id, String value) {
+    if (!checkAddProperty(props, typeId, filter, id)) {
+      return;
+    }
+
+    if (value == null) {
+      throw new IllegalArgumentException("Value must not be null!");
+    }
+
+    props.addProperty(new PropertyIdDataImpl(id, value));
+  }
+
+  private void addPropertyString(PropertiesDataImpl props, String typeId, Set<String> filter,
+      String id, String value) {
+    if (!checkAddProperty(props, typeId, filter, id)) {
+      return;
+    }
+
+    props.addProperty(new PropertyStringDataImpl(id, value));
+  }
+
+  private void addPropertyInteger(PropertiesDataImpl props, String typeId, Set<String> filter,
+      String id, long value) {
+    if (!checkAddProperty(props, typeId, filter, id)) {
+      return;
+    }
+
+    props.addProperty(new PropertyIntegerDataImpl(id, BigInteger.valueOf(value)));
+  }
+
+  private void addPropertyBoolean(PropertiesDataImpl props, String typeId, Set<String> filter,
+      String id, boolean value) {
+    if (!checkAddProperty(props, typeId, filter, id)) {
+      return;
+    }
+
+    props.addProperty(new PropertyBooleanDataImpl(id, value));
+  }
+
+  private void addPropertyDateTime(PropertiesDataImpl props, String typeId, Set<String> filter,
+      String id, GregorianCalendar value) {
+    if (!checkAddProperty(props, typeId, filter, id)) {
+      return;
+    }
+
+    props.addProperty(new PropertyDateTimeDataImpl(id, value));
+  }
+
+  private boolean checkAddProperty(PropertiesData 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 = fTypes.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 boolean addPropertyDefault(PropertiesDataImpl 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 PropertyBooleanDataImpl(propDef.getId(), (List<Boolean>) defaultValue));
+        break;
+      case DATETIME:
+        props.addProperty(new PropertyDateTimeDataImpl(propDef.getId(),
+            (List<GregorianCalendar>) defaultValue));
+        break;
+      case DECIMAL:
+        props.addProperty(new PropertyDecimalDataImpl(propDef.getId(),
+            (List<BigDecimal>) defaultValue));
+        break;
+      case HTML:
+        props.addProperty(new PropertyHtmlDataImpl(propDef.getId(), (List<String>) defaultValue));
+        break;
+      case ID:
+        props.addProperty(new PropertyIdDataImpl(propDef.getId(), (List<String>) defaultValue));
+        break;
+      case INTEGER:
+        props.addProperty(new PropertyIntegerDataImpl(propDef.getId(),
+            (List<BigInteger>) defaultValue));
+        break;
+      case STRING:
+        props.addProperty(new PropertyStringDataImpl(propDef.getId(), (List<String>) defaultValue));
+        break;
+      case URI:
+        props.addProperty(new PropertyUriDataImpl(propDef.getId(), (List<String>) defaultValue));
+        break;
+      default:
+        throw new RuntimeException("Unknown datatype! Spec change?");
+      }
+
+      return true;
+    }
+
+    return false;
+  }
+
+  /**
+   * Compiles the allowable actions for a file or folder.
+   */
+  private AllowableActionsData compileAllowableActions(File file, boolean userReadOnly) {
+    if (file == null) {
+      throw new IllegalArgumentException("File must not be null!");
+    }
+
+    // we can gather properties if the file or folder doesn't exist
+    if (!file.exists()) {
+      throw new CmisObjectNotFoundException("Object not found!");
+    }
+
+    boolean isReadOnly = !file.canWrite();
+    boolean isFolder = file.isDirectory();
+    boolean isRoot = fRoot.equals(file);
+
+    Map<String, Boolean> aam = new HashMap<String, Boolean>();
+
+    aam.put(AllowableActionsData.ACTION_CAN_GET_OBJECT_PARENTS, !isRoot);
+    aam.put(AllowableActionsData.ACTION_CAN_CREATE_RELATIONSHIP, false);
+    aam.put(AllowableActionsData.ACTION_CAN_GET_PROPERTIES, true);
+    aam.put(AllowableActionsData.ACTION_CAN_GET_RENDITIONS, false);
+    aam.put(AllowableActionsData.ACTION_CAN_UPDATE_PROPERTIES, !userReadOnly && !isReadOnly);
+    aam.put(AllowableActionsData.ACTION_CAN_MOVE_OBJECT, !userReadOnly);
+    aam.put(AllowableActionsData.ACTION_CAN_DELETE_OBJECT, !userReadOnly && !isReadOnly);
+    aam.put(AllowableActionsData.ACTION_CAN_GET_OBJECT_RELATIONSHIPS, false);
+    aam.put(AllowableActionsData.ACTION_CAN_APPLY_POLICY, false);
+    aam.put(AllowableActionsData.ACTION_CAN_REMOVE_POLICY, false);
+    aam.put(AllowableActionsData.ACTION_CAN_GET_ACL, true);
+    aam.put(AllowableActionsData.ACTION_CAN_APPLY_ACL, false);
+
+    if (isFolder) {
+      aam.put(AllowableActionsData.ACTION_CAN_GET_DESCENDANTS, true);
+      aam.put(AllowableActionsData.ACTION_CAN_GET_CHILDREN, true);
+      aam.put(AllowableActionsData.ACTION_CAN_GET_FOLDER_PARENT, !isRoot);
+      aam.put(AllowableActionsData.ACTION_CAN_GET_FOLDER_TREE, true);
+      aam.put(AllowableActionsData.ACTION_CAN_CREATE_DOCUMENT, !userReadOnly);
+      aam.put(AllowableActionsData.ACTION_CAN_CREATE_FOLDER, !userReadOnly);
+      aam.put(AllowableActionsData.ACTION_CAN_CREATE_POLICY, false);
+      aam.put(AllowableActionsData.ACTION_CAN_DELETE_TREE, !userReadOnly && !isReadOnly);
+    }
+    else {
+      aam.put(AllowableActionsData.ACTION_CAN_GET_CONTENT_STREAM, true);
+      aam.put(AllowableActionsData.ACTION_CAN_SET_CONTENT_STREAM, !userReadOnly && !isReadOnly);
+      aam.put(AllowableActionsData.ACTION_CAN_DELETE_CONTENT_STREAM, !userReadOnly && !isReadOnly);
+      aam.put(AllowableActionsData.ACTION_CAN_ADD_OBJECT_TO_FOLDER, false);
+      aam.put(AllowableActionsData.ACTION_CAN_REMOVE_OBJECT_FROM_FOLDER, false);
+      aam.put(AllowableActionsData.ACTION_CAN_CHECK_OUT, false);
+      aam.put(AllowableActionsData.ACTION_CAN_CANCEL_CHECK_OUT, false);
+      aam.put(AllowableActionsData.ACTION_CAN_CHECK_IN, false);
+      aam.put(AllowableActionsData.ACTION_CAN_GET_ALL_VERSIONS, true);
+    }
+
+    AllowableActionsDataImpl result = new AllowableActionsDataImpl();
+    result.setAllowableActions(aam);
+
+    return result;
+  }
+
+  /**
+   * Compiles the ACL for a file or folder.
+   */
+  private AccessControlList compileAcl(File file) {
+    AccessControlListImpl result = new AccessControlListImpl();
+    result.setAces(new ArrayList<AccessControlEntry>());
+
+    for (Map.Entry<String, Boolean> ue : fUserMap.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 (!ue.getValue().booleanValue() && file.canWrite()) {
+        entry.getPermissions().add(CMIS_WRITE);
+        entry.getPermissions().add(CMIS_ALL);
+      }
+
+      entry.setDirect(true);
+
+      // add ACE
+      result.getAces().add(entry);
+    }
+
+    return result;
+  }
+
+  /**
+   * Writes the properties for a document or folder.
+   */
+  private void writePropertiesFile(File file, PropertiesData properties) {
+    File propFile = getPropertiesFile(file);
+
+    // if no properties set delete the properties file
+    if ((properties == null) || (properties.getProperties() == null)
+        || (properties.getProperties().size() == 0)) {
+      propFile.delete();
+      return;
+    }
+
+    // create object
+    CmisObjectType object = new CmisObjectType();
+    object.setProperties(Converter.convert(properties));
+
+    // write it
+    try {
+      JaxBHelper.CMIS_EXTRA_OBJECT_FACTORY.createObject(object);
+      JAXBElement<CmisObjectType> objElement = JaxBHelper.CMIS_EXTRA_OBJECT_FACTORY
+          .createObject(object);
+
+      Marshaller m = JaxBHelper.createMarshaller();
+      m.setProperty("jaxb.formatted.output", true);
+      m.marshal(objElement, propFile);
+    }
+    catch (Exception e) {
+      throw new CmisStorageException("Couldn't store properties!", e);
+    }
+  }
+
+  // --- internal stuff ---
+
+  /**
+   * Converts milliseconds into a calendar object.
+   */
+  private GregorianCalendar millisToCalendar(long millis) {
+    GregorianCalendar result = new GregorianCalendar();
+    result.setTimeZone(TimeZone.getTimeZone("GMT"));
+    result.setTimeInMillis(millis);
+
+    return result;
+  }
+
+  /**
+   * 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 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.CMIS_OBJECT_ID);
+    result.add(PropertyIds.CMIS_OBJECT_TYPE_ID);
+    result.add(PropertyIds.CMIS_BASE_TYPE_ID);
+
+    return result;
+  }
+
+  /**
+   * Gets the type id from a set of properties.
+   */
+  private String getTypeId(PropertiesData properties) {
+    PropertyData<?> typeProperty = properties.getProperties().get(PropertyIds.CMIS_OBJECT_TYPE_ID);
+    if (!(typeProperty instanceof PropertyIdData)) {
+      throw new CmisInvalidArgumentException("Type id must be set!");
+    }
+
+    String typeId = ((PropertyIdData) typeProperty).getFirstValue();
+    if (typeId == null) {
+      throw new CmisInvalidArgumentException("Type id must be set!");
+    }
+
+    return typeId;
+  }
+
+  /**
+   * Returns the first value of an id property.
+   */
+  private String getIdProperty(PropertiesData properties, String name) {
+    PropertyData<?> property = properties.getProperties().get(name);
+    if (!(property instanceof PropertyIdData)) {
+      return null;
+    }
+
+    return ((PropertyIdData) property).getFirstValue();
+  }
+
+  /**
+   * Returns the first value of an string property.
+   */
+  private String getStringProperty(PropertiesData properties, String name) {
+    PropertyData<?> property = properties.getProperties().get(name);
+    if (!(property instanceof PropertyStringData)) {
+      return null;
+    }
+
+    return ((PropertyStringData) property).getFirstValue();
+  }
+
+  /**
+   * Returns the first value of an datetime property.
+   */
+  private GregorianCalendar getDateTimeProperty(PropertiesData properties, String name) {
+    PropertyData<?> property = properties.getProperties().get(name);
+    if (!(property instanceof PropertyDateTimeData)) {
+      return null;
+    }
+
+    return ((PropertyDateTimeData) property).getFirstValue();
+  }
+
+  /**
+   * Checks if the user in the given context is valid for this repository and if the user has the
+   * required permissions.
+   */
+  private boolean checkUser(CallContext context, boolean writeRequired) {
+    if (context == null) {
+      throw new CmisPermissionDeniedException("No user context!");
+    }
+
+    Boolean readOnly = fUserMap.get(context.getUsername());
+    if (readOnly == null) {
+      throw new CmisPermissionDeniedException("Unknown user!");
+    }
+
+    if (readOnly.booleanValue() && writeRequired) {
+      throw new CmisPermissionDeniedException("No write permission!");
+    }
+
+    return readOnly.booleanValue();
+  }
+
+  /**
+   * Returns the properties file of the given file.
+   */
+  private File getPropertiesFile(File file) {
+    if (file.isDirectory()) {
+      return new File(file, SHADOW_FOLDER);
+    }
+
+    return new File(file.getAbsolutePath() + SHADOW_EXT);
+  }
+
+  /**
+   * Returns the File object by id or throws an appropriate exception.
+   */
+  private File getFile(String id) {
+    try {
+      return idToFile(id);
+    }
+    catch (Exception e) {
+      throw new CmisObjectNotFoundException(e.getMessage(), e);
+    }
+  }
+
+  /**
+   * Converts an id to a File object. A simple and insecure implementation, but good enough for now.
+   */
+  private File idToFile(String id) throws Exception {
+    if ((id == null) || (id.length() == 0)) {
+      throw new CmisInvalidArgumentException("Id is not valid!");
+    }
+
+    if (id.equals(ROOT_ID)) {
+      return fRoot;
+    }
+
+    return new File(fRoot, (new String(Base64.decodeBase64(id.getBytes("ISO-8859-1")), "UTF-8"))
+        .replace('/', File.separatorChar));
+  }
+
+  /**
+   * Returns the id of a File object or throws an appropriate exception.
+   */
+  private String getId(File file) {
+    try {
+      return fileToId(file);
+    }
+    catch (Exception e) {
+      throw new CmisRuntimeException(e.getMessage());
+    }
+  }
+
+  /**
+   * Creates a File object from an id. A simple and insecure implementation, but good enough for
+   * now.
+   */
+  private String fileToId(File file) throws Exception {
+    if (file == null) {
+      throw new IllegalArgumentException("File is not valid!");
+    }
+
+    if (fRoot.equals(file)) {
+      return ROOT_ID;
+    }
+
+    String path = getRepositoryPath(file);
+
+    return new String(Base64.encodeBase64(path.getBytes("UTF-8")), "ISO-8859-1");
+  }
+
+  private String getRepositoryPath(File file) {
+    return file.getAbsolutePath().substring(fRoot.getAbsolutePath().length()).replace(
+        File.separatorChar, '/');
+  }
+
+  private void warn(String msg, Throwable t) {
+    log.warn("<" + fRepositoryId + "> " + msg, t);
+  }
+
+  private void debug(String msg) {
+    debug(msg, null);
+  }
+
+  private void debug(String msg, Throwable t) {
+    log.debug("<" + fRepositoryId + "> " + msg, t);
+  }
+}

Propchange: incubator/chemistry/trunk/opencmis/opencmis-server/opencmis-server-fileshare/src/main/java/org/apache/opencmis/fileshare/FileShareRepository.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: incubator/chemistry/trunk/opencmis/opencmis-server/opencmis-server-fileshare/src/main/java/org/apache/opencmis/fileshare/FolderInfo.java
URL: http://svn.apache.org/viewvc/incubator/chemistry/trunk/opencmis/opencmis-server/opencmis-server-fileshare/src/main/java/org/apache/opencmis/fileshare/FolderInfo.java?rev=910572&view=auto
==============================================================================
--- incubator/chemistry/trunk/opencmis/opencmis-server/opencmis-server-fileshare/src/main/java/org/apache/opencmis/fileshare/FolderInfo.java (added)
+++ incubator/chemistry/trunk/opencmis/opencmis-server/opencmis-server-fileshare/src/main/java/org/apache/opencmis/fileshare/FolderInfo.java Tue Feb 16 16:03:38 2010
@@ -0,0 +1,44 @@
+/*
+ * 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.apache.opencmis.fileshare;
+
+import org.apache.opencmis.commons.enums.BaseObjectTypeIds;
+import org.apache.opencmis.server.spi.ObjectInfoImpl;
+
+public class FolderInfo extends ObjectInfoImpl {
+
+  public FolderInfo() {
+    setBaseType(BaseObjectTypeIds.CMIS_FOLDER);
+    setContentType(null);
+    setFileName(null);
+    setHasAcl(true);
+    setHasContent(false);
+    setHasVersionHistory(false);
+    setIsCurrentVersion(true);
+    setRelationshipSourceIds(null);
+    setRelationshipTargetIds(null);
+    setRenditionInfos(null);
+    setSupportsDescendants(true);
+    setSupportsFolderTree(true);
+    setSupportsPolicies(false);
+    setSupportsRelationships(false);
+    setWorkingCopyId(null);
+    setWorkingCopyOriginalId(null);
+  }
+}

Propchange: incubator/chemistry/trunk/opencmis/opencmis-server/opencmis-server-fileshare/src/main/java/org/apache/opencmis/fileshare/FolderInfo.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: incubator/chemistry/trunk/opencmis/opencmis-server/opencmis-server-fileshare/src/main/java/org/apache/opencmis/fileshare/MIMETypes.java
URL: http://svn.apache.org/viewvc/incubator/chemistry/trunk/opencmis/opencmis-server/opencmis-server-fileshare/src/main/java/org/apache/opencmis/fileshare/MIMETypes.java?rev=910572&view=auto
==============================================================================
--- incubator/chemistry/trunk/opencmis/opencmis-server/opencmis-server-fileshare/src/main/java/org/apache/opencmis/fileshare/MIMETypes.java (added)
+++ incubator/chemistry/trunk/opencmis/opencmis-server/opencmis-server-fileshare/src/main/java/org/apache/opencmis/fileshare/MIMETypes.java Tue Feb 16 16:03:38 2010
@@ -0,0 +1,240 @@
+/*
+ * 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.apache.opencmis.fileshare;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+public class MIMETypes {
+
+  private static Map<String, String> EXT2MIME = new HashMap<String, String>();
+
+  static {
+    EXT2MIME.put("", "application/octet-stream");
+    EXT2MIME.put("ai", "application/postscript");
+    EXT2MIME.put("aif", "audio/x-aiff");
+    EXT2MIME.put("aifc", "audio/x-aiff");
+    EXT2MIME.put("aiff", "audio/x-aiff");
+    EXT2MIME.put("asf", "video/x-ms-asf");
+    EXT2MIME.put("asr", "video/x-ms-asf");
+    EXT2MIME.put("asx", "video/x-ms-asf");
+    EXT2MIME.put("au", "audio/basic");
+    EXT2MIME.put("avi", "video/x-msvideo");
+    EXT2MIME.put("axs", "application/olescript");
+    EXT2MIME.put("bas", "text/plain");
+    EXT2MIME.put("bmp", "image/bmp");
+    EXT2MIME.put("c", "text/plain");
+    EXT2MIME.put("cat", "application/vnd.ms-pkiseccat");
+    EXT2MIME.put("cdf", "application/x-cdf");
+    EXT2MIME.put("cer", "application/x-x509-ca-cert");
+    EXT2MIME.put("clp", "application/x-msclip");
+    EXT2MIME.put("cmx", "image/x-cmx");
+    EXT2MIME.put("cod", "image/cis-cod");
+    EXT2MIME.put("cpio", "application/x-cpio");
+    EXT2MIME.put("crd", "application/x-mscardfile");
+    EXT2MIME.put("crl", "application/pkix-crl");
+    EXT2MIME.put("crt", "application/x-x509-ca-cert");
+    EXT2MIME.put("csh", "application/x-csh");
+    EXT2MIME.put("css", "text/css");
+    EXT2MIME.put("dll", "application/x-msdownload");
+    EXT2MIME.put("doc", "application/msword");
+    EXT2MIME.put("docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
+    EXT2MIME.put("doct", "application/vnd.openxmlformats-officedocument.wordprocessingml.template");
+    EXT2MIME.put("dot", "application/msword");
+    EXT2MIME.put("dvi", "application/x-dvi");
+    EXT2MIME.put("dxr", "application/x-director");
+    EXT2MIME.put("eps", "application/postscript");
+    EXT2MIME.put("etx", "text/x-setext");
+    EXT2MIME.put("evy", "application/envoy");
+    EXT2MIME.put("fif", "application/fractals");
+    EXT2MIME.put("flr", "x-world/x-vrml");
+    EXT2MIME.put("gif", "image/gif");
+    EXT2MIME.put("gtar", "application/x-gtar");
+    EXT2MIME.put("gz", "application/x-gzip");
+    EXT2MIME.put("h", "text/plain");
+    EXT2MIME.put("hdf", "application/x-hdf");
+    EXT2MIME.put("hlp", "application/winhlp");
+    EXT2MIME.put("hqx", "application/mac-binhex40");
+    EXT2MIME.put("hta", "application/hta");
+    EXT2MIME.put("htc", "text/x-component");
+    EXT2MIME.put("htm", "text/html");
+    EXT2MIME.put("html", "text/html");
+    EXT2MIME.put("htt", "text/webviewhtml");
+    EXT2MIME.put("ico", "image/x-icon");
+    EXT2MIME.put("ief", "image/ief");
+    EXT2MIME.put("iii", "application/x-iphone");
+    EXT2MIME.put("isp", "application/x-internet-signup");
+    EXT2MIME.put("jfif", "image/pipeg");
+    EXT2MIME.put("jpe", "image/jpeg");
+    EXT2MIME.put("jpeg", "image/jpeg");
+    EXT2MIME.put("jpg", "image/jpeg");
+    EXT2MIME.put("js", "application/x-javascript");
+    EXT2MIME.put("latex", "application/x-latex");
+    EXT2MIME.put("lsf", "video/x-la-asf");
+    EXT2MIME.put("lsx", "video/x-la-asf");
+    EXT2MIME.put("m3u", "audio/x-mpegurl");
+    EXT2MIME.put("man", "application/x-troff-man");
+    EXT2MIME.put("mdb", "application/x-msaccess");
+    EXT2MIME.put("me", "application/x-troff-me");
+    EXT2MIME.put("mhtv", "message/rfc822");
+    EXT2MIME.put("mhtml", "message/rfc822");
+    EXT2MIME.put("mid", "audio/mid");
+    EXT2MIME.put("mov", "video/quicktime");
+    EXT2MIME.put("movie", "video/x-sgi-movie");
+    EXT2MIME.put("mp2", "video/mpeg");
+    EXT2MIME.put("mp3", "audio/mpeg");
+    EXT2MIME.put("mpa", "video/mpeg");
+    EXT2MIME.put("mpe", "video/mpegv");
+    EXT2MIME.put("mpeg", "video/mpeg");
+    EXT2MIME.put("mpg", "video/mpegv");
+    EXT2MIME.put("mpp", "application/vnd.ms-project");
+    EXT2MIME.put("mpv2", "video/mpeg");
+    EXT2MIME.put("ms", "application/x-troff-ms");
+    EXT2MIME.put("mvb", "application/x-msmediaview");
+    EXT2MIME.put("nws", "message/rfc822");
+    EXT2MIME.put("oda", "application/oda");
+    EXT2MIME.put("p10", "application/pkcs10");
+    EXT2MIME.put("p12", "application/x-pkcs12v");
+    EXT2MIME.put("p7b", "application/x-pkcs7-certificates");
+    EXT2MIME.put("p7c", "application/x-pkcs7-mime");
+    EXT2MIME.put("p7m", "application/x-pkcs7-mime");
+    EXT2MIME.put("p7r", "application/x-pkcs7-certreqresp");
+    EXT2MIME.put("p7s", "application/x-pkcs7-signature");
+    EXT2MIME.put("pbm", "image/x-portable-bitmap");
+    EXT2MIME.put("pdf", "application/pdf");
+    EXT2MIME.put("pfx", "application/x-pkcs12");
+    EXT2MIME.put("pgm", "image/x-portable-graymap");
+    EXT2MIME.put("vpko", "application/ynd.ms-pkipko");
+    EXT2MIME.put("pma", "application/x-perfmon");
+    EXT2MIME.put("pmc", "application/x-perfmon");
+    EXT2MIME.put("pml", "application/x-perfmon");
+    EXT2MIME.put("pmr", "application/x-perfmon");
+    EXT2MIME.put("pmw", "application/x-perfmon");
+    EXT2MIME.put("png", "image/png");
+    EXT2MIME.put("pnm", "image/x-portable-anymap");
+    EXT2MIME.put("pot", "application/vnd.ms-powerpoint");
+    EXT2MIME.put("ppm", "image/x-portable-pixmap");
+    EXT2MIME.put("pps", "application/vnd.ms-powerpoint");
+    EXT2MIME.put("ppt", "application/vnd.ms-powerpoint");
+    EXT2MIME.put("pptx",
+        "application/vnd.openxmlformats-officedocument.presentationml.presentation");
+    EXT2MIME.put("ppsx", "application/vnd.openxmlformats-officedocument.presentationml.slideshow");
+    EXT2MIME.put("potx", "application/vnd.openxmlformats-officedocument.presentationml.template");
+    EXT2MIME.put("prf", "application/pics-rules");
+    EXT2MIME.put("ps", "application/postscript");
+    EXT2MIME.put("pub", "application/x-mspublisher");
+    EXT2MIME.put("qt", "video/quicktime");
+    EXT2MIME.put("ra", "audio/x-pn-realaudio");
+    EXT2MIME.put("ram", "audio/x-pn-realaudio");
+    EXT2MIME.put("ras", "image/x-cmu-raster");
+    EXT2MIME.put("rgb", "image/x-rgb");
+    EXT2MIME.put("rmi", "audio/mid");
+    EXT2MIME.put("roff", "application/x-troff");
+    EXT2MIME.put("rtf", "application/rtf");
+    EXT2MIME.put("rtx", "text/richtext");
+    EXT2MIME.put("scd", "application/x-msschedule");
+    EXT2MIME.put("sct", "text/scriptlet");
+    EXT2MIME.put("sh", "application/x-sh");
+    EXT2MIME.put("shar", "application/x-shar");
+    EXT2MIME.put("sit", "application/x-stuffit");
+    EXT2MIME.put("snd", "audio/basic");
+    EXT2MIME.put("spc", "application/x-pkcs7-certificates");
+    EXT2MIME.put("spl", "application/futuresplash");
+    EXT2MIME.put("src", "application/x-wais-source");
+    EXT2MIME.put("sst", "application/vnd.ms-pkicertstore");
+    EXT2MIME.put("stl", "application/vnd.ms-pkistl");
+    EXT2MIME.put("stm", "text/html");
+    EXT2MIME.put("svg", "image/svg+xml");
+    EXT2MIME.put("swf", "application/x-shockwave-flash");
+    EXT2MIME.put("t", "application/x-troff");
+    EXT2MIME.put("tar", "application/x-tar");
+    EXT2MIME.put("tcl", "application/x-tcl");
+    EXT2MIME.put("tex", "application/x-tex");
+    EXT2MIME.put("texi", "application/x-texinfo");
+    EXT2MIME.put("texinfo", "application/x-texinfo");
+    EXT2MIME.put("tgz", "application/x-compressed");
+    EXT2MIME.put("tif", "image/tiff");
+    EXT2MIME.put("tiff", "image/tiff");
+    EXT2MIME.put("tr", "application/x-troff");
+    EXT2MIME.put("trm", "application/x-msterminal");
+    EXT2MIME.put("tsv", "text/tab-separated-values");
+    EXT2MIME.put("txt", "text/plain");
+    EXT2MIME.put("uls", "text/iuls");
+    EXT2MIME.put("ustar", "application/x-ustar");
+    EXT2MIME.put("vcf", "text/x-vcard");
+    EXT2MIME.put("vrml", "x-world/x-vrml");
+    EXT2MIME.put("wav", "audio/x-wav");
+    EXT2MIME.put("wcm", "application/vnd.ms-works");
+    EXT2MIME.put("wdb", "application/vnd.ms-works");
+    EXT2MIME.put("wmf", "application/x-msmetafile");
+    EXT2MIME.put("wps", "application/vnd.ms-works");
+    EXT2MIME.put("wri", "application/x-mswrite");
+    EXT2MIME.put("wrl", "x-world/x-vrml");
+    EXT2MIME.put("wrz", "x-world/x-vrml");
+    EXT2MIME.put("xaf", "x-world/x-vrml");
+    EXT2MIME.put("xbm", "image/x-xbitmap");
+    EXT2MIME.put("xla", "application/vnd.ms-excel");
+    EXT2MIME.put("xlc", "application/vnd.ms-excel");
+    EXT2MIME.put("xlm", "application/vnd.ms-excel");
+    EXT2MIME.put("xls", "application/vnd.ms-excel");
+    EXT2MIME.put("xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+    EXT2MIME.put("xlt", "application/vnd.ms-excel");
+    EXT2MIME.put("xltx", "application/vnd.openxmlformats-officedocument.spreadsheetml.template");
+    EXT2MIME.put("xlw", "application/vnd.ms-excel");
+    EXT2MIME.put("xml", "text/xml");
+    EXT2MIME.put("xof", "x-world/x-vrml");
+    EXT2MIME.put("xpm", "image/x-xpixmap");
+    EXT2MIME.put("xwd", "image/x-xwindowdump");
+    EXT2MIME.put("z", "application/x-compress");
+    EXT2MIME.put("zip", "application/zip");
+  }
+
+  /**
+   * Returns the MIME type for file extension.
+   */
+  public static String getMIMEType(String ext) {
+    if (ext == null) {
+      return EXT2MIME.get("");
+    }
+
+    int x = ext.lastIndexOf('.');
+    if (x > -1) {
+      ext = ext.substring(x + 1);
+    }
+
+    String mime = EXT2MIME.get(ext.toLowerCase());
+    if (mime == null) {
+      mime = EXT2MIME.get("");
+    }
+
+    return mime;
+  }
+
+  /**
+   * Returns the MIME type for a file.
+   */
+  public static String getMIMEType(File file) {
+    if (file == null) {
+      return getMIMEType("");
+    }
+
+    return getMIMEType(file.getName());
+  }
+}

Propchange: incubator/chemistry/trunk/opencmis/opencmis-server/opencmis-server-fileshare/src/main/java/org/apache/opencmis/fileshare/MIMETypes.java
------------------------------------------------------------------------------
    svn:eol-style = native