blob: 60424c533fc16ece801fc40ae214c6b3c8502f82 [file] [log] [blame]
/*
* 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:
* <p>
*
* <pre>
* 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);
}
}