/*
* Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Florent Guillaume
*/
package org.eclipse.ecr.opencmis.impl.server;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import org.apache.chemistry.opencmis.client.api.ObjectId;
import org.apache.chemistry.opencmis.client.api.ObjectType;
import org.apache.chemistry.opencmis.client.api.OperationContext;
import org.apache.chemistry.opencmis.client.api.Policy;
import org.apache.chemistry.opencmis.client.runtime.ObjectIdImpl;
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.ContentStream;
import org.apache.chemistry.opencmis.commons.data.ExtensionsData;
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.PropertyData;
import org.apache.chemistry.opencmis.commons.data.RenditionData;
import org.apache.chemistry.opencmis.commons.data.RepositoryInfo;
import org.apache.chemistry.opencmis.commons.definitions.PropertyBooleanDefinition;
import org.apache.chemistry.opencmis.commons.definitions.PropertyDateTimeDefinition;
import org.apache.chemistry.opencmis.commons.definitions.PropertyDecimalDefinition;
import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition;
import org.apache.chemistry.opencmis.commons.definitions.PropertyHtmlDefinition;
import org.apache.chemistry.opencmis.commons.definitions.PropertyIdDefinition;
import org.apache.chemistry.opencmis.commons.definitions.PropertyIntegerDefinition;
import org.apache.chemistry.opencmis.commons.definitions.PropertyStringDefinition;
import org.apache.chemistry.opencmis.commons.definitions.PropertyUriDefinition;
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.BaseTypeId;
import org.apache.chemistry.opencmis.commons.enums.Cardinality;
import org.apache.chemistry.opencmis.commons.enums.ChangeType;
import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships;
import org.apache.chemistry.opencmis.commons.enums.RelationshipDirection;
import org.apache.chemistry.opencmis.commons.enums.UnfileObject;
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.CmisNotSupportedException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException;
import org.apache.chemistry.opencmis.commons.impl.Converter;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.AbstractPropertyData;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.BindingsObjectFactoryImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ChangeEventInfoDataImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.FailedToDeleteDataImpl;
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.PropertiesImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIdImpl;
import org.apache.chemistry.opencmis.commons.impl.jaxb.CmisTypeContainer;
import org.apache.chemistry.opencmis.commons.impl.server.AbstractCmisService;
import org.apache.chemistry.opencmis.commons.server.CallContext;
import org.apache.chemistry.opencmis.commons.server.ObjectInfo;
import org.apache.chemistry.opencmis.commons.spi.BindingsObjectFactory;
import org.apache.chemistry.opencmis.commons.spi.Holder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.ecr.audit.api.AuditReader;
import org.eclipse.ecr.audit.api.LogEntry;
import org.eclipse.ecr.core.api.ClientException;
import org.eclipse.ecr.core.api.CoreSession;
import org.eclipse.ecr.core.api.DocumentModel;
import org.eclipse.ecr.core.api.DocumentModelList;
import org.eclipse.ecr.core.api.DocumentRef;
import org.eclipse.ecr.core.api.Filter;
import org.eclipse.ecr.core.api.IdRef;
import org.eclipse.ecr.core.api.IterableQueryResult;
import org.eclipse.ecr.core.api.LifeCycleConstants;
import org.eclipse.ecr.core.api.PathRef;
import org.eclipse.ecr.core.api.VersioningOption;
import org.eclipse.ecr.core.api.event.DocumentEventTypes;
import org.eclipse.ecr.core.api.impl.CompoundFilter;
import org.eclipse.ecr.core.api.impl.FacetFilter;
import org.eclipse.ecr.core.api.impl.LifeCycleFilter;
import org.eclipse.ecr.core.api.model.PropertyException;
import org.eclipse.ecr.core.api.pathsegment.PathSegmentService;
import org.eclipse.ecr.core.api.repository.Repository;
import org.eclipse.ecr.core.api.repository.RepositoryManager;
import org.eclipse.ecr.core.schema.FacetNames;
import org.eclipse.ecr.opencmis.impl.util.ListUtils;
import org.eclipse.ecr.opencmis.impl.util.ListUtils.BatchedList;
import org.eclipse.ecr.runtime.api.Framework;
import org.nuxeo.common.utils.Path;
/**
* Nuxeo implementation of the CMIS Services, on top of a {@link CoreSession}.
*/
public class NuxeoCmisService extends AbstractCmisService {
private static final Log log = LogFactory.getLog(NuxeoCmisService.class);
public static final int DEFAULT_TYPE_LEVELS = 2;
public static final int DEFAULT_FOLDER_LEVELS = 2;
public static final int DEFAULT_CHANGE_LOG_SIZE = 100;
public static final int DEFAULT_QUERY_SIZE = 100;
public static final int DEFAULT_MAX_CHILDREN = 100;
public static final String NUXEO_WAR = "nuxeo.war";
protected final BindingsObjectFactory objectFactory = new BindingsObjectFactoryImpl();
protected final NuxeoRepository repository;
protected final CoreSession coreSession;
/** When false, we don't own the core session and shouldn't close it. */
protected final boolean coreSessionOwned;
/** Filter that hides HiddenInNavigation and deleted objects. */
protected final Filter documentFilter;
protected final CallContext callContext;
/** Constructor called by binding. */
public NuxeoCmisService(NuxeoRepository repository, CallContext callContext) {
this.repository = repository;
this.callContext = callContext;
this.coreSession = repository == null ? null
: openCoreSession(repository.getId());
coreSessionOwned = true;
documentFilter = getDocumentFilter();
}
/** Constructor called by high-level session from existing core session. */
public NuxeoCmisService(NuxeoRepository repository,
CallContext callContext, CoreSession coreSession) {
this.repository = repository;
this.callContext = callContext;
this.coreSession = coreSession;
coreSessionOwned = false;
documentFilter = getDocumentFilter();
}
// called in a finally block from dispatcher
@Override
public void close() {
if (coreSession != null && coreSessionOwned) {
closeCoreSession();
}
clearObjectInfos();
}
protected CoreSession openCoreSession(String repositoryId) {
try {
Repository repository = Framework.getService(
RepositoryManager.class).getRepository(repositoryId);
if (repository == null) {
throw new CmisRuntimeException("Cannot get repository: "
+ repositoryId);
}
return repository.open();
} catch (CmisRuntimeException e) {
throw e;
} catch (Exception e) {
throw new CmisRuntimeException(e.toString(), e);
}
}
protected void closeCoreSession() {
try {
Repository.close(coreSession);
} catch (Exception e) {
throw new CmisRuntimeException(e.toString(), e);
}
}
public NuxeoRepository getNuxeoRepository() {
return repository;
}
public CoreSession getCoreSession() {
return coreSession;
}
public BindingsObjectFactory getObjectFactory() {
return objectFactory;
}
public CallContext getCallContext() {
return callContext;
}
/** Gets the filter that hides HiddenInNavigation and deleted objects. */
protected Filter getDocumentFilter() {
Filter facetFilter = new FacetFilter(FacetNames.HIDDEN_IN_NAVIGATION,
false);
Filter lcFilter = new LifeCycleFilter(LifeCycleConstants.DELETED_STATE,
false);
return new CompoundFilter(facetFilter, lcFilter);
}
protected String getIdFromDocumentRef(DocumentRef ref)
throws ClientException {
if (ref instanceof IdRef) {
return ((IdRef) ref).value;
} else {
return coreSession.getDocument(ref).getId();
}
}
/* This is the only method that does not have a repositoryId / coreSession. */
@Override
public List<RepositoryInfo> getRepositoryInfos(ExtensionsData extension) {
List<NuxeoRepository> repos = NuxeoRepositories.getRepositories();
List<RepositoryInfo> infos = new ArrayList<RepositoryInfo>(repos.size());
for (NuxeoRepository repo : repos) {
String latestChangeLogToken = getLatestChangeLogToken(repo.getId());
infos.add(repo.getRepositoryInfo(latestChangeLogToken));
}
return infos;
}
@Override
public RepositoryInfo getRepositoryInfo(String repositoryId,
ExtensionsData extension) {
String latestChangeLogToken = getLatestChangeLogToken(repositoryId);
return NuxeoRepositories.getRepository(repositoryId).getRepositoryInfo(
latestChangeLogToken);
}
@Override
public TypeDefinition getTypeDefinition(String repositoryId, String typeId,
ExtensionsData extension) {
TypeDefinition type = repository.getTypeDefinition(typeId);
if (type == null) {
throw new CmisInvalidArgumentException("No such type: " + typeId);
}
// TODO copy only when local binding
// clone
return Converter.convert(Converter.convert(type));
}
@Override
public TypeDefinitionList getTypeChildren(String repositoryId,
String typeId, Boolean includePropertyDefinitions,
BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) {
TypeDefinitionList types = repository.getTypeChildren(typeId,
includePropertyDefinitions, maxItems, skipCount);
// TODO copy only when local binding
// clone
return Converter.convert(Converter.convert(types));
}
@Override
public List<TypeDefinitionContainer> getTypeDescendants(
String repositoryId, String typeId, BigInteger depth,
Boolean includePropertyDefinitions, ExtensionsData extension) {
int d = depth == null ? DEFAULT_TYPE_LEVELS : depth.intValue();
List<TypeDefinitionContainer> types = repository.getTypeDescendants(
typeId, d, includePropertyDefinitions);
// clone
// TODO copy only when local binding
List<CmisTypeContainer> tmp = new ArrayList<CmisTypeContainer>(
types.size());
Converter.convertTypeContainerList(types, tmp);
return Converter.convertTypeContainerList(tmp);
}
protected DocumentModel getDocumentModel(String id) {
DocumentRef docRef = new IdRef(id);
try {
if (!coreSession.exists(docRef)) {
throw new CmisObjectNotFoundException(docRef.toString());
}
DocumentModel doc = coreSession.getDocument(docRef);
if (isFilteredOut(doc)) {
throw new CmisObjectNotFoundException(docRef.toString());
}
return doc;
} catch (ClientException e) {
throw new CmisRuntimeException(e.toString(), e);
}
}
@Override
public NuxeoObjectData getObject(String repositoryId, String objectId,
String filter, Boolean includeAllowableActions,
IncludeRelationships includeRelationships, String renditionFilter,
Boolean includePolicyIds, Boolean includeAcl,
ExtensionsData extension) {
DocumentModel doc = getDocumentModel(objectId);
NuxeoObjectData data = new NuxeoObjectData(this, doc, filter,
includeAllowableActions, includeRelationships, renditionFilter,
includePolicyIds, includeAcl, extension);
collectObjectInfo(repositoryId, data);
return data;
}
/**
* Checks if the doc should be ignored because it is "invisible" (deleted,
* hidden in navigation).
*/
public boolean isFilteredOut(DocumentModel doc) {
return !documentFilter.accept(doc);
}
protected DocumentModel createDocumentModel(ObjectId folder,
TypeDefinition type) {
DocumentModel doc;
String typeId = type.getId();
String nuxeoTypeId = type.getLocalName();
if (BaseTypeId.CMIS_DOCUMENT.value().equals(typeId)) {
nuxeoTypeId = NuxeoTypeHelper.NUXEO_FILE;
} else if (BaseTypeId.CMIS_FOLDER.value().equals(typeId)) {
nuxeoTypeId = NuxeoTypeHelper.NUXEO_FOLDER;
} else if (BaseTypeId.CMIS_RELATIONSHIP.value().equals(typeId)) {
nuxeoTypeId = NuxeoTypeHelper.NUXEO_RELATION;
}
try {
doc = coreSession.createDocumentModel(nuxeoTypeId);
} catch (ClientException e) {
throw new IllegalArgumentException(typeId);
}
if (folder != null) {
DocumentModel parentDoc;
try {
parentDoc = coreSession.getDocument(new IdRef(folder.getId()));
} catch (ClientException e) {
throw new CmisRuntimeException("Cannot create object", e);
}
String pathSegment = nuxeoTypeId; // default path segment based on
// id
doc.setPathInfo(parentDoc.getPathAsString(), pathSegment);
}
return doc;
}
// create and save session
protected NuxeoObjectData createObject(Properties properties,
ObjectId folder, BaseTypeId baseType, ContentStream contentStream) {
String typeId;
Map<String, PropertyData<?>> p;
PropertyData<?> d;
TypeDefinition type = null;
if (properties != null //
&& (p = properties.getProperties()) != null //
&& (d = p.get(PropertyIds.OBJECT_TYPE_ID)) != null) {
typeId = (String) d.getFirstValue();
if (baseType == null) {
type = repository.getTypeDefinition(typeId);
if (type == null) {
throw new IllegalArgumentException(typeId);
}
baseType = type.getBaseTypeId();
}
} else {
typeId = null;
}
if (typeId == null) {
switch (baseType) {
case CMIS_DOCUMENT:
typeId = "File"; // TODO constant
break;
case CMIS_FOLDER:
typeId = BaseTypeId.CMIS_FOLDER.value();
break;
case CMIS_POLICY:
throw new CmisRuntimeException("Cannot create policy");
case CMIS_RELATIONSHIP:
throw new CmisRuntimeException("Cannot create relationship");
default:
throw new CmisRuntimeException("No base type");
}
}
if (type == null) {
type = repository.getTypeDefinition(typeId);
}
if (type == null || type.getBaseTypeId() != baseType) {
throw new CmisInvalidArgumentException(typeId);
}
if (type.isCreatable() == Boolean.FALSE) {
throw new CmisInvalidArgumentException("Not creatable: " + typeId);
}
DocumentModel doc = createDocumentModel(folder, type);
NuxeoObjectData data = new NuxeoObjectData(this, doc);
updateProperties(data, null, properties, true);
try {
if (contentStream != null) {
if (contentStream.getFileName() == null) {
// infer filename from properties
PropertyData<?> pd = properties.getProperties().get(
PropertyIds.NAME);
if (pd != null) {
String filename = (String) pd.getFirstValue();
contentStream = new ContentStreamImpl(filename,
contentStream.getBigLength(),
contentStream.getMimeType(),
contentStream.getStream());
}
}
try {
NuxeoPropertyData.setContentStream(doc, contentStream, true);
} catch (CmisContentAlreadyExistsException e) {
// cannot happen, overwrite = true
}
}
// set path segment from title
PathSegmentService pss = Framework.getService(PathSegmentService.class);
String pathSegment = pss.generatePathSegment(doc);
Path path = doc.getPath();
doc.setPathInfo(path == null ? null
: path.removeLastSegments(1).toString(), pathSegment);
data.doc = coreSession.createDocument(doc);
coreSession.save();
} catch (IOException e) {
throw new CmisRuntimeException(e.toString(), e);
} catch (Exception e) {
throw new CmisRuntimeException("Cannot create", e);
}
return data;
}
protected <T> void updateProperties(NuxeoObjectData object,
String changeToken, Properties properties, boolean creation) {
TypeDefinition type = object.getTypeDefinition();
// TODO changeToken
Map<String, PropertyData<?>> p;
if (properties == null || (p = properties.getProperties()) == null) {
return;
}
for (Entry<String, PropertyData<?>> en : p.entrySet()) {
String key = en.getKey();
PropertyData<?> d = en.getValue();
setObjectProperty(object, key, d, type, creation);
}
}
protected <T> void updateProperties(NuxeoObjectData object,
String changeToken, Map<String, ?> properties, TypeDefinition type,
boolean creation) {
// TODO changeToken
if (properties == null) {
return;
}
for (Entry<String, ?> en : properties.entrySet()) {
String key = en.getKey();
Object value = en.getValue();
@SuppressWarnings("unchecked")
PropertyDefinition<T> pd = (PropertyDefinition<T>) type.getPropertyDefinitions().get(
key);
if (pd == null) {
throw new CmisRuntimeException("Unknown property: " + key);
}
setObjectProperty(object, key, value, pd, creation);
}
}
protected <T> void setObjectProperty(NuxeoObjectData object, String key,
PropertyData<T> d, TypeDefinition type, boolean creation) {
@SuppressWarnings("unchecked")
PropertyDefinition<T> pd = (PropertyDefinition<T>) type.getPropertyDefinitions().get(
key);
if (pd == null) {
throw new CmisRuntimeException("Unknown property: " + key);
}
Object value;
if (d == null) {
value = null;
} else if (pd.getCardinality() == Cardinality.SINGLE) {
value = d.getFirstValue();
} else {
value = d.getValues();
}
setObjectProperty(object, key, value, pd, creation);
}
protected <T> void setObjectProperty(NuxeoObjectData object, String key,
Object value, PropertyDefinition<T> pd, boolean creation) {
Updatability updatability = pd.getUpdatability();
if (updatability == Updatability.READONLY
|| (updatability == Updatability.ONCREATE && !creation)) {
// log.error("Read-only property, ignored: " + key);
return;
}
if (PropertyIds.OBJECT_TYPE_ID.equals(key)
|| PropertyIds.LAST_MODIFICATION_DATE.equals(key)) {
return;
}
// TODO avoid constructing property object just to set value
NuxeoPropertyDataBase<T> np = (NuxeoPropertyDataBase<T>) NuxeoPropertyData.construct(
object, pd, callContext);
np.setValue(value);
}
protected NuxeoObjectData setInitialVersioningState(NuxeoObjectData object,
VersioningState versioningState) {
try {
if (versioningState == null) {
// default is MAJOR, per spec
versioningState = VersioningState.MAJOR;
}
switch (versioningState) {
case NONE: // cannot be made non-versionable in Nuxeo
case CHECKEDOUT:
break;
case MINOR:
object.doc.checkIn(VersioningOption.MINOR, null);
object.doc.getCoreSession().save();
break;
case MAJOR:
object.doc.checkIn(VersioningOption.MAJOR, null);
object.doc.getCoreSession().save();
break;
}
return object;
} catch (ClientException e) {
throw new CmisRuntimeException(e.toString(), e);
}
}
@Override
public String create(String repositoryId, Properties properties,
String folderId, ContentStream contentStream,
VersioningState versioningState, List<String> policies,
ExtensionsData extension) {
// TODO policies
NuxeoObjectData object = createObject(properties, new ObjectIdImpl(
folderId), null, contentStream);
setInitialVersioningState(object, versioningState);
return object.getId();
}
@Override
public String createDocument(String repositoryId, Properties properties,
String folderId, ContentStream contentStream,
VersioningState versioningState, List<String> policies,
Acl addAces, Acl removeAces, ExtensionsData extension) {
// TODO policies, addAces, removeAces
NuxeoObjectData object = createObject(properties, new ObjectIdImpl(
folderId), BaseTypeId.CMIS_DOCUMENT, contentStream);
setInitialVersioningState(object, versioningState);
return object.getId();
}
@Override
public String createFolder(String repositoryId, Properties properties,
String folderId, List<String> policies, Acl addAces,
Acl removeAces, ExtensionsData extension) {
// TODO policies, addAces, removeAces
NuxeoObjectData object = createObject(properties, new ObjectIdImpl(
folderId), BaseTypeId.CMIS_FOLDER, null);
return object.getId();
}
@Override
public String createPolicy(String repositoryId, Properties properties,
String folderId, List<String> policies, Acl addAces,
Acl removeAces, ExtensionsData extension) {
throw new CmisNotSupportedException();
}
@Override
public String createRelationship(String repositoryId,
Properties properties, List<String> policies, Acl addAces,
Acl removeAces, ExtensionsData extension) {
NuxeoObjectData object = createObject(properties, null,
BaseTypeId.CMIS_RELATIONSHIP, null);
return object.getId();
}
@Override
public String createDocumentFromSource(String repositoryId,
String sourceId, Properties properties, String folderId,
VersioningState versioningState, List<String> policies,
Acl addAces, Acl removeAces, ExtensionsData extension) {
if (folderId == null) {
// no unfileable objects for now
throw new CmisInvalidArgumentException("Invalid null folder ID");
}
DocumentModel doc = getDocumentModel(sourceId);
DocumentModel folder = getDocumentModel(folderId);
try {
DocumentModel copyDoc = coreSession.copy(doc.getRef(),
folder.getRef(), null);
NuxeoObjectData copy = new NuxeoObjectData(this, copyDoc);
if (properties != null && properties.getPropertyList() != null
&& !properties.getPropertyList().isEmpty()) {
updateProperties(copy, null, properties, false);
copy.doc = coreSession.saveDocument(copyDoc);
}
coreSession.save();
setInitialVersioningState(copy, versioningState);
return copy.getId();
} catch (ClientException e) {
throw new CmisRuntimeException(e.toString(), e);
}
}
public NuxeoObjectData copy(String sourceId, String targetId,
Map<String, ?> properties, TypeDefinition type,
VersioningState versioningState, List<Policy> policies,
List<Ace> addACEs, List<Ace> removeACEs, OperationContext context) {
DocumentModel doc = getDocumentModel(sourceId);
DocumentModel folder = getDocumentModel(targetId);
try {
DocumentModel copyDoc = coreSession.copy(doc.getRef(),
folder.getRef(), null);
NuxeoObjectData copy = new NuxeoObjectData(this, copyDoc, context);
if (properties != null && !properties.isEmpty()) {
updateProperties(copy, null, properties, type, false);
copy.doc = coreSession.saveDocument(copyDoc);
}
coreSession.save();
setInitialVersioningState(copy, versioningState);
return copy;
} catch (ClientException e) {
throw new CmisRuntimeException(e.toString(), e);
}
}
@Override
public void deleteContentStream(String repositoryId,
Holder<String> objectIdHolder, Holder<String> changeTokenHolder,
ExtensionsData extension) {
setContentStream(repositoryId, objectIdHolder, Boolean.TRUE,
changeTokenHolder, null, extension);
}
@Override
public FailedToDeleteData deleteTree(String repositoryId, String folderId,
Boolean allVersions, UnfileObject unfileObjects,
Boolean continueOnFailure, ExtensionsData extension) {
if (unfileObjects == UnfileObject.UNFILE) {
throw new CmisConstraintException("Unfiling not supported");
}
if (repository.getRootFolderId().equals(folderId)) {
throw new CmisInvalidArgumentException("Cannot delete root");
}
try {
DocumentModel doc = getDocumentModel(folderId);
if (!doc.isFolder()) {
throw new CmisInvalidArgumentException("Not a folder: "
+ folderId);
}
coreSession.removeDocument(new IdRef(folderId));
coreSession.save();
// TODO returning null fails in opencmis 0.1.0 due to
// org.apache.chemistry.opencmis.client.runtime.PersistentFolderImpl.deleteTree
return new FailedToDeleteDataImpl();
} catch (ClientException e) {
throw new CmisRuntimeException(e.toString(), e);
}
}
@Override
public AllowableActions getAllowableActions(String repositoryId,
String objectId, ExtensionsData extension) {
DocumentModel doc = getDocumentModel(objectId);
return NuxeoObjectData.getAllowableActions(doc, false);
}
@Override
public ContentStream getContentStream(String repositoryId, String objectId,
String streamId, BigInteger offset, BigInteger length,
ExtensionsData extension) {
// TODO offset, length
if (NuxeoObjectData.STREAM_ICON.equals(streamId)) {
try {
DocumentModel doc = getDocumentModel(objectId);
String iconPath;
try {
iconPath = (String) doc.getPropertyValue(NuxeoTypeHelper.NX_ICON);
} catch (PropertyException e) {
iconPath = null;
}
InputStream is = NuxeoObjectData.getIconStream(iconPath,
callContext);
if (is == null) {
throw new CmisConstraintException(
"No icon content stream: " + objectId);
}
int slash = iconPath.lastIndexOf('/');
String filename = slash == -1 ? iconPath
: iconPath.substring(slash + 1);
long len;
try {
len = NuxeoObjectData.getStreamLength(is);
} catch (IOException e) {
throw new CmisRuntimeException(e.toString(), e);
}
// refetch now-consumed stream
is = NuxeoObjectData.getIconStream(iconPath, callContext);
return new ContentStreamImpl(filename, BigInteger.valueOf(len),
NuxeoObjectData.getIconMimeType(iconPath), is);
} catch (ClientException e) {
throw new CmisRuntimeException(e.toString(), e);
}
} else if (streamId == null) {
DocumentModel doc = getDocumentModel(objectId);
ContentStream cs = NuxeoPropertyData.getContentStream(doc);
if (cs != null) {
return cs;
}
throw new CmisConstraintException("No content stream: " + objectId);
}
throw new CmisInvalidArgumentException("Invalid stream id: " + streamId);
}
@Override
public List<RenditionData> getRenditions(String repositoryId,
String objectId, String renditionFilter, BigInteger maxItems,
BigInteger skipCount, ExtensionsData extension) {
DocumentModel doc = getDocumentModel(objectId);
return NuxeoObjectData.getRenditions(doc, maxItems, skipCount,
callContext);
}
@Override
public ObjectData getObjectByPath(String repositoryId, String path,
String filter, Boolean includeAllowableActions,
IncludeRelationships includeRelationships, String renditionFilter,
Boolean includePolicyIds, Boolean includeAcl,
ExtensionsData extension) {
DocumentModel doc;
try {
DocumentRef pathRef = new PathRef(path);
if (coreSession.exists(pathRef)) {
doc = coreSession.getDocument(pathRef);
if (isFilteredOut(doc)) {
throw new CmisObjectNotFoundException(path);
}
} else {
// Adobe Drive 2 confuses cmis:name and path segment
// try using sequence of titles
doc = getObjectByPathOfNames(path);
}
} catch (ClientException e) {
throw new CmisRuntimeException(e.toString(), e);
}
ObjectData data = new NuxeoObjectData(this, doc, filter,
includeAllowableActions, includeRelationships, renditionFilter,
includePolicyIds, includeAcl, extension);
collectObjectInfo(repositoryId, data);
return data;
}
/**
* Gets a document given a path built out of dc:title components.
* <p>
* Filtered out docs are ignored.
*/
protected DocumentModel getObjectByPathOfNames(String path)
throws ClientException, CmisObjectNotFoundException {
DocumentModel doc = coreSession.getRootDocument();
for (String name : new Path(path).segments()) {
String query = String.format(
"SELECT * FROM Document WHERE ecm:parentId = %s AND dc:title = %s",
escapeStringForNXQL(doc.getId()), escapeStringForNXQL(name));
DocumentModelList docs = coreSession.query(query);
if (docs.isEmpty()) {
throw new CmisObjectNotFoundException(path);
}
doc = null;
for (DocumentModel d : docs) {
if (isFilteredOut(d)) {
continue;
}
if (doc == null) {
doc = d;
} else {
log.warn(String.format(
"Path '%s' returns several documents for '%s'",
path, name));
break;
}
}
if (doc == null) {
throw new CmisObjectNotFoundException(path);
}
}
return doc;
}
protected static String REPLACE_QUOTE = Matcher.quoteReplacement("\\'");
protected static String escapeStringForNXQL(String s) {
return "'" + s.replaceAll("'", REPLACE_QUOTE) + "'";
}
@Override
public Properties getProperties(String repositoryId, String objectId,
String filter, ExtensionsData extension) {
DocumentModel doc = getDocumentModel(objectId);
NuxeoObjectData data = new NuxeoObjectData(this, doc, filter, null,
null, null, null, null, null);
return data.getProperties();
}
protected boolean collectObjectInfos = true;
protected Map<String, ObjectInfo> objectInfos;
// part of CMIS API and of ObjectInfoHandler
@Override
public ObjectInfo getObjectInfo(String repositoryId, String objectId) {
ObjectInfo info = getObjectInfos().get(objectId);
if (info != null) {
return info;
}
DocumentModel doc = getDocumentModel(objectId);
NuxeoObjectData data = new NuxeoObjectData(this, doc, null,
Boolean.TRUE, IncludeRelationships.BOTH, null, Boolean.TRUE,
Boolean.FALSE, null);
return getObjectInfo(repositoryId, data);
}
// AbstractCmisService helper
@Override
protected ObjectInfo getObjectInfo(String repositoryId, ObjectData data) {
ObjectInfo info = getObjectInfos().get(data.getId());
if (info != null) {
return info;
}
try {
collectObjectInfos = false;
info = getObjectInfoIntern(repositoryId, data);
getObjectInfos().put(info.getId(), info);
} catch (Exception e) {
log.error(e.toString(), e);
} finally {
collectObjectInfos = true;
}
return info;
}
protected Map<String, ObjectInfo> getObjectInfos() {
if (objectInfos == null) {
objectInfos = new HashMap<String, ObjectInfo>();
}
return objectInfos;
}
@Override
public void clearObjectInfos() {
objectInfos = null;
}
protected void collectObjectInfo(String repositoryId, String objectId) {
if (collectObjectInfos && callContext.isObjectInfoRequired()) {
getObjectInfo(repositoryId, objectId);
}
}
protected void collectObjectInfo(String repositoryId, ObjectData data) {
if (collectObjectInfos && callContext.isObjectInfoRequired()) {
getObjectInfo(repositoryId, data);
}
}
@Override
public void addObjectInfo(ObjectInfo info) {
// ObjectInfoHandler, unused here
throw new UnsupportedOperationException();
}
@Override
public void moveObject(String repositoryId, Holder<String> objectIdHolder,
String targetFolderId, String sourceFolderId,
ExtensionsData extension) {
String objectId;
if (objectIdHolder == null
|| (objectId = objectIdHolder.getValue()) == null) {
throw new CmisInvalidArgumentException("Missing object ID");
}
if (repository.getRootFolderId().equals(objectId)) {
throw new CmisConstraintException("Cannot move root");
}
if (targetFolderId == null) {
throw new CmisInvalidArgumentException("Missing target folder ID");
}
try {
getDocumentModel(objectId); // check exists and not deleted
DocumentRef docRef = new IdRef(objectId);
DocumentModel parent = coreSession.getParentDocument(docRef);
if (isFilteredOut(parent)) {
throw new CmisObjectNotFoundException("No parent: " + objectId);
}
if (sourceFolderId == null) {
sourceFolderId = parent.getId();
} else {
// check it's the actual parent
if (!parent.getId().equals(sourceFolderId)) {
throw new CmisInvalidArgumentException("Object " + objectId
+ " is not filed in " + sourceFolderId);
}
}
DocumentModel target = getDocumentModel(targetFolderId);
if (!target.isFolder()) {
throw new CmisInvalidArgumentException(
"Target is not a folder: " + targetFolderId);
}
coreSession.move(docRef, new IdRef(targetFolderId), null);
coreSession.save();
} catch (ClientException e) {
throw new CmisRuntimeException(e.toString(), e);
}
}
@Override
public void setContentStream(String repositoryId,
Holder<String> objectIdHolder, Boolean overwriteFlag,
Holder<String> changeTokenHolder, ContentStream contentStream,
ExtensionsData extension) {
String objectId;
if (objectIdHolder == null
|| (objectId = objectIdHolder.getValue()) == null) {
throw new CmisInvalidArgumentException("Missing object ID");
}
DocumentModel doc = getDocumentModel(objectId);
// TODO test doc checkout state
try {
NuxeoPropertyData.setContentStream(doc, contentStream,
!Boolean.FALSE.equals(overwriteFlag));
coreSession.saveDocument(doc);
coreSession.save();
} catch (CmisBaseException e) {
throw e;
} catch (Exception e) {
throw new CmisRuntimeException(e.toString(), e);
}
}
@Override
public void updateProperties(String repositoryId,
Holder<String> objectIdHolder, Holder<String> changeTokenHolder,
Properties properties, ExtensionsData extension) {
String objectId;
if (objectIdHolder == null
|| (objectId = objectIdHolder.getValue()) == null) {
throw new CmisInvalidArgumentException("Missing object ID");
}
DocumentModel doc = getDocumentModel(objectId);
NuxeoObjectData object = new NuxeoObjectData(this, doc);
String changeToken = changeTokenHolder == null ? null
: changeTokenHolder.getValue();
updateProperties(object, changeToken, properties, false);
try {
coreSession.saveDocument(doc);
coreSession.save();
} catch (ClientException e) {
throw new CmisRuntimeException(e.toString(), e);
}
}
@Override
public Acl applyAcl(String repositoryId, String objectId, Acl addAces,
Acl removeAces, AclPropagation aclPropagation,
ExtensionsData extension) {
throw new CmisNotSupportedException();
}
@Override
public Acl applyAcl(String repositoryId, String objectId, Acl aces,
AclPropagation aclPropagation) {
throw new CmisNotSupportedException();
}
@Override
public Acl getAcl(String repositoryId, String objectId,
Boolean onlyBasicPermissions, ExtensionsData extension) {
throw new CmisNotSupportedException();
}
@Override
public ObjectList getContentChanges(String repositoryId,
Holder<String> changeLogTokenHolder, Boolean includeProperties,
String filter, Boolean includePolicyIds, Boolean includeAcl,
BigInteger maxItems, ExtensionsData extension) {
if (changeLogTokenHolder == null) {
throw new CmisInvalidArgumentException(
"Missing change log token holder");
}
String changeLogToken = changeLogTokenHolder.getValue();
long minDate;
if (changeLogToken == null) {
minDate = 0;
} else {
try {
minDate = Long.parseLong(changeLogToken);
} catch (NumberFormatException e) {
throw new CmisInvalidArgumentException(
"Invalid change log token");
}
}
try {
AuditReader reader = Framework.getService(AuditReader.class);
if (reader == null) {
throw new CmisRuntimeException("Cannot find audit service");
}
int max = maxItems == null ? -1 : maxItems.intValue();
if (max < 0) {
max = DEFAULT_CHANGE_LOG_SIZE;
}
// TODO XXX repositoryId as well
Map<String, Object> params = new HashMap<String, Object>();
String query = "FROM LogEntry log" //
+ " WHERE log.eventDate >= :minDate" //
+ " AND log.eventId IN (:evCreated, :evModified, :evRemoved)" //
+ " ORDER BY log.eventDate";
params.put("minDate", new Date(minDate));
params.put("evCreated", DocumentEventTypes.DOCUMENT_CREATED);
params.put("evModified", DocumentEventTypes.DOCUMENT_UPDATED);
params.put("evRemoved", DocumentEventTypes.DOCUMENT_REMOVED);
List<?> entries = reader.nativeQuery(query, params, 1, max + 1);
ObjectListImpl ol = new ObjectListImpl();
boolean hasMoreItems = entries.size() > max;
ol.setHasMoreItems(Boolean.valueOf(hasMoreItems));
if (hasMoreItems) {
entries = entries.subList(0, max);
}
List<ObjectData> ods = new ArrayList<ObjectData>(entries.size());
Date date = null;
for (Object entry : entries) {
LogEntry logEntry = (LogEntry) entry;
ObjectDataImpl od = new ObjectDataImpl();
ChangeEventInfoDataImpl cei = new ChangeEventInfoDataImpl();
// change type
String eventId = logEntry.getEventId();
ChangeType changeType;
if (DocumentEventTypes.DOCUMENT_CREATED.equals(eventId)) {
changeType = ChangeType.CREATED;
} else if (DocumentEventTypes.DOCUMENT_UPDATED.equals(eventId)) {
changeType = ChangeType.UPDATED;
} else if (DocumentEventTypes.DOCUMENT_REMOVED.equals(eventId)) {
changeType = ChangeType.DELETED;
} else {
continue;
}
cei.setChangeType(changeType);
// change time
GregorianCalendar changeTime = (GregorianCalendar) Calendar.getInstance();
date = logEntry.getEventDate();
changeTime.setTime(date);
cei.setChangeTime(changeTime);
od.setChangeEventInfo(cei);
// properties: id, doc type
PropertiesImpl properties = new PropertiesImpl();
properties.addProperty(new PropertyIdImpl(
PropertyIds.OBJECT_ID, logEntry.getDocUUID()));
properties.addProperty(new PropertyIdImpl(
PropertyIds.OBJECT_TYPE_ID, logEntry.getDocType()));
od.setProperties(properties);
ods.add(od);
}
ol.setObjects(ods);
ol.setNumItems(BigInteger.valueOf(-1));
String latestChangeLogToken = date == null ? null
: String.valueOf(date.getTime());
changeLogTokenHolder.setValue(latestChangeLogToken);
return ol;
} catch (Exception e) {
throw new CmisRuntimeException(e.toString(), e);
}
}
protected String getLatestChangeLogToken(String repositoryId) {
try {
AuditReader reader = Framework.getService(AuditReader.class);
if (reader == null) {
log.warn("Audit Service not found. latest change log token will be '0'");
return "0";
//throw new CmisRuntimeException("Cannot find audit service");
}
// TODO XXX repositoryId as well
Map<String, Object> params = new HashMap<String, Object>();
String query = "FROM LogEntry log" //
+ " WHERE log.eventId IN (:evCreated, :evModified, :evRemoved)" //
+ " ORDER BY log.eventDate DESC";
params.put("evCreated", DocumentEventTypes.DOCUMENT_CREATED);
params.put("evModified", DocumentEventTypes.DOCUMENT_UPDATED);
params.put("evRemoved", DocumentEventTypes.DOCUMENT_REMOVED);
List<?> entries = reader.nativeQuery(query, params, 1, 1);
if (entries.size() == 0) {
return "0";
}
LogEntry logEntry = (LogEntry) entries.get(0);
return String.valueOf(logEntry.getEventDate().getTime());
} catch (Exception e) {
throw new CmisRuntimeException(e.toString(), e);
}
}
@Override
public ObjectList query(String repositoryId, String statement,
Boolean searchAllVersions, Boolean includeAllowableActions,
IncludeRelationships includeRelationships, String renditionFilter,
BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) {
long skip = skipCount == null ? 0 : skipCount.longValue();
if (skip < 0) {
skip = 0;
}
long max = maxItems == null ? -1 : maxItems.longValue();
if (max <= 0) {
max = DEFAULT_QUERY_SIZE;
}
long numItems;
List<ObjectData> list;
IterableQueryResult res = null;
try {
Map<String, PropertyDefinition<?>> typeInfo = new HashMap<String, PropertyDefinition<?>>();
res = coreSession.queryAndFetch(statement, CMISQLQueryMaker.TYPE,
this, typeInfo);
// convert from Nuxeo to CMIS format
list = new ArrayList<ObjectData>();
if (skip > 0) {
res.skipTo(skip);
}
for (Map<String, Serializable> map : res) {
ObjectDataImpl od = new ObjectDataImpl();
// properties (kept in list form)
PropertiesImpl properties = new PropertiesImpl();
for (Entry<String, Serializable> en : map.entrySet()) {
String queryName = en.getKey();
PropertyDefinition<?> pd = typeInfo.get(queryName);
if (pd == null) {
throw new NullPointerException("Cannot get "
+ queryName);
}
PropertyData<?> p = createPropertyData(pd, en.getValue(),
queryName);
properties.addProperty(p);
}
od.setProperties(properties);
// optional stuff
if (Boolean.TRUE.equals(includeAllowableActions)) {
// od.setAllowableActions(allowableActions);
}
if (includeRelationships != null
&& includeRelationships != IncludeRelationships.NONE) {
// od.setRelationships(relationships);
}
if (renditionFilter != null && renditionFilter.length() > 0) {
// od.setRenditions(renditions);
}
list.add(od);
if (list.size() >= max) {
break;
}
}
numItems = res.size();
} catch (ClientException e) {
throw new CmisRuntimeException(e.getMessage(), e);
} finally {
if (res != null) {
res.close();
}
}
ObjectListImpl objList = new ObjectListImpl();
objList.setObjects(list);
objList.setNumItems(BigInteger.valueOf(numItems));
objList.setHasMoreItems(Boolean.valueOf(numItems > skip + list.size()));
return objList;
}
// TODO extract and move to BindingsObjectFactoryImpl
@SuppressWarnings("unchecked")
protected <T> PropertyData<T> createPropertyData(PropertyDefinition<T> pd,
Serializable value, String queryName) {
AbstractPropertyData<T> p;
String id = pd.getId();
if (pd instanceof PropertyIdDefinition) {
if (pd.getCardinality() == Cardinality.SINGLE) {
p = (AbstractPropertyData<T>) objectFactory.createPropertyIdData(
id, (String) value);
} else {
p = (AbstractPropertyData<T>) objectFactory.createPropertyIdData(
id, (List<String>) value);
}
} else if (pd instanceof PropertyStringDefinition) {
if (pd.getCardinality() == Cardinality.SINGLE) {
p = (AbstractPropertyData<T>) objectFactory.createPropertyStringData(
id, (String) value);
} else {
p = (AbstractPropertyData<T>) objectFactory.createPropertyStringData(
id, (List<String>) value);
}
} else if (pd instanceof PropertyDateTimeDefinition) {
if (pd.getCardinality() == Cardinality.SINGLE) {
p = (AbstractPropertyData<T>) objectFactory.createPropertyDateTimeData(
id, (GregorianCalendar) value);
} else {
p = (AbstractPropertyData<T>) objectFactory.createPropertyDateTimeData(
id, (List<GregorianCalendar>) value);
}
} else if (pd instanceof PropertyBooleanDefinition) {
if (pd.getCardinality() == Cardinality.SINGLE) {
p = (AbstractPropertyData<T>) objectFactory.createPropertyBooleanData(
id, (Boolean) value);
} else {
p = (AbstractPropertyData<T>) objectFactory.createPropertyBooleanData(
id, (List<Boolean>) value);
}
} else if (pd instanceof PropertyIntegerDefinition) {
if (pd.getCardinality() == Cardinality.SINGLE) {
p = (AbstractPropertyData<T>) objectFactory.createPropertyIntegerData(
id, (BigInteger) value);
} else {
p = (AbstractPropertyData<T>) objectFactory.createPropertyIntegerData(
id, (List<BigInteger>) value);
}
} else if (pd instanceof PropertyDecimalDefinition) {
if (pd.getCardinality() == Cardinality.SINGLE) {
p = (AbstractPropertyData<T>) objectFactory.createPropertyDecimalData(
id, (BigDecimal) value);
} else {
p = (AbstractPropertyData<T>) objectFactory.createPropertyDecimalData(
id, (List<BigDecimal>) value);
}
} else if (pd instanceof PropertyHtmlDefinition) {
if (pd.getCardinality() == Cardinality.SINGLE) {
p = (AbstractPropertyData<T>) objectFactory.createPropertyHtmlData(
id, (String) value);
} else {
p = (AbstractPropertyData<T>) objectFactory.createPropertyHtmlData(
id, (List<String>) value);
}
} else if (pd instanceof PropertyUriDefinition) {
if (pd.getCardinality() == Cardinality.SINGLE) {
p = (AbstractPropertyData<T>) objectFactory.createPropertyUriData(
id, (String) value);
} else {
p = (AbstractPropertyData<T>) objectFactory.createPropertyUriData(
id, (List<String>) value);
}
} else {
throw new CmisRuntimeException("Unknown property definition: " + pd);
}
p.setLocalName(pd.getLocalName());
p.setDisplayName(pd.getDisplayName());
p.setQueryName(queryName);
return p;
}
@Override
public void addObjectToFolder(String repositoryId, String objectId,
String folderId, Boolean allVersions, ExtensionsData extension) {
throw new CmisNotSupportedException();
}
@Override
public void removeObjectFromFolder(String repositoryId, String objectId,
String folderId, ExtensionsData extension) {
if (folderId != null) {
// check it's the actual parent
try {
DocumentModel folder = getDocumentModel(folderId);
DocumentModel parent = coreSession.getParentDocument(new IdRef(
objectId));
if (!parent.getId().equals(folder.getId())) {
throw new CmisInvalidArgumentException("Object " + objectId
+ " is not filed in " + folderId);
}
} catch (ClientException e) {
throw new CmisRuntimeException(e.toString(), e);
}
}
deleteObject(repositoryId, objectId, Boolean.FALSE, extension);
}
@Override
public ObjectInFolderList getChildren(String repositoryId, String folderId,
String filter, String orderBy, Boolean includeAllowableActions,
IncludeRelationships includeRelationships, String renditionFilter,
Boolean includePathSegment, BigInteger maxItems,
BigInteger skipCount, ExtensionsData extension) {
if (folderId == null) {
throw new CmisInvalidArgumentException("Null folderId");
}
return getChildrenInternal(repositoryId, folderId, filter, orderBy,
includeAllowableActions, includeRelationships, renditionFilter,
includePathSegment, maxItems, skipCount, false);
}
protected ObjectInFolderList getChildrenInternal(String repositoryId,
String folderId, String filter, String orderBy,
Boolean includeAllowableActions,
IncludeRelationships includeRelationships, String renditionFilter,
Boolean includePathSegment, BigInteger maxItems,
BigInteger skipCount, boolean folderOnly) {
ObjectInFolderListImpl result = new ObjectInFolderListImpl();
List<ObjectInFolderData> list = new ArrayList<ObjectInFolderData>();
DocumentModel folder = getDocumentModel(folderId);
if (!folder.isFolder()) {
return null;
}
DocumentModelList children;
try {
children = coreSession.getChildren(folder.getRef());
} catch (ClientException e) {
throw new CmisRuntimeException(e.toString(), e);
}
for (DocumentModel child : children) {
if (isFilteredOut(child)) {
continue;
}
if (folderOnly && !child.isFolder()) {
continue;
}
NuxeoObjectData data = new NuxeoObjectData(this, child, filter,
includeAllowableActions, includeRelationships,
renditionFilter, Boolean.FALSE, Boolean.FALSE, null);
ObjectInFolderDataImpl oifd = new ObjectInFolderDataImpl();
oifd.setObject(data);
if (Boolean.TRUE.equals(includePathSegment)) {
oifd.setPathSegment(child.getName());
}
list.add(oifd);
collectObjectInfo(repositoryId, data);
}
// TODO orderBy
BatchedList<ObjectInFolderData> batch = ListUtils.getBatchedList(list,
maxItems, skipCount, DEFAULT_MAX_CHILDREN);
result.setObjects(batch.getList());
result.setHasMoreItems(batch.getHasMoreItems());
result.setNumItems(batch.getNumItems());
collectObjectInfo(repositoryId, folderId);
return result;
}
@Override
public List<ObjectInFolderContainer> getDescendants(String repositoryId,
String folderId, BigInteger depth, String filter,
Boolean includeAllowableActions,
IncludeRelationships includeRelationships, String renditionFilter,
Boolean includePathSegment, ExtensionsData extension) {
if (folderId == null) {
throw new CmisInvalidArgumentException("Null folderId");
}
int levels = depth == null ? DEFAULT_FOLDER_LEVELS : depth.intValue();
if (levels == 0) {
throw new CmisInvalidArgumentException("Invalid depth: 0");
}
return getDescendantsInternal(repositoryId, folderId, filter,
includeAllowableActions, includeRelationships, renditionFilter,
includePathSegment, 0, levels, false);
}
@Override
public List<ObjectInFolderContainer> getFolderTree(String repositoryId,
String folderId, BigInteger depth, String filter,
Boolean includeAllowableActions,
IncludeRelationships includeRelationships, String renditionFilter,
Boolean includePathSegment, ExtensionsData extension) {
if (folderId == null) {
throw new CmisInvalidArgumentException("Null folderId");
}
int levels = depth == null ? DEFAULT_FOLDER_LEVELS : depth.intValue();
if (levels == 0) {
throw new CmisInvalidArgumentException("Invalid depth: 0");
}
return getDescendantsInternal(repositoryId, folderId, filter,
includeAllowableActions, includeRelationships, renditionFilter,
includePathSegment, 0, levels, true);
}
protected List<ObjectInFolderContainer> getDescendantsInternal(
String repositoryId, String folderId, String filter,
Boolean includeAllowableActions,
IncludeRelationships includeRelationships, String renditionFilter,
Boolean includePathSegments, int level, int maxLevels,
boolean folderOnly) {
if (maxLevels != -1 && level >= maxLevels) {
return null;
}
ObjectInFolderList children = getChildrenInternal(repositoryId,
folderId, filter, null, includeAllowableActions,
includeRelationships, renditionFilter, includePathSegments,
null, null, folderOnly);
if (children == null) {
return Collections.emptyList();
}
List<ObjectInFolderContainer> res = new ArrayList<ObjectInFolderContainer>(
children.getObjects().size());
for (ObjectInFolderData child : children.getObjects()) {
ObjectInFolderContainerImpl oifc = new ObjectInFolderContainerImpl();
oifc.setObject(child);
// recurse
List<ObjectInFolderContainer> subChildren = getDescendantsInternal(
repositoryId, child.getObject().getId(), filter,
includeAllowableActions, includeRelationships,
renditionFilter, includePathSegments, level + 1, maxLevels,
folderOnly);
if (subChildren != null) {
oifc.setChildren(subChildren);
}
res.add(oifc);
}
return res;
}
@Override
public ObjectData getFolderParent(String repositoryId, String folderId,
String filter, ExtensionsData extension) {
List<ObjectParentData> parents = getObjectParentsInternal(repositoryId,
folderId, filter, null, null, null, Boolean.TRUE, true);
return parents.isEmpty() ? null : parents.get(0).getObject();
}
@Override
public List<ObjectParentData> getObjectParents(String repositoryId,
String objectId, String filter, Boolean includeAllowableActions,
IncludeRelationships includeRelationships, String renditionFilter,
Boolean includeRelativePathSegment, ExtensionsData extension) {
return getObjectParentsInternal(repositoryId, objectId, filter,
includeAllowableActions, includeRelationships, renditionFilter,
includeRelativePathSegment, false);
}
protected List<ObjectParentData> getObjectParentsInternal(
String repositoryId, String objectId, String filter,
Boolean includeAllowableActions,
IncludeRelationships includeRelationships, String renditionFilter,
Boolean includeRelativePathSegment, boolean folderOnly) {
String pathSegment;
String parentId;
try {
DocumentRef docRef = new IdRef(objectId);
if (!coreSession.exists(docRef)) {
throw new CmisObjectNotFoundException(objectId);
}
DocumentModel doc = coreSession.getDocument(docRef);
if (isFilteredOut(doc)) {
throw new CmisObjectNotFoundException(objectId);
}
if (folderOnly && !doc.isFolder()) {
throw new CmisInvalidArgumentException("Not a folder: "
+ objectId);
}
pathSegment = doc.getName();
if (pathSegment == null) { // root
return Collections.emptyList();
}
DocumentRef parentRef = doc.getParentRef();
if (parentRef == null) { // placeless
return Collections.emptyList();
}
if (!coreSession.exists(parentRef)) { // non-accessible
return Collections.emptyList();
}
DocumentModel parent = coreSession.getDocument(parentRef);
if (isFilteredOut(parent)) { // filtered out
return Collections.emptyList();
}
parentId = parent.getId();
} catch (ClientException e) {
throw new CmisRuntimeException(e.toString(), e);
}
ObjectData od = getObject(repositoryId, parentId, filter,
includeAllowableActions, includeRelationships, renditionFilter,
Boolean.FALSE, Boolean.FALSE, null);
ObjectParentDataImpl opd = new ObjectParentDataImpl(od);
if (!Boolean.FALSE.equals(includeRelativePathSegment)) {
opd.setRelativePathSegment(pathSegment);
}
return Collections.<ObjectParentData> singletonList(opd);
}
@Override
public void applyPolicy(String repositoryId, String policyId,
String objectId, ExtensionsData extension) {
throw new CmisNotSupportedException();
}
@Override
public List<ObjectData> getAppliedPolicies(String repositoryId,
String objectId, String filter, ExtensionsData extension) {
return Collections.emptyList();
}
@Override
public void removePolicy(String repositoryId, String policyId,
String objectId, ExtensionsData extension) {
throw new CmisNotSupportedException();
}
@Override
public ObjectList getObjectRelationships(String repositoryId,
String objectId, Boolean includeSubRelationshipTypes,
RelationshipDirection relationshipDirection, String typeId,
String filter, Boolean includeAllowableActions,
BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) {
ObjectListImpl res = new ObjectListImpl();
res.setNumItems(BigInteger.valueOf(0));
res.setHasMoreItems(Boolean.FALSE);
res.setObjects(Collections.<ObjectData> emptyList());
return res;
}
@Override
public void checkIn(String repositoryId, Holder<String> objectIdHolder,
Boolean major, Properties properties, ContentStream contentStream,
String checkinComment, List<String> policies, Acl addAces,
Acl removeAces, ExtensionsData extension) {
String objectId;
if (objectIdHolder == null
|| (objectId = objectIdHolder.getValue()) == null) {
throw new CmisInvalidArgumentException("Missing object ID");
}
VersioningOption option = Boolean.TRUE.equals(major) ? VersioningOption.MAJOR
: VersioningOption.MINOR;
DocumentModel doc = getDocumentModel(objectId);
if (doc.isVersion() || doc.isProxy()) {
throw new CmisInvalidArgumentException("Cannot check in non-PWC: "
+ doc);
}
NuxeoObjectData object = new NuxeoObjectData(this, doc);
updateProperties(object, null, properties, false);
try {
coreSession.saveDocument(doc);
DocumentRef ver = doc.checkIn(option, checkinComment);
coreSession.save();
objectIdHolder.setValue(getIdFromDocumentRef(ver));
} catch (ClientException e) {
throw new CmisRuntimeException(e.toString(), e);
}
}
public String checkIn(String objectId, boolean major,
Map<String, ?> properties, ObjectType type,
ContentStream contentStream, String checkinComment) {
VersioningOption option = major ? VersioningOption.MAJOR
: VersioningOption.MINOR;
DocumentModel doc = getDocumentModel(objectId);
if (doc.isVersion() || doc.isProxy()) {
throw new CmisInvalidArgumentException("Cannot check in non-PWC: "
+ doc);
}
NuxeoObjectData object = new NuxeoObjectData(this, doc);
updateProperties(object, null, properties, type, false);
try {
coreSession.saveDocument(doc);
DocumentRef ver = doc.checkIn(option, checkinComment);
coreSession.save();
return getIdFromDocumentRef(ver);
} catch (ClientException e) {
throw new CmisRuntimeException(e.toString(), e);
}
}
@Override
public void checkOut(String repositoryId, Holder<String> objectIdHolder,
ExtensionsData extension, Holder<Boolean> contentCopiedHolder) {
String objectId;
if (objectIdHolder == null
|| (objectId = objectIdHolder.getValue()) == null) {
throw new CmisInvalidArgumentException("Missing object ID");
}
String pwcId = checkOut(objectId);
objectIdHolder.setValue(pwcId);
if (contentCopiedHolder != null) {
contentCopiedHolder.setValue(Boolean.TRUE);
}
}
public String checkOut(String objectId) {
DocumentModel doc = getDocumentModel(objectId);
try {
if (doc.isProxy()) {
throw new CmisInvalidArgumentException(
"Cannot check out non-version: " + objectId);
}
// find pwc
DocumentModel pwc;
if (doc.isVersion()) {
pwc = coreSession.getWorkingCopy(doc.getRef());
if (pwc == null) {
// no live document available
// TODO do a restore somewhere
throw new CmisObjectNotFoundException(objectId);
}
} else {
pwc = doc;
}
if (pwc.isCheckedOut()) {
throw new CmisConstraintException("Already checked out: "
+ objectId);
}
pwc.checkOut();
coreSession.save();
return pwc.getId();
} catch (ClientException e) {
throw new CmisRuntimeException(e.toString(), e);
}
}
@Override
public void cancelCheckOut(String repositoryId, String objectId,
ExtensionsData extension) {
cancelCheckOut(objectId);
}
public void cancelCheckOut(String objectId) {
DocumentModel doc = getDocumentModel(objectId);
try {
if (doc.isVersion() || doc.isProxy() || !doc.isCheckedOut()) {
throw new CmisInvalidArgumentException(
"Cannot cancel check out of non-PWC: " + doc);
}
DocumentRef docRef = doc.getRef();
// find last version
DocumentRef verRef = coreSession.getLastDocumentVersionRef(docRef);
// restore and keep checked in
coreSession.restoreToVersion(docRef, verRef, true, true);
coreSession.save();
} catch (ClientException e) {
throw new CmisRuntimeException(e.toString(), e);
}
}
@Override
public ObjectList getCheckedOutDocs(String repositoryId, String folderId,
String filter, String orderBy, Boolean includeAllowableActions,
IncludeRelationships includeRelationships, String renditionFilter,
BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) {
// TODO implement using query on non-proxy non-version non-checkedin
throw new CmisNotSupportedException();
}
@Override
public List<ObjectData> getAllVersions(String repositoryId,
String objectId, String versionSeriesId, String filter,
Boolean includeAllowableActions, ExtensionsData extension) {
DocumentModel doc;
if (objectId != null) {
// atompub passes object id
doc = getDocumentModel(objectId);
} else if (versionSeriesId != null) {
// soap passes version series id
// version series id is (for now) id of live document
// TODO deal with removal of live doc
doc = getDocumentModel(versionSeriesId);
} else {
throw new CmisInvalidArgumentException(
"Missing object ID or version series ID");
}
try {
List<DocumentRef> versions = coreSession.getVersionsRefs(doc.getRef());
List<ObjectData> list = new ArrayList<ObjectData>(versions.size());
for (DocumentRef verRef : versions) {
String verId = getIdFromDocumentRef(verRef);
ObjectData od = getObject(repositoryId, verId, filter,
includeAllowableActions, IncludeRelationships.NONE,
null, Boolean.FALSE, Boolean.FALSE, null);
list.add(od);
}
// CoreSession returns them in creation order,
// CMIS wants them last first
Collections.reverse(list);
return list;
} catch (ClientException e) {
throw new CmisRuntimeException(e.toString(), e);
}
}
@Override
public NuxeoObjectData getObjectOfLatestVersion(String repositoryId,
String objectId, String versionSeriesId, Boolean major,
String filter, Boolean includeAllowableActions,
IncludeRelationships includeRelationships, String renditionFilter,
Boolean includePolicyIds, Boolean includeAcl,
ExtensionsData extension) {
DocumentModel doc;
if (objectId != null) {
// atompub passes object id
doc = getDocumentModel(objectId);
} else if (versionSeriesId != null) {
// soap passes version series id
// version series id is (for now) id of live document
// TODO deal with removal of live doc
doc = getDocumentModel(versionSeriesId);
} else {
throw new CmisInvalidArgumentException(
"Missing object ID or version series ID");
}
try {
if (Boolean.TRUE.equals(major)) {
// we must list all versions
List<DocumentModel> versions = coreSession.getVersions(doc.getRef());
Collections.reverse(versions);
for (DocumentModel ver : versions) {
if (ver.isMajorVersion()) {
return getObject(repositoryId, ver.getId(), filter,
includeAllowableActions, includeRelationships,
renditionFilter, includePolicyIds, includeAcl,
null);
}
}
return null;
} else {
DocumentRef verRef = coreSession.getLastDocumentVersionRef(doc.getRef());
String verId = getIdFromDocumentRef(verRef);
return getObject(repositoryId, verId, filter,
includeAllowableActions, includeRelationships,
renditionFilter, includePolicyIds, includeAcl, null);
}
} catch (ClientException e) {
throw new CmisRuntimeException(e.toString(), e);
}
}
@Override
public Properties getPropertiesOfLatestVersion(String repositoryId,
String objectId, String versionSeriesId, Boolean major,
String filter, ExtensionsData extension) {
NuxeoObjectData od = getObjectOfLatestVersion(repositoryId, objectId,
versionSeriesId, major, filter, Boolean.FALSE,
IncludeRelationships.NONE, null, Boolean.FALSE, Boolean.FALSE,
null);
return od == null ? null : od.getProperties();
}
@Override
public void deleteObject(String repositoryId, String objectId,
Boolean allVersions, ExtensionsData extension) {
try {
DocumentModel doc = getDocumentModel(objectId);
if (doc.isFolder()) {
// check that there are no children left
DocumentModelList docs = coreSession.getChildren(new IdRef(
objectId), null, documentFilter, null);
if (docs.size() > 0) {
throw new CmisConstraintException(
"Cannot delete non-empty folder: " + objectId);
}
}
coreSession.removeDocument(doc.getRef());
coreSession.save();
} catch (ClientException e) {
throw new CmisRuntimeException(e.toString(), e);
}
}
@Override
public void deleteObjectOrCancelCheckOut(String repositoryId,
String objectId, Boolean allVersions, ExtensionsData extension) {
deleteObject(repositoryId, objectId, allVersions, extension);
}
}