blob: 11e682b7cf66c83ca5dfb753757a7bc28171fded [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.jcr;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.jcr.Credentials;
import javax.jcr.ItemNotFoundException;
import javax.jcr.LoginException;
import javax.jcr.NoSuchWorkspaceException;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.PathNotFoundException;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.query.Query;
import javax.jcr.query.QueryManager;
import javax.jcr.query.QueryResult;
import org.apache.chemistry.opencmis.commons.PropertyIds;
import org.apache.chemistry.opencmis.commons.data.AllowableActions;
import org.apache.chemistry.opencmis.commons.data.ContentStream;
import org.apache.chemistry.opencmis.commons.data.FailedToDeleteData;
import org.apache.chemistry.opencmis.commons.data.ObjectData;
import org.apache.chemistry.opencmis.commons.data.ObjectInFolderContainer;
import org.apache.chemistry.opencmis.commons.data.ObjectInFolderData;
import org.apache.chemistry.opencmis.commons.data.ObjectInFolderList;
import org.apache.chemistry.opencmis.commons.data.ObjectList;
import org.apache.chemistry.opencmis.commons.data.ObjectParentData;
import org.apache.chemistry.opencmis.commons.data.Properties;
import org.apache.chemistry.opencmis.commons.data.RepositoryInfo;
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.CapabilityAcl;
import org.apache.chemistry.opencmis.commons.enums.CapabilityChanges;
import org.apache.chemistry.opencmis.commons.enums.CapabilityContentStreamUpdates;
import org.apache.chemistry.opencmis.commons.enums.CapabilityJoin;
import org.apache.chemistry.opencmis.commons.enums.CapabilityQuery;
import org.apache.chemistry.opencmis.commons.enums.CapabilityRenditions;
import org.apache.chemistry.opencmis.commons.enums.VersioningState;
import org.apache.chemistry.opencmis.commons.exceptions.CmisConstraintException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisNotSupportedException;
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.CmisUpdateConflictException;
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.RepositoryCapabilitiesImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.RepositoryInfoImpl;
import org.apache.chemistry.opencmis.commons.server.ObjectInfoHandler;
import org.apache.chemistry.opencmis.commons.spi.Holder;
import org.apache.chemistry.opencmis.jcr.query.QueryTranslator;
import org.apache.chemistry.opencmis.jcr.type.JcrDocumentTypeHandler;
import org.apache.chemistry.opencmis.jcr.type.JcrFolderTypeHandler;
import org.apache.chemistry.opencmis.jcr.type.JcrTypeHandlerManager;
import org.apache.chemistry.opencmis.jcr.util.Util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* JCR back-end for CMIS server.
*/
public class JcrRepository {
private static final Logger LOG = LoggerFactory.getLogger(JcrRepository.class);
protected final Repository repository;
protected final JcrTypeManager typeManager;
protected final PathManager pathManager;
protected final JcrTypeHandlerManager typeHandlerManager;
/**
* Create a new <code>JcrRepository</code> instance backed by a JCR
* repository.
*
* @param repository
* the JCR repository
* @param pathManager
* @param typeManager
* @param typeHandlerManager
*/
public JcrRepository(Repository repository, PathManager pathManager, JcrTypeManager typeManager,
JcrTypeHandlerManager typeHandlerManager) {
this.repository = repository;
this.typeManager = typeManager;
this.typeHandlerManager = typeHandlerManager;
this.pathManager = pathManager;
}
/**
* Logger into the underlying JCR repository.
*
* @param credentials
* @param workspaceName
* @return
* @throws LoginException
* @throws NoSuchWorkspaceException
* @throws RepositoryException
*/
public Session login(Credentials credentials, String workspaceName) {
try {
return repository.login(credentials, workspaceName);
} catch (LoginException e) {
LOG.debug(e.getMessage(), e);
throw new CmisPermissionDeniedException(e.getMessage(), e);
} catch (NoSuchWorkspaceException e) {
LOG.debug(e.getMessage(), e);
throw new CmisObjectNotFoundException(e.getMessage(), e);
} catch (RepositoryException e) {
LOG.debug(e.getMessage(), e);
throw new CmisRuntimeException(e.getMessage(), e);
}
}
/**
* See CMIS 1.0 section 2.2.2.2 getRepositoryInfo
*/
public RepositoryInfo getRepositoryInfo(Session session) {
LOG.debug("getRepositoryInfo");
return compileRepositoryInfo(session.getWorkspace().getName());
}
/**
* See CMIS 1.0 section 2.2.2.2 getRepositoryInfo
*/
public List<RepositoryInfo> getRepositoryInfos(Session session) {
try {
ArrayList<RepositoryInfo> infos = new ArrayList<RepositoryInfo>();
for (String wspName : session.getWorkspace().getAccessibleWorkspaceNames()) {
infos.add(compileRepositoryInfo(wspName));
}
return infos;
} catch (RepositoryException e) {
LOG.debug(e.getMessage(), e);
throw new CmisRuntimeException(e.getMessage(), e);
}
}
/**
* See CMIS 1.0 section 2.2.2.3 getTypeChildren
*/
public TypeDefinitionList getTypeChildren(Session session, String typeId, boolean includePropertyDefinitions,
BigInteger maxItems, BigInteger skipCount) {
LOG.debug("getTypesChildren");
return typeManager.getTypeChildren(typeId, includePropertyDefinitions, maxItems, skipCount);
}
/**
* See CMIS 1.0 section 2.2.2.5 getTypeDefinition
*/
public TypeDefinition getTypeDefinition(Session session, String typeId) {
LOG.debug("getTypeDefinition");
TypeDefinition type = typeManager.getType(typeId);
if (type == null) {
throw new CmisObjectNotFoundException("Type '" + typeId + "' is unknown!");
}
return JcrTypeManager.copyTypeDefinition(type);
}
/**
* See CMIS 1.0 section 2.2.2.4 getTypeDescendants
*/
public List<TypeDefinitionContainer> getTypesDescendants(Session session, String typeId, BigInteger depth,
Boolean includePropertyDefinitions) {
LOG.debug("getTypesDescendants");
return typeManager.getTypesDescendants(typeId, depth, includePropertyDefinitions);
}
/**
* See CMIS 1.0 section 2.2.4.1 createDocument
*/
public String createDocument(Session session, Properties properties, String folderId, ContentStream contentStream,
VersioningState versioningState) {
LOG.debug("createDocument");
// check properties
if (properties == null || properties.getProperties() == null) {
throw new CmisInvalidArgumentException("Properties must be set!");
}
// check type
String typeId = PropertyHelper.getTypeId(properties);
TypeDefinition type = typeManager.getType(typeId);
if (type == null) {
throw new CmisObjectNotFoundException("Type '" + typeId + "' is unknown!");
}
boolean isVersionable = JcrTypeManager.isVersionable(type);
if (!isVersionable && versioningState != VersioningState.NONE) {
throw new CmisConstraintException("Versioning not supported for " + typeId);
}
if (isVersionable && versioningState == VersioningState.NONE) {
throw new CmisConstraintException("Versioning required for " + typeId);
}
// get the name
String name = PropertyHelper.getStringProperty(properties, PropertyIds.NAME);
// get parent Node and create child
JcrFolder parent = getJcrNode(session, folderId).asFolder();
JcrDocumentTypeHandler typeHandler = typeHandlerManager.getDocumentTypeHandler(typeId);
JcrNode jcrNode = typeHandler.createDocument(parent, JcrConverter.toJcrName(name), properties, contentStream,
versioningState);
return jcrNode.getId();
}
/**
* See CMIS 1.0 section 2.2.4.2 createDocumentFromSource
*/
public String createDocumentFromSource(Session session, String sourceId, Properties properties, String folderId,
VersioningState versioningState) {
LOG.debug("createDocumentFromSource");
// get parent folder Node
JcrFolder parent = getJcrNode(session, folderId).asFolder();
// get source document Node
JcrDocument source = getJcrNode(session, sourceId).asDocument();
boolean isVersionable = source.isVersionable();
if (!isVersionable && versioningState != VersioningState.NONE) {
throw new CmisConstraintException("Versioning not supported for " + sourceId);
}
if (isVersionable && versioningState == VersioningState.NONE) {
throw new CmisConstraintException("Versioning required for " + sourceId);
}
// create child from source
JcrNode jcrNode = parent.addNodeFromSource(source, properties);
return jcrNode.getId();
}
/**
* See CMIS 1.0 section 2.2.4.3 createFolder
*/
public String createFolder(Session session, Properties properties, String folderId) {
LOG.debug("createFolder");
// check properties
if (properties == null || properties.getProperties() == null) {
throw new CmisInvalidArgumentException("Properties must be set!");
}
// check type
String typeId = PropertyHelper.getTypeId(properties);
TypeDefinition type = typeManager.getType(typeId);
if (type == null) {
throw new CmisObjectNotFoundException("Type '" + typeId + "' is unknown!");
}
// get the name
String name = PropertyHelper.getStringProperty(properties, PropertyIds.NAME);
// get parent Node
JcrFolder parent = getJcrNode(session, folderId).asFolder();
JcrFolderTypeHandler typeHandler = typeHandlerManager.getFolderTypeHandler(typeId);
JcrNode jcrNode = typeHandler.createFolder(parent, JcrConverter.toJcrName(name), properties);
return jcrNode.getId();
}
/**
* See CMIS 1.0 section 2.2.4.13 moveObject
*/
public ObjectData moveObject(Session session, Holder<String> objectId, String targetFolderId,
ObjectInfoHandler objectInfos, boolean requiresObjectInfo) {
LOG.debug("moveObject");
if (objectId == null || objectId.getValue() == null) {
throw new CmisInvalidArgumentException("Id is not valid!");
}
// get the node and parent
JcrNode jcrNode = getJcrNode(session, objectId.getValue());
JcrFolder parent = getJcrNode(session, targetFolderId).asFolder();
jcrNode = jcrNode.move(parent);
objectId.setValue(jcrNode.getId());
return jcrNode.compileObjectType(null, false, objectInfos, requiresObjectInfo);
}
/**
* See CMIS 1.0 section 2.2.4.16 setContentStream
*/
public void setContentStream(Session session, Holder<String> objectId, Boolean overwriteFlag,
ContentStream contentStream) {
LOG.debug("setContentStream or deleteContentStream");
if (objectId == null || objectId.getValue() == null) {
throw new CmisInvalidArgumentException("Id is not valid!");
}
JcrDocument jcrDocument = getJcrNode(session, objectId.getValue()).asDocument();
String id = jcrDocument.setContentStream(contentStream, Boolean.TRUE.equals(overwriteFlag)).getId();
objectId.setValue(id);
}
/**
* See CMIS 1.0 section 2.2.4.14 deleteObject
*/
public void deleteObject(Session session, String objectId, Boolean allVersions) {
LOG.debug("deleteObject");
// get the node
JcrNode jcrNode = getJcrNode(session, objectId);
try {
// check on private copy
boolean isPwc = jcrNode.isVersionable()
&& JcrPrivateWorkingCopy.isPwc(jcrNode.asVersion().getVersionLabel());
jcrNode.delete(Boolean.TRUE.equals(allVersions), isPwc);
} catch (RepositoryException rex) {
LOG.debug(rex.getMessage(), rex);
throw new CmisRuntimeException(rex.getMessage(), rex);
}
}
/**
* See CMIS 1.0 section 2.2.4.15 deleteTree
*/
public FailedToDeleteData deleteTree(Session session, String folderId) {
LOG.debug("deleteTree");
// get the folder
JcrFolder jcrFolder = getJcrNode(session, folderId).asFolder();
return jcrFolder.deleteTree();
}
/**
* See CMIS 1.0 section 2.2.4.12 updateProperties
*/
public ObjectData updateProperties(Session session, Holder<String> objectId, Properties properties,
ObjectInfoHandler objectInfos, boolean objectInfoRequired) {
LOG.debug("updateProperties");
if (objectId == null) {
throw new CmisInvalidArgumentException("Id is not valid!");
}
// get the node
JcrNode jcrNode = getJcrNode(session, objectId.getValue());
String id = jcrNode.updateProperties(properties).getId();
objectId.setValue(id);
return jcrNode.compileObjectType(null, false, objectInfos, objectInfoRequired);
}
/**
* See CMIS 1.0 section 2.2.4.7 getObject
*/
public ObjectData getObject(Session session, String objectId, String filter, Boolean includeAllowableActions,
ObjectInfoHandler objectInfos, boolean requiresObjectInfo) {
LOG.debug("getObject");
// check id
if (objectId == null) {
throw new CmisInvalidArgumentException("Object Id must be set.");
}
// get the node
JcrNode jcrNode = getJcrNode(session, objectId);
// gather properties
return jcrNode.compileObjectType(splitFilter(filter), includeAllowableActions, objectInfos, requiresObjectInfo);
}
/**
* See CMIS 1.0 section 2.2.4.8 getProperties
*/
public Properties getProperties(Session session, String objectId, String filter, Boolean includeAllowableActions,
ObjectInfoHandler objectInfos, boolean requiresObjectInfo) {
ObjectData object = getObject(session, objectId, filter, includeAllowableActions, objectInfos,
requiresObjectInfo);
return object.getProperties();
}
/**
* See CMIS 1.0 section 2.2.4.6 getAllowableActions
*/
public AllowableActions getAllowableActions(Session session, String objectId) {
LOG.debug("getAllowableActions");
JcrNode jcrNode = getJcrNode(session, objectId);
return jcrNode.getAllowableActions();
}
/**
* See CMIS 1.0 section 2.2.4.10 getContentStream
*/
public ContentStream getContentStream(Session session, String objectId, BigInteger offset, BigInteger length) {
LOG.debug("getContentStream");
if (offset != null || length != null) {
throw new CmisInvalidArgumentException("Offset and Length are not supported!");
}
// get the node
JcrDocument jcrDocument = getJcrNode(session, objectId).asDocument();
return jcrDocument.getContentStream();
}
/**
* See CMIS 1.0 section 2.2.3.1 getChildren
*/
public ObjectInFolderList getChildren(Session session, String folderId, String filter,
Boolean includeAllowableActions, Boolean includePathSegment, BigInteger maxItems, BigInteger skipCount,
ObjectInfoHandler objectInfos, boolean requiresObjectInfo) {
LOG.debug("getChildren");
// 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
JcrFolder jcrFolder = getJcrNode(session, folderId).asFolder();
// set object info of the the folder
if (requiresObjectInfo) {
jcrFolder.compileObjectType(null, false, objectInfos, requiresObjectInfo);
}
// prepare result
ObjectInFolderListImpl result = new ObjectInFolderListImpl();
result.setObjects(new ArrayList<ObjectInFolderData>());
result.setHasMoreItems(false);
int count = 0;
// iterate through children
Set<String> splitFilter = splitFilter(filter);
Iterator<JcrNode> childNodes = jcrFolder.getNodes();
while (childNodes.hasNext()) {
JcrNode child = childNodes.next();
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(child.compileObjectType(splitFilter, includeAllowableActions, objectInfos,
requiresObjectInfo));
if (Boolean.TRUE.equals(includePathSegment)) {
objectInFolder.setPathSegment(child.getName());
}
result.getObjects().add(objectInFolder);
}
result.setNumItems(BigInteger.valueOf(count));
return result;
}
/**
* See CMIS 1.0 section 2.2.3.2 getDescendants
*/
public List<ObjectInFolderContainer> getDescendants(Session session, String folderId, BigInteger depth,
String filter, Boolean includeAllowableActions, Boolean includePathSegment, ObjectInfoHandler objectInfos,
boolean requiresObjectInfo, boolean foldersOnly) {
LOG.debug("getDescendants or getFolderTree");
// check depth
int d = depth == null ? 2 : depth.intValue();
if (d == 0) {
throw new CmisInvalidArgumentException("Depth must not be 0!");
}
if (d < -1) {
d = -1;
}
// get the folder
JcrFolder jcrFolder = getJcrNode(session, folderId).asFolder();
// set object info of the the folder
if (requiresObjectInfo) {
jcrFolder.compileObjectType(null, false, objectInfos, requiresObjectInfo);
}
// get the tree
List<ObjectInFolderContainer> result = new ArrayList<ObjectInFolderContainer>();
gatherDescendants(jcrFolder, result, foldersOnly, d, splitFilter(filter), includeAllowableActions,
includePathSegment, objectInfos, requiresObjectInfo);
return result;
}
/**
* See CMIS 1.0 section 2.2.3.4 getFolderParent
*/
public ObjectData getFolderParent(Session session, String folderId, String filter, ObjectInfoHandler objectInfos,
boolean requiresObjectInfo) {
List<ObjectParentData> parents = getObjectParents(session, folderId, filter, false, false, objectInfos,
requiresObjectInfo);
if (parents.isEmpty()) {
throw new CmisInvalidArgumentException("The root folder has no parent!");
}
return parents.get(0).getObject();
}
/**
* See CMIS 1.0 section 2.2.3.5 getObjectParents
*/
public List<ObjectParentData> getObjectParents(Session session, String objectId, String filter,
Boolean includeAllowableActions, Boolean includeRelativePathSegment, ObjectInfoHandler objectInfos,
boolean requiresObjectInfo) {
LOG.debug("getObjectParents");
// get the file or folder
JcrNode jcrNode = getJcrNode(session, objectId);
// don't climb above the root folder
if (jcrNode.isRoot()) {
return Collections.emptyList();
}
// set object info of the the object
if (requiresObjectInfo) {
jcrNode.compileObjectType(null, false, objectInfos, requiresObjectInfo);
}
// get parent
JcrNode parent = jcrNode.getParent();
ObjectData object = parent.compileObjectType(splitFilter(filter), includeAllowableActions, objectInfos,
requiresObjectInfo);
ObjectParentDataImpl result = new ObjectParentDataImpl();
result.setObject(object);
if (Boolean.TRUE.equals(includeRelativePathSegment)) {
result.setRelativePathSegment(parent.getName());
}
return Collections.singletonList((ObjectParentData) result);
}
/**
* See CMIS 1.0 section 2.2.4.9 getObjectByPath
*/
public ObjectData getObjectByPath(Session session, String folderPath, String filter,
boolean includeAllowableActions, boolean includeACL, ObjectInfoHandler objectInfos,
boolean requiresObjectInfo) {
LOG.debug("getObjectByPath");
// check path
if (folderPath == null || !PathManager.isAbsolute(folderPath)) {
throw new CmisInvalidArgumentException("Invalid folder path!");
}
JcrNode root = getJcrNode(session, PathManager.CMIS_ROOT_ID);
JcrNode jcrNode;
if (PathManager.isRoot(folderPath)) {
jcrNode = root;
} else {
String path = PathManager.relativize(PathManager.CMIS_ROOT_PATH, folderPath);
jcrNode = root.getNode(path);
}
return jcrNode.compileObjectType(splitFilter(filter), includeAllowableActions, objectInfos, requiresObjectInfo);
}
/**
* See CMIS 1.0 section 2.2.3.6 getCheckedOutDocs
*/
public ObjectList getCheckedOutDocs(Session session, String folderId, String filter, String orderBy,
Boolean includeAllowableActions, BigInteger maxItems, BigInteger skipCount) {
LOG.debug("getCheckedOutDocs");
// skip and max
int skip = skipCount == null ? 0 : skipCount.intValue();
if (skip < 0) {
skip = 0;
}
int max = maxItems == null ? Integer.MAX_VALUE : maxItems.intValue();
if (max < 0) {
max = Integer.MAX_VALUE;
}
try {
// Build xpath query of the form
// '//path/to/folderId//*[jcr:isCheckedOut='true' and
// (not(@jcr:createdBy) or @jcr:createdBy='admin')]'
String xPath = "/*[jcr:isCheckedOut='true' " + "and (not(@jcr:createdBy) or @jcr:createdBy='"
+ session.getUserID() + "')]";
if (folderId != null) {
JcrFolder jcrFolder = getJcrNode(session, folderId).asFolder();
String path = jcrFolder.getNode().getPath();
if ("/".equals(path)) {
path = "";
}
xPath = '/' + Util.escape(path) + xPath;
} else {
xPath = '/' + xPath;
}
// Execute query
QueryManager queryManager = session.getWorkspace().getQueryManager();
Query query = queryManager.createQuery(xPath, Query.XPATH);
QueryResult queryResult = query.execute();
// prepare results
ObjectListImpl result = new ObjectListImpl();
result.setObjects(new ArrayList<ObjectData>());
result.setHasMoreItems(false);
// iterate through children
Set<String> splitFilter = splitFilter(filter);
int count = 0;
NodeIterator nodes = queryResult.getNodes();
while (nodes.hasNext()) {
Node node = nodes.nextNode();
JcrNode jcrNode = typeHandlerManager.create(node);
if (!jcrNode.isVersionable()) {
continue;
}
count++;
if (skip > 0) {
skip--;
continue;
}
if (result.getObjects().size() >= max) {
result.setHasMoreItems(true);
continue;
}
// build and add child object
JcrPrivateWorkingCopy jcrVersion = jcrNode.asVersion().getPwc();
ObjectData objectData = jcrVersion.compileObjectType(splitFilter, includeAllowableActions, null, false);
result.getObjects().add(objectData);
}
result.setNumItems(BigInteger.valueOf(count));
return result;
} catch (RepositoryException e) {
LOG.debug(e.getMessage(), e);
throw new CmisRuntimeException(e.getMessage(), e);
}
}
/**
* See CMIS 1.0 section 2.2.7.1 checkOut
*/
public void checkOut(Session session, Holder<String> objectId, Holder<Boolean> contentCopied) {
LOG.debug("checkout");
// check id
if (objectId == null || objectId.getValue() == null) {
throw new CmisInvalidArgumentException("Object Id must be set.");
}
// get the node
JcrNode jcrNode = getJcrNode(session, objectId.getValue());
if (!jcrNode.isVersionable()) {
throw new CmisUpdateConflictException("Not a version: " + jcrNode);
}
// checkout
JcrPrivateWorkingCopy pwc = jcrNode.asVersion().checkout();
objectId.setValue(pwc.getId());
if (contentCopied != null) {
contentCopied.setValue(true);
}
}
/**
* See CMIS 1.0 section 2.2.7.2 cancelCheckout
*/
public void cancelCheckout(Session session, String objectId) {
LOG.debug("cancelCheckout");
// check id
if (objectId == null) {
throw new CmisInvalidArgumentException("Object Id must be set.");
}
// get the node
JcrNode jcrNode = getJcrNode(session, objectId);
if (!jcrNode.isVersionable()) {
throw new CmisUpdateConflictException("Not a version: " + jcrNode);
}
// cancelCheckout
jcrNode.asVersion().cancelCheckout();
}
/**
* See CMIS 1.0 section 2.2.7.3 checkedIn
*/
public void checkIn(Session session, Holder<String> objectId, Boolean major, Properties properties,
ContentStream contentStream, String checkinComment) {
LOG.debug("checkin");
// check id
if (objectId == null || objectId.getValue() == null) {
throw new CmisInvalidArgumentException("Object Id must be set.");
}
// get the node
JcrNode jcrNode;
try {
jcrNode = getJcrNode(session, objectId.getValue());
} catch (CmisObjectNotFoundException e) {
throw new CmisUpdateConflictException(e.getCause().getMessage(), e.getCause());
}
if (!jcrNode.isVersionable()) {
throw new CmisUpdateConflictException("Not a version: " + jcrNode);
}
// checkin
JcrVersion checkedIn = jcrNode.asVersion().checkin(properties, contentStream, checkinComment);
objectId.setValue(checkedIn.getId());
}
/**
* See CMIS 1.0 section 2.2.7.6 getAllVersions
*/
public List<ObjectData> getAllVersions(Session session, String objectId, String filter,
Boolean includeAllowableActions, ObjectInfoHandler objectInfos, boolean requiresObjectInfo) {
LOG.debug("getAllVersions");
// check id
if (objectId == null) {
throw new CmisInvalidArgumentException("Object Id must be set.");
}
Set<String> splitFilter = splitFilter(filter);
// get the node
JcrNode jcrNode = getJcrNode(session, objectId);
// Collect versions
if (jcrNode.isVersionable()) {
JcrVersionBase jcrVersion = jcrNode.asVersion();
Iterator<JcrVersion> versions = jcrVersion.getVersions();
if (versions.hasNext()) {
versions.next(); // skip root version
}
List<ObjectData> allVersions = new ArrayList<ObjectData>();
while (versions.hasNext()) {
JcrVersion version = versions.next();
ObjectData objectData = version.compileObjectType(splitFilter, includeAllowableActions, objectInfos,
requiresObjectInfo);
allVersions.add(objectData);
}
// Add pwc if checked out
if (jcrVersion.isDocumentCheckedOut()) {
JcrPrivateWorkingCopy pwc = jcrVersion.getPwc();
ObjectData objectData = pwc.compileObjectType(splitFilter, includeAllowableActions, objectInfos,
requiresObjectInfo);
allVersions.add(objectData);
}
// CMIS mandates descending order
Collections.reverse(allVersions);
return allVersions;
} else {
// Single version
ObjectData objectData = jcrNode.compileObjectType(splitFilter, includeAllowableActions, objectInfos,
requiresObjectInfo);
return Collections.singletonList(objectData);
}
}
/**
* See CMIS 1.0 section 2.2.6.1 query
*/
public ObjectList query(final Session session, String statement, Boolean searchAllVersions,
Boolean includeAllowableActions, BigInteger maxItems, BigInteger skipCount) {
LOG.debug("query");
if (searchAllVersions) {
throw new CmisNotSupportedException("Not supported: query for all versions");
}
// 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;
}
QueryTranslator queryTranslator = new QueryTranslator(typeManager) {
@Override
protected String jcrPathFromId(String id) {
try {
JcrFolder folder = getJcrNode(session, id).asFolder();
String path = folder.getNode().getPath();
return Util.escape(path);
} catch (RepositoryException e) {
LOG.debug(e.getMessage(), e);
throw new CmisRuntimeException(e.getMessage(), e);
}
}
@Override
protected String jcrPathFromCol(TypeDefinition fromType, String name) {
return typeHandlerManager.getIdentifierMap(fromType.getId()).jcrPathFromCol(name);
}
@Override
protected String jcrTypeName(TypeDefinition fromType) {
return typeHandlerManager.getIdentifierMap(fromType.getId()).jcrTypeName();
}
@Override
protected String jcrTypeCondition(TypeDefinition fromType) {
return typeHandlerManager.getIdentifierMap(fromType.getId()).jcrTypeCondition();
}
};
String xPath = queryTranslator.translateToXPath(statement);
try {
// Execute query
QueryManager queryManager = session.getWorkspace().getQueryManager();
Query query = queryManager.createQuery(xPath, Query.XPATH);
if (skip > 0) {
query.setOffset(skip);
}
if (max < Integer.MAX_VALUE) {
query.setLimit(max + 1); // One more in order to detect whether
// there are more items
}
QueryResult queryResult = query.execute();
// prepare results
ObjectListImpl result = new ObjectListImpl();
result.setObjects(new ArrayList<ObjectData>());
result.setHasMoreItems(false);
// iterate through children
int count = 0;
NodeIterator nodes = queryResult.getNodes();
while (nodes.hasNext() && result.getObjects().size() < max) {
Node node = nodes.nextNode();
JcrNode jcrNode = typeHandlerManager.create(node);
count++;
// Get pwc if this node is versionable and checked out
if (jcrNode.isVersionable() && jcrNode.asVersion().isCheckedOut()) {
jcrNode = jcrNode.asVersion().getPwc();
}
// build and add child object
ObjectData objectData = jcrNode.compileObjectType(null, includeAllowableActions, null, false);
result.getObjects().add(objectData);
}
result.setHasMoreItems(nodes.hasNext());
result.setNumItems(BigInteger.valueOf(count));
return result;
} catch (RepositoryException e) {
LOG.debug(e.getMessage(), e);
throw new CmisRuntimeException(e.getMessage(), e);
}
}
// ------------------------------------------< protected >---
protected RepositoryInfo compileRepositoryInfo(String repositoryId) {
RepositoryInfoImpl fRepositoryInfo = new RepositoryInfoImpl();
fRepositoryInfo.setId(repositoryId);
fRepositoryInfo.setName(getRepositoryName());
fRepositoryInfo.setDescription(getRepositoryDescription());
fRepositoryInfo.setCmisVersionSupported("1.0");
fRepositoryInfo.setProductName("OpenCMIS JCR");
fRepositoryInfo.setProductVersion("0.3");
fRepositoryInfo.setVendorName("OpenCMIS");
fRepositoryInfo.setRootFolder(PathManager.CMIS_ROOT_ID);
fRepositoryInfo.setThinClientUri("");
RepositoryCapabilitiesImpl capabilities = new RepositoryCapabilitiesImpl();
capabilities.setCapabilityAcl(CapabilityAcl.NONE);
capabilities.setAllVersionsSearchable(false);
capabilities.setCapabilityJoin(CapabilityJoin.NONE);
capabilities.setSupportsMultifiling(false);
capabilities.setSupportsUnfiling(false);
capabilities.setSupportsVersionSpecificFiling(false);
capabilities.setIsPwcSearchable(false);
capabilities.setIsPwcUpdatable(true);
capabilities.setCapabilityQuery(CapabilityQuery.BOTHCOMBINED);
capabilities.setCapabilityChanges(CapabilityChanges.OBJECTIDSONLY);
capabilities.setCapabilityContentStreamUpdates(CapabilityContentStreamUpdates.ANYTIME);
capabilities.setSupportsGetDescendants(true);
capabilities.setSupportsGetFolderTree(true);
capabilities.setCapabilityRendition(CapabilityRenditions.NONE);
fRepositoryInfo.setCapabilities(capabilities);
return fRepositoryInfo;
}
protected String getRepositoryName() {
return repository.getDescriptor(Repository.REP_NAME_DESC);
}
protected String getRepositoryDescription() {
StringBuilder description = new StringBuilder();
for (String key : repository.getDescriptorKeys()) {
description.append(key).append('=').append(repository.getDescriptor(key)).append('\n');
}
return description.toString();
}
protected JcrNode getJcrNode(Session session, String id) {
try {
if (id == null || id.length() == 0) {
throw new CmisInvalidArgumentException("Null or empty id");
}
if (id.equals(PathManager.CMIS_ROOT_ID)) {
return typeHandlerManager.create(getRootNode(session));
}
Node node = session.getNodeByIdentifier(id);
JcrNode jcrNode = typeHandlerManager.create(node);
// if node isn't under versioning, then return retrieved object
if (!jcrNode.isVersionable()) {
return jcrNode;
}
JcrVersionBase versionNode = jcrNode.asVersion();
if (JcrPrivateWorkingCopy.denotesPwc(versionNode.getVersionLabel())) {
return versionNode.getPwc();
} else {
JcrVersion version = versionNode.getVersion(((JcrVersion) versionNode).getVersionName());
return version;
}
} catch (ItemNotFoundException e) {
LOG.debug(e.getMessage(), e);
throw new CmisObjectNotFoundException(e.getMessage(), e);
} catch (RepositoryException e) {
LOG.debug(e.getMessage(), e);
throw new CmisRuntimeException(e.getMessage(), e);
}
}
protected Node getRootNode(Session session) {
try {
return session.getNode(pathManager.getJcrRootPath());
} catch (PathNotFoundException e) {
LOG.debug(e.getMessage(), e);
throw new CmisObjectNotFoundException(e.getMessage(), e);
} catch (ItemNotFoundException e) {
LOG.debug(e.getMessage(), e);
throw new CmisObjectNotFoundException(e.getMessage(), e);
} catch (RepositoryException e) {
LOG.debug(e.getMessage(), e);
throw new CmisRuntimeException(e.getMessage(), e);
}
}
// ------------------------------------------< private >---
/**
* Transitively gather the children of a node down to a specific depth
*/
private static void gatherDescendants(JcrFolder jcrFolder, List<ObjectInFolderContainer> list, boolean foldersOnly,
int depth, Set<String> filter, Boolean includeAllowableActions, Boolean includePathSegments,
ObjectInfoHandler objectInfos, boolean requiresObjectInfo) {
// iterate through children
Iterator<JcrNode> childNodes = jcrFolder.getNodes();
while (childNodes.hasNext()) {
JcrNode child = childNodes.next();
// folders only?
if (foldersOnly && !child.isFolder()) {
continue;
}
// add to list
ObjectInFolderDataImpl objectInFolder = new ObjectInFolderDataImpl();
objectInFolder.setObject(child.compileObjectType(filter, includeAllowableActions, objectInfos,
requiresObjectInfo));
if (Boolean.TRUE.equals(includePathSegments)) {
objectInFolder.setPathSegment(child.getName());
}
ObjectInFolderContainerImpl container = new ObjectInFolderContainerImpl();
container.setObject(objectInFolder);
list.add(container);
// move to next level
if (depth != 1 && child.isFolder()) {
container.setChildren(new ArrayList<ObjectInFolderContainer>());
gatherDescendants(child.asFolder(), container.getChildren(), foldersOnly, depth - 1, filter,
includeAllowableActions, includePathSegments, objectInfos, requiresObjectInfo);
}
}
}
/**
* Splits a filter statement into a collection of properties.
*/
private static Set<String> splitFilter(String filter) {
if (filter == null) {
return null;
}
if (filter.trim().length() == 0) {
return null;
}
Set<String> result = new HashSet<String>();
for (String s : filter.split(",")) {
s = s.trim();
if (s.equals("*")) {
return null;
} else if (s.length() > 0) {
result.add(s);
}
}
// set a few base properties
// query name == id (for base type properties)
result.add(PropertyIds.OBJECT_ID);
result.add(PropertyIds.OBJECT_TYPE_ID);
result.add(PropertyIds.BASE_TYPE_ID);
return result;
}
public Repository getRepository() {
return repository;
}
public JcrTypeManager getTypeManager() {
return typeManager;
}
public PathManager getPathManager() {
return pathManager;
}
public JcrTypeHandlerManager getTypeHandlerManager() {
return typeHandlerManager;
}
}