content: /*
   * 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.chemistry.opencmis.inmemory.storedobj.impl;
  
  import java.io.IOException;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.Comparator;
  import java.util.GregorianCalendar;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  import java.util.concurrent.ConcurrentHashMap;
  import java.util.concurrent.locks.Lock;
  import java.util.concurrent.locks.ReentrantLock;
  
  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.ContentStream;
  import org.apache.chemistry.opencmis.commons.data.PropertyData;
  import org.apache.chemistry.opencmis.commons.data.RenditionData;
  import org.apache.chemistry.opencmis.commons.enums.AclPropagation;
  import org.apache.chemistry.opencmis.commons.enums.BaseTypeId;
  import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships;
  import org.apache.chemistry.opencmis.commons.enums.RelationshipDirection;
  import org.apache.chemistry.opencmis.commons.enums.VersioningState;
  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.inmemory.ConfigConstants;
  import org.apache.chemistry.opencmis.inmemory.ConfigurationSettings;
  import org.apache.chemistry.opencmis.inmemory.storedobj.api.Content;
  import org.apache.chemistry.opencmis.inmemory.storedobj.api.Document;
  import org.apache.chemistry.opencmis.inmemory.storedobj.api.DocumentVersion;
  import org.apache.chemistry.opencmis.inmemory.storedobj.api.Fileable;
  import org.apache.chemistry.opencmis.inmemory.storedobj.api.Filing;
  import org.apache.chemistry.opencmis.inmemory.storedobj.api.Folder;
  import org.apache.chemistry.opencmis.inmemory.storedobj.api.MultiFiling;
  import org.apache.chemistry.opencmis.inmemory.storedobj.api.ObjectStore;
  import org.apache.chemistry.opencmis.inmemory.storedobj.api.Relationship;
  import org.apache.chemistry.opencmis.inmemory.storedobj.api.StoredObject;
  import org.apache.chemistry.opencmis.inmemory.storedobj.api.VersionedDocument;
  import org.apache.chemistry.opencmis.inmemory.types.DefaultTypeSystemCreator;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  /**
   * The object store is the central core of the in-memory repository. It is based
   * on huge HashMap map mapping ids to objects in memory. To allow access from
   * multiple threads a Java concurrent HashMap is used that allows parallel
   * access methods.
   * <p>
   * Certain methods in the in-memory repository must guarantee constraints. For
   * example a folder enforces that each child has a unique name. Therefore
   * certain operations must occur in an atomic manner. In the example it must be
   * guaranteed that no write access occurs to the map between acquiring the
   * iterator to find the children and finishing the add operation when no name
   * conflicts can occur. For this purpose this class has methods to lock an
   * unlock the state of the repository. It is very important that the caller
   * acquiring the lock enforces an unlock under all circumstances. Typical code
   * is:
   * 
   * <pre>
   * {@code
   * ObjectStoreImpl os = ... ;
   * try {
   *     os.lock();
   * } finally {
   *     os.unlock();
   * }
   * }
   * </pre>
   * 
   * The locking is very coarse-grained. Productive implementations would probably
   * implement finer grained locks on a folder or document rather than the
   * complete repository.
   */
  public class ObjectStoreImpl implements ObjectStore {
  
      private static final Logger LOG = LoggerFactory.getLogger(ObjectStoreImpl.class.getName());
      private static final int FIRST_ID = 100;
      private static final Long MAX_CONTENT_SIZE_KB = ConfigurationSettings
              .getConfigurationValueAsLong(ConfigConstants.MAX_CONTENT_SIZE_KB);
  
      /**
       * User id for administrator always having all rights.
       */
      public static final String ADMIN_PRINCIPAL_ID = "Admin";
  
      /**
       * Simple id generator that uses just an integer.
       */
      private static int nextUnusedId = FIRST_ID;
  
      /**
       * A concurrent HashMap as core element to hold all objects in the
       * repository.
       */
      private final Map<String, StoredObject> fStoredObjectMap = new ConcurrentHashMap<String, StoredObject>();
  
      /**
       * A concurrent HashMap to hold all Acls in the repository.
       */
      private int nextUnusedAclId = 0;
  
      private final List<InMemoryAcl> fAcls = new ArrayList<InMemoryAcl>();
  
      private final Lock fLock = new ReentrantLock();
  
      private final String fRepositoryId;
      private FolderImpl fRootFolder = null;
  
      public ObjectStoreImpl(String repositoryId) {
          fRepositoryId = repositoryId;
          createRootFolder();
      }
  
      private static synchronized Integer getNextId() {
          return nextUnusedId++;
      }
  
      private synchronized Integer getNextAclId() {
          return nextUnusedAclId++;
      }
  
      private void lock() {
          fLock.lock();
      }
  
      private void unlock() {
          fLock.unlock();
      }
  
      @Override
      public Folder getRootFolder() {
          return fRootFolder;
      }
  
      @Override
      public StoredObject getObjectByPath(String path, String user) {
          StoredObject so = findObjectWithPathInDescendents(path, user, Filing.PATH_SEPARATOR, fRootFolder);
          return so;
      }
  
      private Fileable findObjectWithPathInDescendents(String path, String user, String prefix, Fileable fo) {
          if (path.equals(prefix)) {
              return fo;
          } else if (fo instanceof Folder) {
              List<Fileable> children = getChildren((Folder) fo);
              for (Fileable child : children) {
                  String foundPath = prefix.length() == 1 ? prefix + child.getName() : prefix + Filing.PATH_SEPARATOR
                          + child.getName();
                  if (path.startsWith(foundPath)) {
                      Fileable found = findObjectWithPathInDescendents(path, user, foundPath, child);
                      if (null != found) {
                          return found; // note that there can be multiple folders
                                        // with the same prefix like folder1,
                                        // folder10
                      }
                  }
              }
          }
          return null;
      }
  
      @Override
      public StoredObject getObjectById(String objectId) {
          // we use path as id so we just can look it up in the map
          StoredObject so = fStoredObjectMap.get(objectId);
          return so;
      }
  
      @Override
      public void deleteObject(String objectId, Boolean allVersions, String user) {
          StoredObject obj = fStoredObjectMap.get(objectId);
  
          if (null == obj) {
              throw new CmisObjectNotFoundException("Cannot delete object with id  " + objectId
                      + ". Object does not exist.");
          }
  
          if (obj instanceof FolderImpl) {
              deleteFolder(objectId, user);
          } else if (obj instanceof DocumentVersion) {
              DocumentVersion vers = (DocumentVersion) obj;
              VersionedDocument parentDoc = vers.getParentDocument();
              boolean otherVersionsExists;
              if (allVersions != null && allVersions) {
                  otherVersionsExists = false;
                  List<DocumentVersion> allVers = parentDoc.getAllVersions();
                  for (DocumentVersion ver : allVers) {
                      fStoredObjectMap.remove(ver.getId());
                  }
              } else {
                  fStoredObjectMap.remove(objectId);
                  otherVersionsExists = parentDoc.deleteVersion(vers);
              }
  
              if (!otherVersionsExists) {
                  fStoredObjectMap.remove(parentDoc.getId());
              }
          } else {
              fStoredObjectMap.remove(objectId);
          }
      }
  
      public String storeObject(StoredObject so) {
          String id = so.getId();
          // check if update or create
          if (null == id) {
              id = getNextId().toString();
          }
          fStoredObjectMap.put(id, so);
          return id;
      }
  
      StoredObject getObject(String id) {
          return fStoredObjectMap.get(id);
      }
  
      void removeObject(String id) {
          fStoredObjectMap.remove(id);
      }
  
      public Set<String> getIds() {
          Set<String> entries = fStoredObjectMap.keySet();
          return entries;
      }
  
      /**
       * Clear repository and remove all data.
       */
      @Override
      public void clear() {
          lock();
          fStoredObjectMap.clear();
          storeObject(fRootFolder);
          unlock();
      }
  
      @Override
      public long getObjectCount() {
          return fStoredObjectMap.size();
      }
  
      // /////////////////////////////////////////
      // private helper methods
  
      private void createRootFolder() {
          FolderImpl rootFolder = new FolderImpl();
          rootFolder.setName("RootFolder");
          rootFolder.setParentId(null);
          rootFolder.setTypeId(BaseTypeId.CMIS_FOLDER.value());
          rootFolder.setCreatedBy("Admin");
          rootFolder.setModifiedBy("Admin");
          rootFolder.setModifiedAtNow();
          rootFolder.setRepositoryId(fRepositoryId);
          rootFolder.setAclId(addAcl(InMemoryAcl.getDefaultAcl()));
          String id = storeObject(rootFolder);
          rootFolder.setId(id);
          fRootFolder = rootFolder;
      }
  
      @Override
      public Document createDocument(Map<String, PropertyData<?>> propMap, String user, Folder folder,
              ContentStream contentStream, List<String> policies, Acl addACEs, Acl removeACEs) {
          String name = (String) propMap.get(PropertyIds.NAME).getFirstValue();
          DocumentImpl doc = new DocumentImpl();
          doc.createSystemBasePropertiesWhenCreated(propMap, user);
          doc.setCustomProperties(propMap);
          doc.setRepositoryId(fRepositoryId);
          doc.setName(name);
          if (null != folder) {
              if (hasChild(folder, name)) {
                  throw new CmisNameConstraintViolationException("Cannot create document an object with name " + name
                          + " already exists in folder " + getFolderPath(folder.getId()));
              }
              doc.addParentId(folder.getId());
          }
          ContentStream content = setContent(doc, contentStream);
          doc.setContent(content);
          int aclId = getAclId(((FolderImpl) folder), addACEs, removeACEs);
          doc.setAclId(aclId);
          if (null != policies) {
              doc.setAppliedPolicies(policies);
          }
          String id = storeObject(doc);
          doc.setId(id);
          applyAcl(doc, addACEs, removeACEs);
          return doc;
      }
  
      @Override
      public StoredObject createItem(String name, Map<String, PropertyData<?>> propMap, String user, Folder folder,
              List<String> policies, Acl addACEs, Acl removeACEs) {
          ItemImpl item = new ItemImpl();
          item.createSystemBasePropertiesWhenCreated(propMap, user);
          item.setCustomProperties(propMap);
          item.setRepositoryId(fRepositoryId);
          item.setName(name);
          if (null != folder) {
              if (hasChild(folder, name)) {
                  throw new CmisNameConstraintViolationException("Cannot create document an object with name " + name
                          + " already exists in folder " + getFolderPath(folder.getId()));
              }
              item.addParentId(folder.getId());
          }
          if (null != policies) {
              item.setAppliedPolicies(policies);
          }
          int aclId = getAclId(((FolderImpl) folder), addACEs, removeACEs);
          item.setAclId(aclId);
          String id = storeObject(item);
          item.setId(id);
          applyAcl(item, addACEs, removeACEs);
          return item;
      }
  
      @Override
      public DocumentVersion createVersionedDocument(String name, Map<String, PropertyData<?>> propMap, String user,
              Folder folder, List<String> policies, Acl addACEs, Acl removeACEs, ContentStream contentStream,
              VersioningState versioningState) {
          VersionedDocumentImpl doc = new VersionedDocumentImpl();
          doc.createSystemBasePropertiesWhenCreated(propMap, user);
          doc.setCustomProperties(propMap);
          doc.setRepositoryId(fRepositoryId);
          doc.setName(name);
          String id = storeObject(doc);
          doc.setId(id);
          DocumentVersion version = doc.addVersion(versioningState, user);
          setContent(version, contentStream);
          version.createSystemBasePropertiesWhenCreated(propMap, user);
          version.setCustomProperties(propMap);
          if (null != folder) {
              if (hasChild(folder, name)) {
                  throw new CmisNameConstraintViolationException("Cannot create document an object with name " + name
                          + " already exists in folder " + getFolderPath(folder.getId()));
              }
              doc.addParentId(folder.getId());
          }
          int aclId = getAclId(((FolderImpl) folder), addACEs, removeACEs);
          doc.setAclId(aclId);
          if (null != policies) {
              doc.setAppliedPolicies(policies);
          }
          id = storeObject(version);
          version.setId(id);
          applyAcl(doc, addACEs, removeACEs);
          return version;
      }
  
      @Override
      public Folder createFolder(String name, Map<String, PropertyData<?>> propMap, String user, Folder parent,
              List<String> policies, Acl addACEs, Acl removeACEs) {
  
          if (null == parent) {
              throw new CmisInvalidArgumentException("Cannot create root folder.");
          } else if (hasChild(parent, name)) {
              throw new CmisNameConstraintViolationException(
                      "Cannot create folder, this name already exists in parent folder.");
          }
          FolderImpl folder = new FolderImpl(name, parent.getId());
          if (null != propMap) {
              folder.createSystemBasePropertiesWhenCreated(propMap, user);
              folder.setCustomProperties(propMap);
          }
          folder.setRepositoryId(fRepositoryId);
  
          int aclId = getAclId(((FolderImpl) parent), addACEs, removeACEs);
          folder.setAclId(aclId);
          if (null != policies) {
              folder.setAppliedPolicies(policies);
          }
  
          String id = storeObject(folder);
          folder.setId(id);
          applyAcl(folder, addACEs, removeACEs);
          return folder;
      }
  
      public Folder createFolder(String name) {
          Folder folder = new FolderImpl(name, null);
          folder.setRepositoryId(fRepositoryId);
          return folder;
      }
  
      @Override
      public StoredObject createPolicy(String name, String policyText, Map<String, PropertyData<?>> propMap, String user,
              Acl addACEs, Acl removeACEs) {
          PolicyImpl policy = new PolicyImpl();
          policy.createSystemBasePropertiesWhenCreated(propMap, user);
          policy.setCustomProperties(propMap);
          policy.setRepositoryId(fRepositoryId);
          policy.setName(name);
          policy.setPolicyText(policyText);
          String id = storeObject(policy);
          policy.setId(id);
          applyAcl(policy, addACEs, removeACEs);
          return policy;
      }
  
      @Override
      public StoredObject createRelationship(String name, StoredObject sourceObject, StoredObject targetObject,
              Map<String, PropertyData<?>> propMap, String user, Acl addACEs, Acl removeACEs) {
  
          RelationshipImpl rel = new RelationshipImpl();
          rel.createSystemBasePropertiesWhenCreated(propMap, user);
          rel.setCustomProperties(propMap);
          rel.setRepositoryId(fRepositoryId);
          rel.setName(name);
          if (null != sourceObject) {
              rel.setSource(sourceObject.getId());
          }
          if (null != targetObject) {
              rel.setTarget(targetObject.getId());
          }
          String id = storeObject(rel);
          rel.setId(id);
          applyAcl(rel, addACEs, removeACEs);
          return rel;
      }
  
      @Override
      public void storeVersion(DocumentVersion version) {
          String id = storeObject(version);
          version.setId(id);
      }
  
      @Override
      public void deleteVersion(DocumentVersion version) {
          StoredObject found = fStoredObjectMap.remove(version.getId());
  
          if (null == found) {
              throw new CmisInvalidArgumentException("Cannot delete object with id  " + version.getId()
                      + ". Object does not exist.");
          }
      }
  
      @Override
      public void updateObject(StoredObject so, Map<String, PropertyData<?>> newProperties, String user) {
          // nothing to do
          Map<String, PropertyData<?>> properties = so.getProperties();
          for (String key : newProperties.keySet()) {
              PropertyData<?> value = newProperties.get(key);
  
              if (key.equals(PropertyIds.SECONDARY_OBJECT_TYPE_IDS)) {
                  properties.put(key, value); // preserve it even if it is empty!
              } else if (null == value || value.getValues() == null || value.getFirstValue() == null) {
                  // delete property
                  properties.remove(key);
              } else {
                  properties.put(key, value);
              }
          }
          // update system properties and secondary object type ids
          so.updateSystemBasePropertiesWhenModified(properties, user);
          properties.remove(PropertyIds.SECONDARY_OBJECT_TYPE_IDS);
      }
  
      @Override
      public List<StoredObject> getCheckedOutDocuments(String orderBy, String user,
              IncludeRelationships includeRelationships) {
          List<StoredObject> res = new ArrayList<StoredObject>();
  
          for (StoredObject so : fStoredObjectMap.values()) {
              if (so instanceof VersionedDocument) {
                  VersionedDocument verDoc = (VersionedDocument) so;
                  if (verDoc.isCheckedOut() && hasReadAccess(user, verDoc)) {
                      res.add(verDoc.getPwc());
                  }
              }
          }
  
          return res;
      }
  
      @Override
      public List<StoredObject> getRelationships(String objectId, List<String> typeIds, RelationshipDirection direction) {
  
          List<StoredObject> res = new ArrayList<StoredObject>();
  
          if (typeIds != null && typeIds.size() > 0) {
              for (String typeId : typeIds) {
                  for (StoredObject so : fStoredObjectMap.values()) {
                      if (so instanceof Relationship && so.getTypeId().equals(typeId)) {
                          Relationship ro = (Relationship) so;
                          if (ro.getSourceObjectId().equals(objectId)
                                  && (RelationshipDirection.EITHER == direction || RelationshipDirection.SOURCE == direction)) {
                              res.add(so);
                          } else if (ro.getTargetObjectId().equals(objectId)
                                  && (RelationshipDirection.EITHER == direction || RelationshipDirection.TARGET == direction)) {
                              res.add(so);
                          }
                      }
                  }
              }
          } else {
              res = getAllRelationships(objectId, direction);
          }
          return res;
      }
  
      @Override
      public String getFolderPath(String folderId) {
          StringBuilder sb = new StringBuilder();
          insertPathSegment(sb, folderId);
          return sb.toString();
      }
  
      private void insertPathSegment(StringBuilder sb, String folderId) {
          Folder folder = (Folder) getObjectById(folderId);
          if (null == folder.getParentId()) {
              if (sb.length() == 0) {
                  sb.insert(0, Filing.PATH_SEPARATOR);
              }
          } else {
              sb.insert(0, folder.getName());
              sb.insert(0, Filing.PATH_SEPARATOR);
              insertPathSegment(sb, folder.getParentId());
          }
      }
  
      @Override
      public Acl applyAcl(StoredObject so, Acl addAces, Acl removeAces, AclPropagation aclPropagation, String principalId) {
          if (aclPropagation == AclPropagation.OBJECTONLY || !(so instanceof Folder)) {
              return applyAcl(so, addAces, removeAces);
          } else {
              return applyAclRecursive(((Folder) so), addAces, removeAces, principalId);
          }
      }
  
      @Override
      public Acl applyAcl(StoredObject so, Acl acl, AclPropagation aclPropagation, String principalId) {
          if (aclPropagation == AclPropagation.OBJECTONLY || !(so instanceof Folder)) {
              return applyAcl(so, acl);
          } else {
              return applyAclRecursive(((Folder) so), acl, principalId);
          }
      }
  
      public List<Integer> getAllAclsForUser(String principalId, Permission permission) {
          List<Integer> acls = new ArrayList<Integer>();
          for (InMemoryAcl acl : fAcls) {
              if (acl.hasPermission(principalId, permission)) {
                  acls.add(acl.getId());
              }
          }
          return acls;
      }
  
      @Override
      public Acl getAcl(int aclId) {
          InMemoryAcl acl = getInMemoryAcl(aclId);
          return acl == null ? InMemoryAcl.getDefaultAcl().toCommonsAcl() : acl.toCommonsAcl();
      }
  
      public int getAclId(StoredObjectImpl so, Acl addACEs, Acl removeACEs) {
          InMemoryAcl newAcl;
          boolean removeDefaultAcl = false;
          int aclId = 0;
  
          if (so == null) {
              newAcl = new InMemoryAcl();
          } else {
              aclId = so.getAclId();
              newAcl = getInMemoryAcl(aclId);
              if (null == newAcl) {
                  newAcl = new InMemoryAcl();
              } else {
                  // copy list so that we can safely change it without effecting
                  // the original
                  newAcl = new InMemoryAcl(newAcl.getAces());
              }
          }
  
          if (newAcl.size() == 0 && addACEs == null && removeACEs == null) {
              return 0;
          }
  
          if (null != removeACEs) {
              for (Ace ace : removeACEs.getAces()) {
                  InMemoryAce inMemAce = new InMemoryAce(ace);
                  if (inMemAce.equals(InMemoryAce.getDefaultAce())) {
                      removeDefaultAcl = true;
                  }
              }
          }
  
          if (so != null && 0 == aclId && !removeDefaultAcl) {
              return 0; // if object grants full access to everyone and it will
                        // not be removed we do nothing
          }
  
          // add ACEs
          if (null != addACEs) {
              for (Ace ace : addACEs.getAces()) {
                  InMemoryAce inMemAce = new InMemoryAce(ace);
                  if (inMemAce.equals(InMemoryAce.getDefaultAce())) {
                      return 0; // if everyone has full access there is no need to
                  }
                  // add additional ACLs.
                  newAcl.addAce(inMemAce);
              }
          }
  
          // remove ACEs
          if (null != removeACEs) {
              for (Ace ace : removeACEs.getAces()) {
                  InMemoryAce inMemAce = new InMemoryAce(ace);
                  newAcl.removeAce(inMemAce);
              }
          }
  
          if (newAcl.size() > 0) {
              return addAcl(newAcl);
          } else {
              return 0;
          }
      }
  
      private void deleteFolder(String folderId, String user) {
          StoredObject folder = fStoredObjectMap.get(folderId);
          if (folder == null) {
              throw new CmisInvalidArgumentException("Unknown object with id:  " + folderId);
          }
  
          if (!(folder instanceof FolderImpl)) {
              throw new CmisInvalidArgumentException("Cannot delete folder with id:  " + folderId
                      + ". Object exists but is not a folder.");
          }
  
          // check if children exist
          List<Fileable> children = getChildren((Folder) folder, -1, -1, user, true).getChildren();
          if (children != null && !children.isEmpty()) {
              throw new CmisConstraintException("Cannot delete folder with id:  " + folderId + ". Folder is not empty.");
          }
  
          fStoredObjectMap.remove(folderId);
      }
  
      @Override
      public ChildrenResult getChildren(Folder folder, int maxItemsParam, int skipCountParam, String user, boolean usePwc) {
          List<Fileable> children = getChildren(folder, user, usePwc);
          sortFolderList(children);
  
          int maxItems = maxItemsParam < 0 ? children.size() : maxItemsParam;
          int skipCount = skipCountParam < 0 ? 0 : skipCountParam;
  
          int from = Math.min(skipCount, children.size());
          int to = Math.min(maxItems + from, children.size());
          int noItems = children.size();
  
          children = children.subList(from, to);
          return new ChildrenResult(children, noItems);
      }
  
      private List<Fileable> getChildren(Folder folder) {
          return getChildren(folder, null, false);
      }
  
      private List<Fileable> getChildren(Folder folder, String user, boolean usePwc) {
          List<Fileable> children = new ArrayList<Fileable>();
          for (String id : getIds()) {
              StoredObject obj = getObject(id);
              if (obj instanceof Fileable) {
                  Fileable pathObj = (Fileable) obj;
                  if ((null == user || hasReadAccess(user, obj)) && pathObj.getParentIds().contains(folder.getId())) {
                      if (pathObj instanceof VersionedDocument) {
                          DocumentVersion ver;
                          if (usePwc) {
                              ver = ((VersionedDocument) pathObj).getPwc();
                              if (null == ver) {
                                  ver = ((VersionedDocument) pathObj).getLatestVersion(false);
                              }
                          } else {
                              ver = ((VersionedDocument) pathObj).getLatestVersion(false);
                          }
                          children.add(ver);
                      } else if (!(pathObj instanceof DocumentVersion)) { // ignore
                                                                          // DocumentVersion
                          children.add(pathObj);
                      }
  
                  }
              }
          }
          return children;
      }
  
      @Override
      public ChildrenResult getFolderChildren(Folder folder, int maxItems, int skipCount, String user) {
          List<Fileable> folderChildren = new ArrayList<Fileable>();
          for (String id : getIds()) {
              StoredObject obj = getObject(id);
              if (hasReadAccess(user, obj) && obj instanceof Folder) {
                  Folder childFolder = (Folder) obj;
                  if (childFolder.getParentIds().contains(folder.getId())) {
                      folderChildren.add(childFolder);
                  }
              }
          }
          sortFolderList(folderChildren);
          int from = Math.min(skipCount, folderChildren.size());
          int to = Math.min(maxItems + from, folderChildren.size());
          int noItems = folderChildren.size();
  
          folderChildren = folderChildren.subList(from, to);
  
          return new ChildrenResult(folderChildren, noItems);
      }
  
      @Override
      public void move(StoredObject so, Folder oldParent, Folder newParent, String user) {
          try {
              if (hasChild(newParent, so.getName())) {
                  throw new CmisInvalidArgumentException("Cannot move object " + so.getName() + " to folder "
                          + getFolderPath(newParent.getId()) + ". A child with this name already exists.");
              }
              lock();
              if (so instanceof MultiFiling) {
                  MultiFiling fi = (MultiFiling) so;
                  addParentIntern(fi, newParent);
                  removeParentIntern(fi, oldParent);
              } else if (so instanceof FolderImpl) {
                  ((FolderImpl) so).setParentId(newParent.getId());
              }
          } finally {
              unlock();
          }
      }
  
      @Override
      public void rename(StoredObject so, String newName, String user) {
          try {
              lock();
              if (so.getId().equals(fRootFolder.getId())) {
                  throw new CmisInvalidArgumentException("Root folder cannot be renamed.");
              }
              if (so.getName().equals(newName)) {
                  return;
              }
              if (so instanceof Fileable) {
                  for (String folderId : ((Fileable) so).getParentIds()) {
                      Folder folder = (Folder) getObjectById(folderId);
                      if (hasChild(folder, newName)) {
                          throw new CmisNameConstraintViolationException("Cannot rename object to " + newName
                                  + ". This path already exists in parent " + getFolderPath(folder.getId()) + ".");
                      }
                  }
              }
              so.setName(newName);
          } finally {
              unlock();
          }
      }
  
      private boolean hasChild(Folder folder, String name) {
          List<Fileable> children = getChildren(folder);
          for (Fileable child : children) {
              if (child.getName().equals(name)) {
                  return true;
              }
          }
          return false;
      }
  
      @Override
      public List<String> getParentIds(StoredObject so, String user) {
          List<String> visibleParents = new ArrayList<String>();
          if (!(so instanceof Fileable)) {
              throw new CmisInvalidArgumentException("Object is not fileable: " + so.getId());
          }
          Filing fileable = (Fileable) so;
          List<String> parents = fileable.getParentIds();
          for (String id : parents) {
              StoredObject parent = getObjectById(id);
              if (hasReadAccess(user, parent)) {
                  visibleParents.add(id);
              }
          }
          return visibleParents;
      }
  
      public boolean hasReadAccess(String principalId, StoredObject so) {
          return hasAccess(principalId, so, Permission.READ);
      }
  
      public boolean hasWriteAccess(String principalId, StoredObject so) {
          return hasAccess(principalId, so, Permission.WRITE);
      }
  
      public boolean hasAllAccess(String principalId, StoredObject so) {
          return hasAccess(principalId, so, Permission.ALL);
      }
  
      public void checkReadAccess(String principalId, StoredObject so) {
          checkAccess(principalId, so, Permission.READ);
      }
  
      public void checkWriteAccess(String principalId, StoredObject so) {
          checkAccess(principalId, so, Permission.WRITE);
      }
  
      public void checkAllAccess(String principalId, StoredObject so) {
          checkAccess(principalId, so, Permission.ALL);
      }
  
      private void checkAccess(String principalId, StoredObject so, Permission permission) {
          if (!hasAccess(principalId, so, permission)) {
              throw new CmisPermissionDeniedException("Object with id " + so.getId() + " and name " + so.getName()
                      + " does not grant " + permission.toString() + " access to principal " + principalId);
          }
      }
  
      private boolean hasAccess(String principalId, StoredObject so, Permission permission) {
          if (null != principalId && principalId.equals(ADMIN_PRINCIPAL_ID)) {
              return true;
          }
          List<Integer> aclIds = getAllAclsForUser(principalId, permission);
          return aclIds.contains(((StoredObjectImpl) so).getAclId());
      }
  
      private InMemoryAcl getInMemoryAcl(int aclId) {
  
          for (InMemoryAcl acl : fAcls) {
              if (aclId == acl.getId()) {
                  return acl;
              }
          }
          return null;
      }
  
      private int setAcl(StoredObjectImpl so, Acl acl) {
          int aclId;
          if (null == acl || acl.getAces().isEmpty()) {
              aclId = 0;
          } else {
              aclId = getAclId(null, acl, null);
          }
          so.setAclId(aclId);
          return aclId;
      }
  
      /**
       * Check if an Acl is already known.
       * 
       * @param acl
       *            acl to be checked
       * @return 0 if Acl is not known, id of Acl otherwise
       */
      private int hasAcl(InMemoryAcl acl) {
          for (InMemoryAcl acl2 : fAcls) {
              if (acl2.equals(acl)) {
                  return acl2.getId();
              }
          }
          return -1;
      }
  
      private int addAcl(InMemoryAcl acl) {
          int aclId = -1;
  
          if (null == acl) {
              return 0;
          }
  
          lock();
          try {
              aclId = hasAcl(acl);
              if (aclId < 0) {
                  aclId = getNextAclId();
                  acl.setId(aclId);
                  fAcls.add(acl);
              }
          } finally {
              unlock();
          }
          return aclId;
      }
  
      private Acl applyAcl(StoredObject so, Acl acl) {
          int aclId = setAcl((StoredObjectImpl) so, acl);
          return getAcl(aclId);
      }
  
      private Acl applyAcl(StoredObject so, Acl addAces, Acl removeAces) {
          int aclId = getAclId((StoredObjectImpl) so, addAces, removeAces);
          ((StoredObjectImpl) so).setAclId(aclId);
          return getAcl(aclId);
      }
  
      private Acl applyAclRecursive(Folder folder, Acl addAces, Acl removeAces, String principalId) {
          List<Fileable> children = getChildren(folder, -1, -1, ADMIN_PRINCIPAL_ID, false).getChildren();
          Acl result = applyAcl(folder, addAces, removeAces);
  
          if (null == children) {
              return result;
          }
  
          for (Fileable child : children) {
              if (hasAllAccess(principalId, child)) {
                  if (child instanceof Folder) {
                      applyAclRecursive((Folder) child, addAces, removeAces, principalId);
                  } else {
                      applyAcl(child, addAces, removeAces);
                  }
              }
          }
  
          return result;
      }
  
      private Acl applyAclRecursive(Folder folder, Acl acl, String principalId) {
          List<Fileable> children = getChildren(folder, -1, -1, ADMIN_PRINCIPAL_ID, false).getChildren();
          Acl result = applyAcl(folder, acl);
  
          if (null == children) {
              return result;
          }
  
          for (Fileable child : children) {
              if (hasAllAccess(principalId, child)) {
                  if (child instanceof Folder) {
                      applyAclRecursive((Folder) child, acl, principalId);
                  } else {
                      applyAcl(child, acl);
                  }
              }
          }
  
          return result;
      }
  
      private List<StoredObject> getAllRelationships(String objectId, RelationshipDirection direction) {
  
          List<StoredObject> res = new ArrayList<StoredObject>();
  
          for (StoredObject so : fStoredObjectMap.values()) {
              if (so instanceof Relationship) {
                  Relationship ro = (Relationship) so;
                  if (ro.getSourceObjectId().equals(objectId)
                          && (RelationshipDirection.EITHER == direction || RelationshipDirection.SOURCE == direction)) {
                      res.add(so);
                  } else if (ro.getTargetObjectId().equals(objectId)
                          && (RelationshipDirection.EITHER == direction || RelationshipDirection.TARGET == direction)) {
                      res.add(so);
                  }
              }
          }
          return res;
      }
  
      @Override
      public boolean isTypeInUse(String typeId) {
          // iterate over all the objects and check for each if the type matches
          for (String objectId : getIds()) {
              StoredObject so = getObjectById(objectId);
              if (so.getTypeId().equals(typeId)) {
                  return true;
              }
          }
          return false;
      }
  
      @Override
      public void addParent(StoredObject so, Folder parent) {
          try {
              lock();
              if (hasChild(parent, so.getName())) {
                  throw new IllegalArgumentException(
                          "Cannot assign new parent folder, this name already exists in target folder.");
              }
              MultiFiling mfi;
              if (so instanceof MultiFiling) {
                  mfi = (MultiFiling) so;
              } else {
                  throw new IllegalArgumentException("Object " + so.getId() + "is not fileable");
              }
  
              addParentIntern(mfi, parent);
          } finally {
              unlock();
          }
      }
  
      @Override
      public void removeParent(StoredObject so, Folder parent) {
          try {
              lock();
              MultiFiling mfi;
              if (so instanceof MultiFiling) {
                  mfi = (MultiFiling) so;
              } else {
                  throw new IllegalArgumentException("Object " + so.getId() + "is not fileable");
              }
  
              removeParentIntern(mfi, parent);
          } finally {
              unlock();
          }
      }
  
      private void addParentIntern(MultiFiling so, Folder parent) {
          so.addParentId(parent.getId());
      }
  
      private void removeParentIntern(MultiFiling so, Folder parent) {
          so.removeParentId(parent.getId());
      }
  
      private static void sortFolderList(List<? extends StoredObject> list) {
          // TODO evaluate orderBy, for now sort by path segment
          class FolderComparator implements Comparator<StoredObject> {
  
              @Override
              public int compare(StoredObject f1, StoredObject f2) {
                  String segment1 = f1.getName();
                  String segment2 = f2.getName();
  
                  return segment1.compareTo(segment2);
              }
          }
  
          Collections.sort(list, new FolderComparator());
      }
  
      @Override
      public ContentStream getContent(StoredObject so, long offset, long length) {
          if (so instanceof Content) {
              Content content = (Content) so;
              ContentStream contentStream = content.getContent();
              if (null == contentStream) {
                  return null;
              } else if (offset <= 0 && length < 0) {
                  return contentStream;
              } else {
                  return ((ContentStreamDataImpl) contentStream).getCloneWithLimits(offset, length);
              }
          } else {
              throw new CmisInvalidArgumentException("Cannot set content, object does not implement interface Content.");
          }
      }
  
      @Override
      public ContentStream setContent(StoredObject so, ContentStream contentStream) {
          if (so instanceof Content) {
              ContentStreamDataImpl newContent;
              Content content = (Content) so;
  
              if (null == contentStream) {
                  newContent = null;
              } else {
                  boolean useFakeContentStore = so.getTypeId().equals(DefaultTypeSystemCreator.BIG_CONTENT_FAKE_TYPE);
                  newContent = new ContentStreamDataImpl(MAX_CONTENT_SIZE_KB == null ? 0 : MAX_CONTENT_SIZE_KB,
                          useFakeContentStore);
                  String fileName = contentStream.getFileName();
                  if (null == fileName || fileName.length() <= 0) {
                      fileName = so.getName(); // use name of document as fallback
                  }
                  newContent.setFileName(fileName);
                  String mimeType = contentStream.getMimeType();
                  if (null == mimeType || mimeType.length() <= 0) {
                      mimeType = "application/octet-stream"; // use as fallback
                  }
                  newContent.setMimeType(mimeType);
                  newContent.setLastModified(new GregorianCalendar());
                  try {
                      newContent.setContent(contentStream.getStream());
                  } catch (IOException e) {
                      throw new CmisRuntimeException("Failed to get content from InputStream", e);
                  }
              }
              content.setContent(newContent);
              return newContent;
  
          } else {
              throw new CmisInvalidArgumentException("Cannot set content, object does not implement interface Content.");
          }
      }
  
      @Override
      public void appendContent(StoredObject so, ContentStream contentStream) {
          if (so instanceof Content) {
              Content content = (Content) so;
              ContentStreamDataImpl newContent = (ContentStreamDataImpl) content.getContent();
  
              if (null == newContent) {
                  content.setContent(null);
              } else {
                  try {
                      newContent.appendContent(contentStream.getStream());
                  } catch (IOException e) {
                      throw new CmisStorageException("Failed to append content: IO Exception", e);
                  }
              }
          } else {
              throw new CmisInvalidArgumentException("Cannot set content, object does not implement interface Content.");
          }
      }
  
      @Override
      public List<RenditionData> getRenditions(StoredObject so, String renditionFilter, long maxItems, long skipCount) {
  
          return RenditionUtil.getRenditions(so, renditionFilter, maxItems, skipCount);
      }
  
      @Override
      public ContentStream getRenditionContent(StoredObject so, String streamId, long offset, long length) {
          return RenditionUtil.getRenditionContent(so, streamId, offset, length);
      }
  
  }
  