/*
* Copyright 2014 Florian Müller & Jay Brown
*
* Licensed 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.
*
*
* This code is based on the Apache Chemistry OpenCMIS FileShare project
* <http://chemistry.apache.org/java/developing/repositories/dev-repositories-fileshare.html>.
*
* It is part of a training exercise and not intended for production use!
*
*/
package org.example.cmis.server;
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.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.chemistry.opencmis.commons.BasicPermissions;
import org.apache.chemistry.opencmis.commons.PropertyIds;
import org.apache.chemistry.opencmis.commons.data.Ace;
import org.apache.chemistry.opencmis.commons.data.Acl;
import org.apache.chemistry.opencmis.commons.data.AllowableActions;
import org.apache.chemistry.opencmis.commons.data.BulkUpdateObjectIdAndChangeToken;
import org.apache.chemistry.opencmis.commons.data.ContentStream;
import org.apache.chemistry.opencmis.commons.data.FailedToDeleteData;
import org.apache.chemistry.opencmis.commons.data.MutablePropertyData;
import org.apache.chemistry.opencmis.commons.data.ObjectData;
import org.apache.chemistry.opencmis.commons.data.ObjectInFolderContainer;
import org.apache.chemistry.opencmis.commons.data.ObjectInFolderData;
import org.apache.chemistry.opencmis.commons.data.ObjectInFolderList;
import org.apache.chemistry.opencmis.commons.data.ObjectList;
import org.apache.chemistry.opencmis.commons.data.ObjectParentData;
import org.apache.chemistry.opencmis.commons.data.PermissionMapping;
import org.apache.chemistry.opencmis.commons.data.Properties;
import org.apache.chemistry.opencmis.commons.data.PropertyData;
import org.apache.chemistry.opencmis.commons.data.RepositoryInfo;
import org.apache.chemistry.opencmis.commons.definitions.PermissionDefinition;
import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition;
import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition;
import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionContainer;
import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionList;
import org.apache.chemistry.opencmis.commons.enums.AclPropagation;
import org.apache.chemistry.opencmis.commons.enums.Action;
import org.apache.chemistry.opencmis.commons.enums.BaseTypeId;
import org.apache.chemistry.opencmis.commons.enums.CapabilityAcl;
import org.apache.chemistry.opencmis.commons.enums.CapabilityChanges;
import org.apache.chemistry.opencmis.commons.enums.CapabilityContentStreamUpdates;
import org.apache.chemistry.opencmis.commons.enums.CapabilityJoin;
import org.apache.chemistry.opencmis.commons.enums.CapabilityOrderBy;
import org.apache.chemistry.opencmis.commons.enums.CapabilityQuery;
import org.apache.chemistry.opencmis.commons.enums.CapabilityRenditions;
import org.apache.chemistry.opencmis.commons.enums.CmisVersion;
import org.apache.chemistry.opencmis.commons.enums.SupportedPermissions;
import org.apache.chemistry.opencmis.commons.enums.Updatability;
import org.apache.chemistry.opencmis.commons.enums.VersioningState;
import org.apache.chemistry.opencmis.commons.exceptions.CmisBaseException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisConstraintException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisContentAlreadyExistsException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisNameConstraintViolationException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisPermissionDeniedException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisStorageException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisStreamNotSupportedException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisUpdateConflictException;
import org.apache.chemistry.opencmis.commons.impl.Base64;
import org.apache.chemistry.opencmis.commons.impl.IOUtils;
import org.apache.chemistry.opencmis.commons.impl.MimeTypes;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlEntryImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlListImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlPrincipalDataImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.AclCapabilitiesDataImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.AllowableActionsImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.BulkUpdateObjectIdAndChangeTokenImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.CreatablePropertyTypesImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.FailedToDeleteDataImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.NewTypeSettableAttributesImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectDataImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectInFolderContainerImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectInFolderDataImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectInFolderListImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectListImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectParentDataImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PartialContentStreamImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PermissionDefinitionDataImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PermissionMappingDataImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertiesImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyBooleanImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyDateTimeImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIdImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIntegerImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyStringImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.RepositoryCapabilitiesImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.RepositoryInfoImpl;
import org.apache.chemistry.opencmis.commons.impl.server.ObjectInfoImpl;
import org.apache.chemistry.opencmis.commons.server.CallContext;
import org.apache.chemistry.opencmis.commons.server.ObjectInfoHandler;
import org.apache.chemistry.opencmis.commons.spi.Holder;
/**
* Implements all repository operations.
*/
public class FileBridgeRepository {
private static final String ROOT_ID = "@root@";
private static final String USER_UNKNOWN = "<unknown>";
private static final int BUFFER_SIZE = 64 * 1024;
private static final Pattern IN_FOLDER_QUERY_PATTERN = Pattern
.compile("(?i)select\\s+.+\\s+from\\s+(\\S*).*\\s+where\\s+in_folder\\('(.*)'\\)");
/** Repository id. */
private final String repositoryId;
/** Root directory. */
private final File root;
/** Types. */
private final FileBridgeTypeManager typeManager;
/** Users. */
private final Map<String, Boolean> readWriteUserMap;
/** CMIS 1.0 repository info. */
private final RepositoryInfo repositoryInfo10;
/** CMIS 1.1 repository info. */
private final RepositoryInfo repositoryInfo11;
public FileBridgeRepository(final String repositoryId, final String rootPath,
final FileBridgeTypeManager typeManager) {
// check repository id
if (repositoryId == null || repositoryId.trim().length() == 0) {
throw new IllegalArgumentException("Invalid repository id!");
}
this.repositoryId = repositoryId;
// check root folder
if (rootPath == null || rootPath.trim().length() == 0) {
throw new IllegalArgumentException("Invalid root folder!");
}
root = new File(rootPath);
if (!root.isDirectory()) {
throw new IllegalArgumentException("Root is not a directory!");
}
// set type manager objects
this.typeManager = typeManager;
// set up read-write user map
readWriteUserMap = new HashMap<String, Boolean>();
// set up repository infos
repositoryInfo10 = createRepositoryInfo(CmisVersion.CMIS_1_0);
repositoryInfo11 = createRepositoryInfo(CmisVersion.CMIS_1_1);
}
private RepositoryInfo createRepositoryInfo(CmisVersion cmisVersion) {
assert cmisVersion != null;
RepositoryInfoImpl repositoryInfo = new RepositoryInfoImpl();
repositoryInfo.setId(repositoryId);
repositoryInfo.setName(repositoryId);
repositoryInfo.setDescription(repositoryId);
// exercise 1.1
repositoryInfo.setCmisVersionSupported(cmisVersion.value());
// exercise 1.2
repositoryInfo.setProductName("FileBridge Server");
repositoryInfo.setProductVersion("1.0");
repositoryInfo.setVendorName("My Company");
// exercise 1.3
repositoryInfo.setRootFolder(ROOT_ID);
repositoryInfo.setThinClientUri("");
repositoryInfo.setChangesIncomplete(true);
RepositoryCapabilitiesImpl capabilities = new RepositoryCapabilitiesImpl();
capabilities.setCapabilityAcl(CapabilityAcl.DISCOVER);
capabilities.setAllVersionsSearchable(false);
capabilities.setCapabilityJoin(CapabilityJoin.NONE);
capabilities.setSupportsMultifiling(false);
capabilities.setSupportsUnfiling(false);
capabilities.setSupportsVersionSpecificFiling(false);
capabilities.setIsPwcSearchable(false);
capabilities.setIsPwcUpdatable(false);
capabilities.setCapabilityQuery(CapabilityQuery.METADATAONLY);
capabilities.setCapabilityChanges(CapabilityChanges.NONE);
capabilities.setCapabilityContentStreamUpdates(CapabilityContentStreamUpdates.ANYTIME);
capabilities.setSupportsGetDescendants(true);
capabilities.setSupportsGetFolderTree(true);
capabilities.setCapabilityRendition(CapabilityRenditions.NONE);
if (cmisVersion != CmisVersion.CMIS_1_0) {
capabilities.setCapabilityOrderBy(CapabilityOrderBy.NONE);
NewTypeSettableAttributesImpl typeSetAttributes = new NewTypeSettableAttributesImpl();
typeSetAttributes.setCanSetControllableAcl(false);
typeSetAttributes.setCanSetControllablePolicy(false);
typeSetAttributes.setCanSetCreatable(false);
typeSetAttributes.setCanSetDescription(false);
typeSetAttributes.setCanSetDisplayName(false);
typeSetAttributes.setCanSetFileable(false);
typeSetAttributes.setCanSetFulltextIndexed(false);
typeSetAttributes.setCanSetId(false);
typeSetAttributes.setCanSetIncludedInSupertypeQuery(false);
typeSetAttributes.setCanSetLocalName(false);
typeSetAttributes.setCanSetLocalNamespace(false);
typeSetAttributes.setCanSetQueryable(false);
typeSetAttributes.setCanSetQueryName(false);
capabilities.setNewTypeSettableAttributes(typeSetAttributes);
CreatablePropertyTypesImpl creatablePropertyTypes = new CreatablePropertyTypesImpl();
capabilities.setCreatablePropertyTypes(creatablePropertyTypes);
}
repositoryInfo.setCapabilities(capabilities);
AclCapabilitiesDataImpl aclCapability = new AclCapabilitiesDataImpl();
aclCapability.setSupportedPermissions(SupportedPermissions.BASIC);
aclCapability.setAclPropagation(AclPropagation.OBJECTONLY);
// permissions
List<PermissionDefinition> permissions = new ArrayList<PermissionDefinition>();
permissions.add(createPermission(BasicPermissions.READ, "Read"));
permissions.add(createPermission(BasicPermissions.WRITE, "Write"));
permissions.add(createPermission(BasicPermissions.ALL, "All"));
aclCapability.setPermissionDefinitionData(permissions);
// mapping
List<PermissionMapping> list = new ArrayList<PermissionMapping>();
list.add(createMapping(PermissionMapping.CAN_CREATE_DOCUMENT_FOLDER, BasicPermissions.READ));
list.add(createMapping(PermissionMapping.CAN_CREATE_FOLDER_FOLDER, BasicPermissions.READ));
list.add(createMapping(PermissionMapping.CAN_DELETE_CONTENT_DOCUMENT, BasicPermissions.WRITE));
list.add(createMapping(PermissionMapping.CAN_DELETE_OBJECT, BasicPermissions.ALL));
list.add(createMapping(PermissionMapping.CAN_DELETE_TREE_FOLDER, BasicPermissions.ALL));
list.add(createMapping(PermissionMapping.CAN_GET_ACL_OBJECT, BasicPermissions.READ));
list.add(createMapping(PermissionMapping.CAN_GET_ALL_VERSIONS_VERSION_SERIES, BasicPermissions.READ));
list.add(createMapping(PermissionMapping.CAN_GET_CHILDREN_FOLDER, BasicPermissions.READ));
list.add(createMapping(PermissionMapping.CAN_GET_DESCENDENTS_FOLDER, BasicPermissions.READ));
list.add(createMapping(PermissionMapping.CAN_GET_FOLDER_PARENT_OBJECT, BasicPermissions.READ));
list.add(createMapping(PermissionMapping.CAN_GET_PARENTS_FOLDER, BasicPermissions.READ));
list.add(createMapping(PermissionMapping.CAN_GET_PROPERTIES_OBJECT, BasicPermissions.READ));
list.add(createMapping(PermissionMapping.CAN_MOVE_OBJECT, BasicPermissions.WRITE));
list.add(createMapping(PermissionMapping.CAN_MOVE_SOURCE, BasicPermissions.READ));
list.add(createMapping(PermissionMapping.CAN_MOVE_TARGET, BasicPermissions.WRITE));
list.add(createMapping(PermissionMapping.CAN_SET_CONTENT_DOCUMENT, BasicPermissions.WRITE));
list.add(createMapping(PermissionMapping.CAN_UPDATE_PROPERTIES_OBJECT, BasicPermissions.WRITE));
list.add(createMapping(PermissionMapping.CAN_VIEW_CONTENT_OBJECT, BasicPermissions.READ));
Map<String, PermissionMapping> map = new LinkedHashMap<String, PermissionMapping>();
for (PermissionMapping pm : list) {
map.put(pm.getKey(), pm);
}
aclCapability.setPermissionMappingData(map);
repositoryInfo.setAclCapabilities(aclCapability);
return repositoryInfo;
}
private PermissionDefinition createPermission(String permission, String description) {
PermissionDefinitionDataImpl pd = new PermissionDefinitionDataImpl();
pd.setId(permission);
pd.setDescription(description);
return pd;
}
private PermissionMapping createMapping(String key, String permission) {
PermissionMappingDataImpl pm = new PermissionMappingDataImpl();
pm.setKey(key);
pm.setPermissions(Collections.singletonList(permission));
return pm;
}
/**
* Returns the id of this repository.
*/
public String getRepositoryId() {
return repositoryId;
}
/**
* Returns the root directory of this repository
*/
public File getRootDirectory() {
return root;
}
/**
* Sets read-only flag for the given user.
*/
public void setUserReadOnly(String user) {
if (user == null || user.length() == 0) {
return;
}
readWriteUserMap.put(user, true);
}
/**
* Sets read-write flag for the given user.
*/
public void setUserReadWrite(String user) {
if (user == null || user.length() == 0) {
return;
}
readWriteUserMap.put(user, false);
}
// --- CMIS operations ---
/**
* CMIS getRepositoryInfo.
*/
public RepositoryInfo getRepositoryInfo(CallContext context) {
checkUser(context, false);
if (context.getCmisVersion() == CmisVersion.CMIS_1_0) {
return repositoryInfo10;
} else {
return repositoryInfo11;
}
}
/**
* CMIS getTypesChildren.
*/
public TypeDefinitionList getTypeChildren(CallContext context, String typeId, Boolean includePropertyDefinitions,
BigInteger maxItems, BigInteger skipCount) {
checkUser(context, false);
return typeManager.getTypeChildren(context, typeId, includePropertyDefinitions, maxItems, skipCount);
}
/**
* CMIS getTypesDescendants.
*/
public List<TypeDefinitionContainer> getTypeDescendants(CallContext context, String typeId, BigInteger depth,
Boolean includePropertyDefinitions) {
checkUser(context, false);
return typeManager.getTypeDescendants(context, typeId, depth, includePropertyDefinitions);
}
/**
* CMIS getTypeDefinition.
*/
public TypeDefinition getTypeDefinition(CallContext context, String typeId) {
checkUser(context, false);
return typeManager.getTypeDefinition(context, typeId);
}
/**
* Create* dispatch for AtomPub.
*/
public ObjectData create(CallContext context, Properties properties, String folderId, ContentStream contentStream,
VersioningState versioningState, ObjectInfoHandler objectInfos) {
boolean userReadOnly = checkUser(context, true);
String typeId = FileBridgeUtils.getObjectTypeId(properties);
TypeDefinition type = typeManager.getInternalTypeDefinition(typeId);
if (type == null) {
throw new CmisObjectNotFoundException("Type '" + typeId + "' is unknown!");
}
String objectId = null;
if (type.getBaseTypeId() == BaseTypeId.CMIS_DOCUMENT) {
objectId = createDocument(context, properties, folderId, contentStream, versioningState);
} else if (type.getBaseTypeId() == BaseTypeId.CMIS_FOLDER) {
objectId = createFolder(context, properties, folderId);
} else {
throw new CmisObjectNotFoundException("Cannot create object of type '" + typeId + "'!");
}
return compileObjectData(context, getFile(objectId), null, false, false, userReadOnly, objectInfos);
}
/**
* CMIS createDocument.
*/
public String createDocument(CallContext context, Properties properties, String folderId,
ContentStream contentStream, VersioningState versioningState) {
checkUser(context, true);
// 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!");
}
// check properties
checkNewProperties(properties, BaseTypeId.CMIS_DOCUMENT);
// check the file
String name = FileBridgeUtils.getStringProperty(properties, PropertyIds.NAME);
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);
}
// write content, if available
if (contentStream != null && contentStream.getStream() != null) {
writeContent(newFile, contentStream.getStream());
}
return getId(newFile);
}
/**
* CMIS createDocumentFromSource.
*/
public String createDocumentFromSource(CallContext context, String sourceId, Properties properties,
String folderId, VersioningState versioningState) {
checkUser(context, true);
// 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!");
}
// check properties
checkCopyProperties(properties, BaseTypeId.CMIS_DOCUMENT.value());
// check the name
String name = null;
if (properties != null && properties.getProperties() != null) {
name = FileBridgeUtils.getStringProperty(properties, PropertyIds.NAME);
}
if (name == null) {
name = source.getName();
}
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 {
writeContent(newFile, new FileInputStream(source));
} catch (IOException e) {
throw new CmisStorageException("Could not roead or write content: " + e.getMessage(), e);
}
return getId(newFile);
}
/**
* Writes the content to disc.
*/
private void writeContent(File newFile, InputStream stream) {
OutputStream out = null;
InputStream in = null;
try {
out = new BufferedOutputStream(new FileOutputStream(newFile), BUFFER_SIZE);
in = new BufferedInputStream(stream, BUFFER_SIZE);
byte[] buffer = new byte[BUFFER_SIZE];
int b;
while ((b = in.read(buffer)) > -1) {
out.write(buffer, 0, b);
}
out.flush();
} catch (IOException e) {
throw new CmisStorageException("Could not write content: " + e.getMessage(), e);
} finally {
IOUtils.closeQuietly(out);
IOUtils.closeQuietly(in);
}
}
/**
* CMIS createFolder.
*/
public String createFolder(CallContext context, Properties properties, String folderId) {
checkUser(context, true);
// check properties
checkNewProperties(properties, BaseTypeId.CMIS_FOLDER);
// get parent File
File parent = getFile(folderId);
if (!parent.isDirectory()) {
throw new CmisObjectNotFoundException("Parent is not a folder!");
}
// create the folder
String name = FileBridgeUtils.getStringProperty(properties, PropertyIds.NAME);
File newFolder = new File(parent, name);
if (!newFolder.mkdir()) {
throw new CmisStorageException("Could not create folder!");
}
return getId(newFolder);
}
/**
* CMIS moveObject.
*/
public ObjectData moveObject(CallContext context, Holder<String> objectId, String targetFolderId,
ObjectInfoHandler objectInfos) {
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));
}
return compileObjectData(context, newFile, null, false, false, userReadOnly, objectInfos);
}
/**
* CMIS setContentStream, deleteContentStream, and appendContentStream.
*/
public void changeContentStream(CallContext context, Holder<String> objectId, Boolean overwriteFlag,
ContentStream contentStream, boolean append) {
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 = FileBridgeUtils.getBooleanParameter(overwriteFlag, true);
if (!owf && file.length() > 0) {
throw new CmisContentAlreadyExistsException("Content already exists!");
}
OutputStream out = null;
InputStream in = null;
try {
out = new BufferedOutputStream(new FileOutputStream(file, append), BUFFER_SIZE);
if (contentStream == null || contentStream.getStream() == null) {
// delete content
out.write(new byte[0]);
} else {
// set content
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);
}
}
} catch (Exception e) {
throw new CmisStorageException("Could not write content: " + e.getMessage(), e);
} finally {
IOUtils.closeQuietly(out);
IOUtils.closeQuietly(in);
}
}
/**
* CMIS deleteObject.
*/
public void deleteObject(CallContext context, String objectId) {
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 file
if (!file.delete()) {
throw new CmisStorageException("Deletion failed!");
}
}
/**
* CMIS deleteTree.
*/
public FailedToDeleteData deleteTree(CallContext context, String folderId, Boolean continueOnFailure) {
checkUser(context, true);
boolean cof = FileBridgeUtils.getBooleanParameter(continueOnFailure, false);
// 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 {
throw new CmisConstraintException("Object is not a folder!");
}
return result;
}
/**
* Removes a folder and its content.
*/
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;
}
/**
* CMIS updateProperties.
*/
public ObjectData updateProperties(CallContext context, Holder<String> objectId, Properties properties,
ObjectInfoHandler objectInfos) {
boolean userReadOnly = checkUser(context, true);
// check object id
if (objectId == null || objectId.getValue() == null) {
throw new CmisInvalidArgumentException("Id is not valid!");
}
// get the file or folder
File file = getFile(objectId.getValue());
// check the properties
String typeId = (file.isDirectory() ? BaseTypeId.CMIS_FOLDER.value() : BaseTypeId.CMIS_DOCUMENT.value());
checkUpdateProperties(properties, typeId);
// get and check the new name
String newName = FileBridgeUtils.getStringProperty(properties, PropertyIds.NAME);
boolean isRename = (newName != null) && (!file.getName().equals(newName));
if (isRename && !isValidName(newName)) {
throw new CmisNameConstraintViolationException("Name is not valid!");
}
// rename file or folder if necessary
File newFile = file;
if (isRename) {
File parent = file.getParentFile();
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));
}
}
return compileObjectData(context, newFile, null, false, false, userReadOnly, objectInfos);
}
/**
* CMIS bulkUpdateProperties.
*/
public List<BulkUpdateObjectIdAndChangeToken> bulkUpdateProperties(CallContext context,
List<BulkUpdateObjectIdAndChangeToken> objectIdAndChangeToken, Properties properties,
ObjectInfoHandler objectInfos) {
checkUser(context, true);
if (objectIdAndChangeToken == null) {
throw new CmisInvalidArgumentException("No object ids provided!");
}
List<BulkUpdateObjectIdAndChangeToken> result = new ArrayList<BulkUpdateObjectIdAndChangeToken>();
for (BulkUpdateObjectIdAndChangeToken oid : objectIdAndChangeToken) {
if (oid == null) {
// ignore invalid ids
continue;
}
try {
Holder<String> oidHolder = new Holder<String>(oid.getId());
updateProperties(context, oidHolder, properties, objectInfos);
result.add(new BulkUpdateObjectIdAndChangeTokenImpl(oid.getId(), oidHolder.getValue(), null));
} catch (CmisBaseException e) {
// ignore exceptions - see specification
}
}
return result;
}
/**
* CMIS getObject.
*/
public ObjectData getObject(CallContext context, String objectId, String versionServicesId, String filter,
Boolean includeAllowableActions, Boolean includeAcl, ObjectInfoHandler objectInfos) {
boolean userReadOnly = checkUser(context, false);
// check id
if (objectId == null && versionServicesId == null) {
throw new CmisInvalidArgumentException("Object Id must be set.");
}
if (objectId == null) {
// this works only because there are no versions in a file system
// and the object id and version series id are the same
objectId = versionServicesId;
}
// get the file or folder
File file = getFile(objectId);
// set defaults if values not set
boolean iaa = FileBridgeUtils.getBooleanParameter(includeAllowableActions, false);
boolean iacl = FileBridgeUtils.getBooleanParameter(includeAcl, false);
// split filter
Set<String> filterCollection = FileBridgeUtils.splitFilter(filter);
// gather properties
return compileObjectData(context, file, filterCollection, iaa, iacl, userReadOnly, objectInfos);
}
/**
* CMIS getAllowableActions.
*/
public AllowableActions getAllowableActions(CallContext context, String objectId) {
boolean userReadOnly = checkUser(context, false);
// get the file or folder
File file = getFile(objectId);
if (!file.exists()) {
throw new CmisObjectNotFoundException("Object not found!");
}
return compileAllowableActions(file, userReadOnly);
}
/**
* CMIS getACL.
*/
public Acl getAcl(CallContext context, String objectId) {
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 ContentStream getContentStream(CallContext context, String objectId, BigInteger offset, BigInteger length) {
checkUser(context, false);
// get the file
final File file = getFile(objectId);
if (!file.isFile()) {
throw new CmisStreamNotSupportedException("Not a file!");
}
if (file.length() == 0) {
throw new CmisConstraintException("Document has no content!");
}
InputStream stream = null;
try {
stream = new BufferedInputStream(new FileInputStream(file), 64 * 1024);
if (offset != null || length != null) {
stream = new ContentRangeInputStream(stream, offset, length);
}
} catch (FileNotFoundException e) {
throw new CmisObjectNotFoundException(e.getMessage(), e);
}
// compile data
ContentStreamImpl result;
if ((offset != null && offset.longValue() > 0) || length != null) {
result = new PartialContentStreamImpl();
} else {
result = new ContentStreamImpl();
}
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,
ObjectInfoHandler objectInfos) {
boolean userReadOnly = checkUser(context, false);
// split filter
Set<String> filterCollection = FileBridgeUtils.splitFilter(filter);
// set defaults if values not set
boolean iaa = FileBridgeUtils.getBooleanParameter(includeAllowableActions, false);
boolean ips = FileBridgeUtils.getBooleanParameter(includePathSegment, false);
// 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 (context.isObjectInfoRequired()) {
compileObjectData(context, 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 files
if (child.isHidden()) {
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(compileObjectData(context, 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, ObjectInfoHandler objectInfos,
boolean foldersOnly) {
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 = FileBridgeUtils.splitFilter(filter);
// set defaults if values not set
boolean iaa = FileBridgeUtils.getBooleanParameter(includeAllowableActions, false);
boolean ips = FileBridgeUtils.getBooleanParameter(includePathSegment, false);
// get the folder
File folder = getFile(folderId);
if (!folder.isDirectory()) {
throw new CmisObjectNotFoundException("Not a folder!");
}
// set object info of the the folder
if (context.isObjectInfoRequired()) {
compileObjectData(context, folder, null, false, false, userReadOnly, objectInfos);
}
// get the tree
List<ObjectInFolderContainer> result = new ArrayList<ObjectInFolderContainer>();
gatherDescendants(context, folder, result, foldersOnly, d, filterCollection, iaa, ips, userReadOnly,
objectInfos);
return result;
}
/**
* Gather the children of a folder.
*/
private void gatherDescendants(CallContext context, File folder, List<ObjectInFolderContainer> list,
boolean foldersOnly, int depth, Set<String> filter, boolean includeAllowableActions,
boolean includePathSegments, boolean userReadOnly, ObjectInfoHandler objectInfos) {
assert folder != null;
assert list != null;
// iterate through children
for (File child : folder.listFiles()) {
// skip hidden and shadow files
if (child.isHidden()) {
continue;
}
// folders only?
if (foldersOnly && !child.isDirectory()) {
continue;
}
// add to list
ObjectInFolderDataImpl objectInFolder = new ObjectInFolderDataImpl();
objectInFolder.setObject(compileObjectData(context, 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(context, child, container.getChildren(), foldersOnly, depth - 1, filter,
includeAllowableActions, includePathSegments, userReadOnly, objectInfos);
}
}
}
/**
* CMIS getFolderParent.
*/
public ObjectData getFolderParent(CallContext context, String folderId, String filter, ObjectInfoHandler objectInfos) {
List<ObjectParentData> parents = getObjectParents(context, folderId, filter, false, false, objectInfos);
if (parents.isEmpty()) {
throw new CmisInvalidArgumentException("The root folder has no parent!");
}
return parents.get(0).getObject();
}
/**
* CMIS getObjectParents.
*/
public List<ObjectParentData> getObjectParents(CallContext context, String objectId, String filter,
Boolean includeAllowableActions, Boolean includeRelativePathSegment, ObjectInfoHandler objectInfos) {
boolean userReadOnly = checkUser(context, false);
// split filter
Set<String> filterCollection = FileBridgeUtils.splitFilter(filter);
// set defaults if values not set
boolean iaa = FileBridgeUtils.getBooleanParameter(includeAllowableActions, false);
boolean irps = FileBridgeUtils.getBooleanParameter(includeRelativePathSegment, false);
// get the file or folder
File file = getFile(objectId);
// don't climb above the root folder
if (root.equals(file)) {
return Collections.emptyList();
}
// set object info of the the object
if (context.isObjectInfoRequired()) {
compileObjectData(context, file, null, false, false, userReadOnly, objectInfos);
}
// get parent folder
File parent = file.getParentFile();
ObjectData object = compileObjectData(context, parent, filterCollection, iaa, false, userReadOnly, objectInfos);
ObjectParentDataImpl result = new ObjectParentDataImpl();
result.setObject(object);
if (irps) {
result.setRelativePathSegment(file.getName());
}
return Collections.<ObjectParentData> singletonList(result);
}
/**
* CMIS getObjectByPath.
*/
public ObjectData getObjectByPath(CallContext context, String folderPath, String filter,
boolean includeAllowableActions, boolean includeACL, ObjectInfoHandler objectInfos) {
boolean userReadOnly = checkUser(context, false);
// split filter
Set<String> filterCollection = FileBridgeUtils.splitFilter(filter);
// check path
if (folderPath == null || folderPath.length() == 0 || folderPath.charAt(0) != '/') {
throw new CmisInvalidArgumentException("Invalid folder path!");
}
// get the file or folder
File file = null;
if (folderPath.length() == 1) {
file = root;
} else {
String path = folderPath.replace('/', File.separatorChar).substring(1);
file = new File(root, path);
}
if (!file.exists()) {
throw new CmisObjectNotFoundException("Path doesn't exist.");
}
return compileObjectData(context, file, filterCollection, includeAllowableActions, includeACL, userReadOnly,
objectInfos);
}
/**
* CMIS query (simple IN_FOLDER queries only)
*/
public ObjectList query(CallContext context, String statement, Boolean includeAllowableActions,
BigInteger maxItems, BigInteger skipCount, ObjectInfoHandler objectInfos) {
boolean userReadOnly = checkUser(context, false);
Matcher matcher = IN_FOLDER_QUERY_PATTERN.matcher(statement.trim());
if (!matcher.matches()) {
throw new CmisInvalidArgumentException("Invalid or unsupported query.");
}
String typeId = matcher.group(1);
String folderId = matcher.group(2);
TypeDefinition type = typeManager.getInternalTypeDefinition(typeId);
if (type == null) {
throw new CmisInvalidArgumentException("Unknown type.");
}
boolean queryFiles = (type.getBaseTypeId() == BaseTypeId.CMIS_DOCUMENT);
if (folderId.length() == 0) {
throw new CmisInvalidArgumentException("Invalid folder id.");
}
// set defaults if values not set
boolean iaa = FileBridgeUtils.getBooleanParameter(includeAllowableActions, false);
// 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 CmisInvalidArgumentException("Not a folder!");
}
// prepare result
ObjectListImpl result = new ObjectListImpl();
result.setObjects(new ArrayList<ObjectData>());
result.setHasMoreItems(false);
int count = 0;
// iterate through children
for (File hit : folder.listFiles()) {
// skip hidden files
if (hit.isHidden()) {
continue;
}
// skip directory if documents are requested
if (hit.isDirectory() && queryFiles) {
continue;
}
// skip files if folders are requested
if (hit.isFile() && !queryFiles) {
continue;
}
count++;
if (skip > 0) {
skip--;
continue;
}
if (result.getObjects().size() >= max) {
result.setHasMoreItems(true);
continue;
}
// build and add child object
ObjectData object = compileObjectData(context, hit, null, iaa, false, userReadOnly, objectInfos);
// set query names
for (PropertyData<?> prop : object.getProperties().getPropertyList()) {
((MutablePropertyData<?>) prop).setQueryName(type.getPropertyDefinitions().get(prop.getId())
.getQueryName());
}
result.getObjects().add(object);
}
result.setNumItems(BigInteger.valueOf(count));
return result;
}
// --- helpers ---
/**
* Compiles an object type object from a file or folder.
*/
private ObjectData compileObjectData(CallContext context, File file, Set<String> filter,
boolean includeAllowableActions, boolean includeAcl, boolean userReadOnly, ObjectInfoHandler objectInfos) {
ObjectDataImpl result = new ObjectDataImpl();
ObjectInfoImpl objectInfo = new ObjectInfoImpl();
result.setProperties(compileProperties(context, file, filter, objectInfo));
if (includeAllowableActions) {
result.setAllowableActions(compileAllowableActions(file, userReadOnly));
}
if (includeAcl) {
result.setAcl(compileAcl(file));
result.setIsExactAcl(true);
}
if (context.isObjectInfoRequired()) {
objectInfo.setObject(result);
objectInfos.addObjectInfo(objectInfo);
}
return result;
}
/**
* Gathers all base properties of a file or folder.
*/
private Properties compileProperties(CallContext context, File file, Set<String> orgfilter,
ObjectInfoImpl objectInfo) {
if (file == null) {
throw new IllegalArgumentException("File must not be null!");
}
// we can't 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;
// identify if the file is a doc or a folder/directory
if (file.isDirectory()) {
typeId = BaseTypeId.CMIS_FOLDER.value();
objectInfo.setBaseType(BaseTypeId.CMIS_FOLDER);
objectInfo.setTypeId(typeId);
objectInfo.setContentType(null);
objectInfo.setFileName(null);
objectInfo.setHasAcl(true);
objectInfo.setHasContent(false);
objectInfo.setVersionSeriesId(null);
objectInfo.setIsCurrentVersion(true);
objectInfo.setRelationshipSourceIds(null);
objectInfo.setRelationshipTargetIds(null);
objectInfo.setRenditionInfos(null);
objectInfo.setSupportsDescendants(true);
objectInfo.setSupportsFolderTree(true);
objectInfo.setSupportsPolicies(false);
objectInfo.setSupportsRelationships(false);
objectInfo.setWorkingCopyId(null);
objectInfo.setWorkingCopyOriginalId(null);
} else {
typeId = BaseTypeId.CMIS_DOCUMENT.value();
objectInfo.setBaseType(BaseTypeId.CMIS_DOCUMENT);
objectInfo.setTypeId(typeId);
objectInfo.setHasAcl(true);
objectInfo.setHasContent(true);
objectInfo.setHasParent(true);
objectInfo.setVersionSeriesId(null);
objectInfo.setIsCurrentVersion(true);
objectInfo.setRelationshipSourceIds(null);
objectInfo.setRelationshipTargetIds(null);
objectInfo.setRenditionInfos(null);
objectInfo.setSupportsDescendants(false);
objectInfo.setSupportsFolderTree(false);
objectInfo.setSupportsPolicies(false);
objectInfo.setSupportsRelationships(false);
objectInfo.setWorkingCopyId(null);
objectInfo.setWorkingCopyOriginalId(null);
}
// exercise 3.3
try {
PropertiesImpl result = new PropertiesImpl();
// id
String id = fileToId(file);
addPropertyId(result, typeId, filter, PropertyIds.OBJECT_ID, id);
objectInfo.setId(id);
// name
String name = file.getName();
addPropertyString(result, typeId, filter, PropertyIds.NAME, name);
objectInfo.setName(name);
// created and modified by
addPropertyString(result, typeId, filter, PropertyIds.CREATED_BY, USER_UNKNOWN);
addPropertyString(result, typeId, filter, PropertyIds.LAST_MODIFIED_BY, USER_UNKNOWN);
objectInfo.setCreatedBy(USER_UNKNOWN);
// creation and modification date
GregorianCalendar lastModified = FileBridgeUtils.millisToCalendar(file.lastModified());
addPropertyDateTime(result, typeId, filter, PropertyIds.CREATION_DATE, lastModified);
addPropertyDateTime(result, typeId, filter, PropertyIds.LAST_MODIFICATION_DATE, lastModified);
objectInfo.setCreationDate(lastModified);
objectInfo.setLastModificationDate(lastModified);
// change token - always null
addPropertyString(result, typeId, filter, PropertyIds.CHANGE_TOKEN, null);
// CMIS 1.1 properties
if (context.getCmisVersion() != CmisVersion.CMIS_1_0) {
addPropertyString(result, typeId, filter, PropertyIds.DESCRIPTION, null);
addPropertyIdList(result, typeId, filter, PropertyIds.SECONDARY_OBJECT_TYPE_IDS, null);
}
// directory or file
if (file.isDirectory()) {
// base type and type name
addPropertyId(result, typeId, filter, PropertyIds.BASE_TYPE_ID, BaseTypeId.CMIS_FOLDER.value());
addPropertyId(result, typeId, filter, PropertyIds.OBJECT_TYPE_ID, BaseTypeId.CMIS_FOLDER.value());
String path = getRepositoryPath(file);
addPropertyString(result, typeId, filter, PropertyIds.PATH, path);
// folder properties
if (!root.equals(file)) {
addPropertyId(result, typeId, filter, PropertyIds.PARENT_ID,
(root.equals(file.getParentFile()) ? ROOT_ID : fileToId(file.getParentFile())));
objectInfo.setHasParent(true);
} else {
addPropertyId(result, typeId, filter, PropertyIds.PARENT_ID, null);
objectInfo.setHasParent(false);
}
addPropertyIdList(result, typeId, filter, PropertyIds.ALLOWED_CHILD_OBJECT_TYPE_IDS, null);
} else {
// base type and type name
addPropertyId(result, typeId, filter, PropertyIds.BASE_TYPE_ID, BaseTypeId.CMIS_DOCUMENT.value());
addPropertyId(result, typeId, filter, PropertyIds.OBJECT_TYPE_ID, BaseTypeId.CMIS_DOCUMENT.value());
// file properties
addPropertyBoolean(result, typeId, filter, PropertyIds.IS_IMMUTABLE, false);
addPropertyBoolean(result, typeId, filter, PropertyIds.IS_LATEST_VERSION, true);
addPropertyBoolean(result, typeId, filter, PropertyIds.IS_MAJOR_VERSION, true);
addPropertyBoolean(result, typeId, filter, PropertyIds.IS_LATEST_MAJOR_VERSION, true);
addPropertyString(result, typeId, filter, PropertyIds.VERSION_LABEL, file.getName());
addPropertyId(result, typeId, filter, PropertyIds.VERSION_SERIES_ID, fileToId(file));
addPropertyBoolean(result, typeId, filter, PropertyIds.IS_VERSION_SERIES_CHECKED_OUT, false);
addPropertyString(result, typeId, filter, PropertyIds.VERSION_SERIES_CHECKED_OUT_BY, null);
addPropertyString(result, typeId, filter, PropertyIds.VERSION_SERIES_CHECKED_OUT_ID, null);
addPropertyString(result, typeId, filter, PropertyIds.CHECKIN_COMMENT, "");
if (context.getCmisVersion() != CmisVersion.CMIS_1_0) {
addPropertyBoolean(result, typeId, filter, PropertyIds.IS_PRIVATE_WORKING_COPY, false);
}
if (file.length() == 0) {
addPropertyBigInteger(result, typeId, filter, PropertyIds.CONTENT_STREAM_LENGTH, null);
addPropertyString(result, typeId, filter, PropertyIds.CONTENT_STREAM_MIME_TYPE, null);
addPropertyString(result, typeId, filter, PropertyIds.CONTENT_STREAM_FILE_NAME, null);
objectInfo.setHasContent(false);
objectInfo.setContentType(null);
objectInfo.setFileName(null);
} else {
addPropertyInteger(result, typeId, filter, PropertyIds.CONTENT_STREAM_LENGTH, file.length());
addPropertyString(result, typeId, filter, PropertyIds.CONTENT_STREAM_MIME_TYPE,
MimeTypes.getMIMEType(file));
addPropertyString(result, typeId, filter, PropertyIds.CONTENT_STREAM_FILE_NAME, file.getName());
objectInfo.setHasContent(true);
objectInfo.setContentType(MimeTypes.getMIMEType(file));
objectInfo.setFileName(file.getName());
}
addPropertyId(result, typeId, filter, PropertyIds.CONTENT_STREAM_ID, null);
}
return result;
} catch (CmisBaseException cbe) {
throw cbe;
} catch (Exception e) {
throw new CmisRuntimeException(e.getMessage(), e);
}
}
/**
* Checks a property set for a new object.
*/
private void checkNewProperties(Properties properties, BaseTypeId baseTypeId) {
// check properties
if (properties == null || properties.getProperties() == null) {
throw new CmisInvalidArgumentException("Properties must be set!");
}
// check the name
String name = FileBridgeUtils.getStringProperty(properties, PropertyIds.NAME);
if (!isValidName(name)) {
throw new CmisNameConstraintViolationException("Name is not valid!");
}
// check the type
String typeId = FileBridgeUtils.getObjectTypeId(properties);
if (typeId == null) {
throw new CmisInvalidArgumentException("Type Id is not set!");
}
TypeDefinition type = typeManager.getInternalTypeDefinition(typeId);
if (type == null) {
throw new CmisObjectNotFoundException("Type '" + typeId + "' is unknown!");
}
if (type.getBaseTypeId() != baseTypeId) {
if (baseTypeId == BaseTypeId.CMIS_DOCUMENT) {
throw new CmisInvalidArgumentException("Type is not a document type!");
} else if (baseTypeId == BaseTypeId.CMIS_DOCUMENT) {
throw new CmisInvalidArgumentException("Type is not a folder type!");
} else {
throw new CmisRuntimeException("A file system does not support a " + baseTypeId.value() + " type!");
}
}
// check type properties
checkTypeProperties(properties, typeId, true);
// check if required properties are missing
for (PropertyDefinition<?> propDef : type.getPropertyDefinitions().values()) {
if (propDef.isRequired() && !properties.getProperties().containsKey(propDef.getId())
&& propDef.getUpdatability() != Updatability.READONLY) {
throw new CmisConstraintException("Property '" + propDef.getId() + "' is required!");
}
}
}
/**
* Checks a property set for a copied document.
*/
private void checkCopyProperties(Properties properties, String sourceTypeId) {
// check properties
if (properties == null || properties.getProperties() == null) {
return;
}
String typeId = sourceTypeId;
// check the name
String name = FileBridgeUtils.getStringProperty(properties, PropertyIds.NAME);
if (name != null) {
if (!isValidName(name)) {
throw new CmisNameConstraintViolationException("Name is not valid!");
}
}
// check the type
typeId = FileBridgeUtils.getObjectTypeId(properties);
if (typeId == null) {
typeId = sourceTypeId;
}
TypeDefinition type = typeManager.getInternalTypeDefinition(typeId);
if (type == null) {
throw new CmisObjectNotFoundException("Type '" + typeId + "' is unknown!");
}
if (type.getBaseTypeId() != BaseTypeId.CMIS_DOCUMENT) {
throw new CmisInvalidArgumentException("Target type must be a document type!");
}
// check type properties
checkTypeProperties(properties, typeId, true);
// check if required properties are missing
for (PropertyDefinition<?> propDef : type.getPropertyDefinitions().values()) {
if (propDef.isRequired() && !properties.getProperties().containsKey(propDef.getId())
&& propDef.getUpdatability() != Updatability.READONLY) {
throw new CmisConstraintException("Property '" + propDef.getId() + "' is required!");
}
}
}
/**
* Checks a property set for an update.
*/
private void checkUpdateProperties(Properties properties, String typeId) {
// check properties
if (properties == null || properties.getProperties() == null) {
throw new CmisInvalidArgumentException("Properties must be set!");
}
// check the name
String name = FileBridgeUtils.getStringProperty(properties, PropertyIds.NAME);
if (name != null) {
if (!isValidName(name)) {
throw new CmisNameConstraintViolationException("Name is not valid!");
}
}
// check type properties
checkTypeProperties(properties, typeId, false);
}
/**
* Checks if the property belong to the type and are settable.
*/
private void checkTypeProperties(Properties properties, String typeId, boolean isCreate) {
// check type
TypeDefinition type = typeManager.getInternalTypeDefinition(typeId);
if (type == null) {
throw new CmisObjectNotFoundException("Type '" + typeId + "' is unknown!");
}
// check if all required properties are there
for (PropertyData<?> prop : properties.getProperties().values()) {
PropertyDefinition<?> propType = type.getPropertyDefinitions().get(prop.getId());
// do we know that property?
if (propType == null) {
throw new CmisConstraintException("Property '" + prop.getId() + "' is unknown!");
}
// can it be set?
if (propType.getUpdatability() == Updatability.READONLY) {
throw new CmisConstraintException("Property '" + prop.getId() + "' is readonly!");
}
if (!isCreate) {
// can it be set?
if (propType.getUpdatability() == Updatability.ONCREATE) {
throw new CmisConstraintException("Property '" + prop.getId() + "' cannot be updated!");
}
}
}
}
private void addPropertyId(PropertiesImpl props, String typeId, Set<String> filter, String id, String value) {
if (!checkAddProperty(props, typeId, filter, id)) {
return;
}
props.addProperty(new PropertyIdImpl(id, value));
}
private void addPropertyIdList(PropertiesImpl props, String typeId, Set<String> filter, String id,
List<String> value) {
if (!checkAddProperty(props, typeId, filter, id)) {
return;
}
props.addProperty(new PropertyIdImpl(id, value));
}
private void addPropertyString(PropertiesImpl props, String typeId, Set<String> filter, String id, String value) {
if (!checkAddProperty(props, typeId, filter, id)) {
return;
}
props.addProperty(new PropertyStringImpl(id, value));
}
private void addPropertyInteger(PropertiesImpl props, String typeId, Set<String> filter, String id, long value) {
addPropertyBigInteger(props, typeId, filter, id, BigInteger.valueOf(value));
}
private void addPropertyBigInteger(PropertiesImpl props, String typeId, Set<String> filter, String id,
BigInteger value) {
if (!checkAddProperty(props, typeId, filter, id)) {
return;
}
props.addProperty(new PropertyIntegerImpl(id, value));
}
private void addPropertyBoolean(PropertiesImpl props, String typeId, Set<String> filter, String id, boolean value) {
if (!checkAddProperty(props, typeId, filter, id)) {
return;
}
props.addProperty(new PropertyBooleanImpl(id, value));
}
private void addPropertyDateTime(PropertiesImpl props, String typeId, Set<String> filter, String id,
GregorianCalendar value) {
if (!checkAddProperty(props, typeId, filter, id)) {
return;
}
props.addProperty(new PropertyDateTimeImpl(id, value));
}
private boolean checkAddProperty(Properties properties, String typeId, Set<String> filter, String id) {
if ((properties == null) || (properties.getProperties() == null)) {
throw new IllegalArgumentException("Properties must not be null!");
}
if (id == null) {
throw new IllegalArgumentException("Id must not be null!");
}
TypeDefinition type = typeManager.getInternalTypeDefinition(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;
}
/**
* Compiles the allowable actions for a file or folder.
*/
private AllowableActions compileAllowableActions(File file, boolean userReadOnly) {
if (file == null) {
throw new IllegalArgumentException("File must not be null!");
}
// we can't gather allowable actions 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 = root.equals(file);
Set<Action> aas = EnumSet.noneOf(Action.class);
addAction(aas, Action.CAN_GET_OBJECT_PARENTS, !isRoot);
addAction(aas, Action.CAN_GET_PROPERTIES, true);
addAction(aas, Action.CAN_UPDATE_PROPERTIES, !userReadOnly && !isReadOnly);
addAction(aas, Action.CAN_MOVE_OBJECT, !userReadOnly && !isRoot);
addAction(aas, Action.CAN_DELETE_OBJECT, !userReadOnly && !isReadOnly && !isRoot);
addAction(aas, Action.CAN_GET_ACL, true);
if (isFolder) {
addAction(aas, Action.CAN_GET_DESCENDANTS, true);
addAction(aas, Action.CAN_GET_CHILDREN, true);
addAction(aas, Action.CAN_GET_FOLDER_PARENT, !isRoot);
addAction(aas, Action.CAN_GET_FOLDER_TREE, true);
addAction(aas, Action.CAN_CREATE_DOCUMENT, !userReadOnly);
addAction(aas, Action.CAN_CREATE_FOLDER, !userReadOnly);
addAction(aas, Action.CAN_DELETE_TREE, !userReadOnly && !isReadOnly);
} else {
addAction(aas, Action.CAN_GET_CONTENT_STREAM, file.length() > 0);
addAction(aas, Action.CAN_SET_CONTENT_STREAM, !userReadOnly && !isReadOnly);
addAction(aas, Action.CAN_DELETE_CONTENT_STREAM, !userReadOnly && !isReadOnly);
addAction(aas, Action.CAN_GET_ALL_VERSIONS, true);
}
AllowableActionsImpl result = new AllowableActionsImpl();
result.setAllowableActions(aas);
return result;
}
private void addAction(Set<Action> aas, Action action, boolean condition) {
if (condition) {
aas.add(action);
}
}
/**
* Compiles the ACL for a file or folder.
*/
private Acl compileAcl(File file) {
AccessControlListImpl result = new AccessControlListImpl();
result.setAces(new ArrayList<Ace>());
for (Map.Entry<String, Boolean> ue : readWriteUserMap.entrySet()) {
// create principal
AccessControlPrincipalDataImpl principal = new AccessControlPrincipalDataImpl(ue.getKey());
// create ACE
AccessControlEntryImpl entry = new AccessControlEntryImpl();
entry.setPrincipal(principal);
entry.setPermissions(new ArrayList<String>());
entry.getPermissions().add(BasicPermissions.READ);
if (!ue.getValue().booleanValue() && file.canWrite()) {
entry.getPermissions().add(BasicPermissions.WRITE);
entry.getPermissions().add(BasicPermissions.ALL);
}
entry.setDirect(true);
// add ACE
result.getAces().add(entry);
}
return result;
}
/**
* 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;
}
return false;
}
/**
* 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 = readWriteUserMap.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 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 IOException {
if (id == null || id.length() == 0) {
throw new CmisInvalidArgumentException("Id is not valid!");
}
if (id.equals(ROOT_ID)) {
return root;
}
return new File(root, (new String(Base64.decode(id.getBytes("US-ASCII")), "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(), e);
}
}
/**
* Creates a File object from an id. A simple and insecure implementation,
* but good enough for now.
*/
private String fileToId(File file) throws IOException {
if (file == null) {
throw new IllegalArgumentException("File is not valid!");
}
if (root.equals(file)) {
return ROOT_ID;
}
String path = getRepositoryPath(file);
return Base64.encodeBytes(path.getBytes("UTF-8"));
}
private String getRepositoryPath(File file) {
String path = file.getAbsolutePath().substring(root.getAbsolutePath().length())
.replace(File.separatorChar, '/');
if (path.length() == 0) {
path = "/";
} else if (path.charAt(0) != '/') {
path = "/" + path;
}
return path;
}
}