org.opencms.cmis.CmsCmisRelationHelper Maven / Gradle / Ivy
Show all versions of opencms-core Show documentation
/*
* This library is part of OpenCms -
* the Open Source Content Management System
*
* Copyright (C) Alkacon Software (http://www.alkacon.com)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* For further information about Alkacon Software, please see the
* company website: http://www.alkacon.com
*
* For further information about OpenCms, please see the
* project website: http://www.opencms.org
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.opencms.cmis;
import static org.opencms.cmis.CmsCmisUtil.addAction;
import static org.opencms.cmis.CmsCmisUtil.addPropertyDateTime;
import static org.opencms.cmis.CmsCmisUtil.addPropertyId;
import static org.opencms.cmis.CmsCmisUtil.addPropertyString;
import static org.opencms.cmis.CmsCmisUtil.handleCmsException;
import static org.opencms.cmis.CmsCmisUtil.millisToCalendar;
import org.opencms.file.CmsObject;
import org.opencms.file.CmsResource;
import org.opencms.file.CmsResourceFilter;
import org.opencms.file.CmsUser;
import org.opencms.lock.CmsLock;
import org.opencms.main.CmsException;
import org.opencms.relations.CmsRelation;
import org.opencms.relations.CmsRelationFilter;
import org.opencms.relations.CmsRelationType;
import org.opencms.security.CmsPermissionSet;
import org.opencms.util.CmsUUID;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
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.ObjectData;
import org.apache.chemistry.opencmis.commons.data.Properties;
import org.apache.chemistry.opencmis.commons.enums.Action;
import org.apache.chemistry.opencmis.commons.enums.BaseTypeId;
import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships;
import org.apache.chemistry.opencmis.commons.exceptions.CmisBaseException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlListImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.AllowableActionsImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectDataImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertiesImpl;
import org.apache.chemistry.opencmis.commons.impl.server.ObjectInfoImpl;
/**
* Helper class for CMIS CRUD operations on relation objects.
*
* Since CMIS requires any object to have an ID by which it is accessed, but OpenCms relations
* are not addressable by ids, we invent an artificial relation id string of the form
* REL_(SOURCE_ID)_(TARGET_ID)_(TYPE).
*
*/
public class CmsCmisRelationHelper implements I_CmsCmisObjectHelper {
/**
* A class which contains the necessary information to identify a relation object.
*/
public static class RelationKey {
/** The internal OpenCms relation object (optional). */
private CmsRelation m_relation;
/** The relation type string. */
private String m_relType;
/** The internal OpenCms resource which is the relation source (optional). */
private CmsResource m_source;
/** The source id of the relation. */
private CmsUUID m_sourceId;
/** The target id of the relation. */
private CmsUUID m_targetId;
/**
* Creates a new relation key.
*
* @param sourceId the source id
* @param targetId the target id
* @param relType the relation type
*/
public RelationKey(CmsUUID sourceId, CmsUUID targetId, String relType) {
m_sourceId = sourceId;
m_targetId = targetId;
m_relType = relType;
}
/**
* Reads the actual resource and relation data from the OpenCms VFS.
*
* @param cms the CMS context to use for reading the data
*/
public void fillRelation(CmsObject cms) {
try {
m_source = cms.readResource(m_sourceId);
List relations = cms.getRelationsForResource(
m_source,
CmsRelationFilter.TARGETS.filterStructureId(m_targetId).filterType(getRelationType(m_relType)));
if (relations.isEmpty()) {
throw new CmisObjectNotFoundException(toString());
}
m_relation = relations.get(0);
} catch (CmsException e) {
CmsCmisUtil.handleCmsException(e);
}
}
/**
* Gets the relation object.
*
* @return the relation object
*/
public CmsRelation getRelation() {
return m_relation;
}
/**
* Gets the relation type.
*
* @return the relation type
*/
public String getRelType() {
return m_relType;
}
/**
* Gets the source resource of the relation.
*
* @return the source of the relation
*/
public CmsResource getSource() {
return m_source;
}
/**
* Gets the source id.
*
* @return the source id
*/
public CmsUUID getSourceId() {
return m_sourceId;
}
/**
* Gets the target id of the relation.
*
* @return the target id
*/
public CmsUUID getTargetId() {
return m_targetId;
}
/**
* Sets the relation type.
*
* @param relType the relation type
*/
public void setRelType(String relType) {
m_relType = relType;
}
/**
* Sets the source id.
*
* @param sourceId the source id
*/
public void setSourceId(CmsUUID sourceId) {
m_sourceId = sourceId;
}
/**
* Sets the target id.
*
* @param targetId the target id
*/
public void setTargetId(CmsUUID targetId) {
m_targetId = targetId;
}
/**
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return createKey(m_sourceId, m_targetId, m_relType);
}
}
/** The prefix used to identify relation ids. */
public static final String RELATION_ID_PREFIX = "REL_";
/** The pattern which relation ids should match. */
public static final Pattern RELATION_PATTERN = Pattern.compile(
"^REL_(" + CmsUUID.UUID_REGEX + ")_(" + CmsUUID.UUID_REGEX + ")_(.*)$");
/** The underlying CMIS repository. */
private CmsCmisRepository m_repository;
/**
* Creates a new relation helper for the given repository.
*
* @param repository the repository
*/
public CmsCmisRelationHelper(CmsCmisRepository repository) {
m_repository = repository;
}
/**
* Creates a relation id string from the source and target ids and a relation type.
*
* @param source the source id
* @param target the target id
* @param relType the relation type
*
* @return the relation id
*/
protected static String createKey(CmsUUID source, CmsUUID target, String relType) {
return RELATION_ID_PREFIX + source + "_" + target + "_" + relType;
}
/**
* Gets a relation type by name.
*
* @param typeName the relation type name
*
* @return the relation type with the matching name
*/
protected static CmsRelationType getRelationType(String typeName) {
for (CmsRelationType relType : CmsRelationType.getAll()) {
if (relType.getName().equalsIgnoreCase(typeName)) {
return relType;
}
}
return null;
}
/**
* @see org.opencms.cmis.I_CmsCmisObjectHelper#deleteObject(org.opencms.cmis.CmsCmisCallContext, java.lang.String, boolean)
*/
public void deleteObject(CmsCmisCallContext context, String objectId, boolean allVersions) {
try {
RelationKey rk = parseRelationKey(objectId);
CmsUUID sourceId = rk.getSourceId();
CmsObject cms = m_repository.getCmsObject(context);
CmsResource sourceResource = cms.readResource(sourceId);
boolean wasLocked = CmsCmisUtil.ensureLock(cms, sourceResource);
try {
CmsRelationFilter relFilter = CmsRelationFilter.ALL.filterType(
getRelationType(rk.getRelType())).filterStructureId(rk.getTargetId());
cms.deleteRelationsFromResource(sourceResource.getRootPath(), relFilter);
} finally {
if (wasLocked) {
cms.unlockResource(sourceResource);
}
}
} catch (CmsException e) {
CmsCmisUtil.handleCmsException(e);
}
}
/**
* @see org.opencms.cmis.I_CmsCmisObjectHelper#getAcl(org.opencms.cmis.CmsCmisCallContext, java.lang.String, boolean)
*/
public Acl getAcl(CmsCmisCallContext context, String objectId, boolean onlyBasicPermissions) {
CmsObject cms = m_repository.getCmsObject(context);
RelationKey rk = parseRelationKey(objectId);
rk.fillRelation(cms);
return collectAcl(cms, rk.getSource(), onlyBasicPermissions);
}
/**
* @see org.opencms.cmis.I_CmsCmisObjectHelper#getAllowableActions(org.opencms.cmis.CmsCmisCallContext, java.lang.String)
*/
public AllowableActions getAllowableActions(CmsCmisCallContext context, String objectId) {
CmsObject cms = m_repository.getCmsObject(context);
RelationKey rk = parseRelationKey(objectId);
rk.fillRelation(cms);
return collectAllowableActions(cms, rk.getSource(), rk.getRelation());
}
/**
* @see org.opencms.cmis.I_CmsCmisObjectHelper#getObject(org.opencms.cmis.CmsCmisCallContext, java.lang.String, java.lang.String, boolean, org.apache.chemistry.opencmis.commons.enums.IncludeRelationships, java.lang.String, boolean, boolean)
*/
public ObjectData getObject(
CmsCmisCallContext context,
String objectId,
String filter,
boolean includeAllowableActions,
IncludeRelationships includeRelationships,
String renditionFilter,
boolean includePolicyIds,
boolean includeAcl) {
CmsObject cms = m_repository.getCmsObject(context);
RelationKey rk = parseRelationKey(objectId);
rk.fillRelation(cms);
Set filterSet = CmsCmisUtil.splitFilter(filter);
ObjectData result = collectObjectData(
context,
cms,
rk.getSource(),
rk.getRelation(),
filterSet,
includeAllowableActions,
includeAcl);
return result;
}
/**
* Compiles the ACL for a relation.
*
* @param cms the CMS context
* @param resource the resource for which to collect the ACLs
* @param onlyBasic flag to only include basic ACEs
*
* @return the ACL for the resource
*/
protected Acl collectAcl(CmsObject cms, CmsResource resource, boolean onlyBasic) {
AccessControlListImpl cmisAcl = new AccessControlListImpl();
List cmisAces = new ArrayList();
cmisAcl.setAces(cmisAces);
cmisAcl.setExact(Boolean.FALSE);
return cmisAcl;
}
/**
* Collects the allowable actions for a relation.
*
* @param cms the current CMS context
* @param file the source of the relation
* @param relation the relation object
*
* @return the allowable actions for the given resource
*/
protected AllowableActions collectAllowableActions(CmsObject cms, CmsResource file, CmsRelation relation) {
try {
Set aas = new LinkedHashSet();
AllowableActionsImpl result = new AllowableActionsImpl();
CmsLock lock = cms.getLock(file);
CmsUser user = cms.getRequestContext().getCurrentUser();
boolean canWrite = !cms.getRequestContext().getCurrentProject().isOnlineProject()
&& (lock.isOwnedBy(user) || lock.isLockableBy(user))
&& cms.hasPermissions(file, CmsPermissionSet.ACCESS_WRITE, false, CmsResourceFilter.DEFAULT);
addAction(aas, Action.CAN_GET_PROPERTIES, true);
addAction(aas, Action.CAN_DELETE_OBJECT, canWrite && !relation.getType().isDefinedInContent());
result.setAllowableActions(aas);
return result;
} catch (CmsException e) {
handleCmsException(e);
return null;
}
}
/**
* Fills in an ObjectData record.
*
* @param context the call context
* @param cms the CMS context
* @param resource the resource for which we want the ObjectData
* @param relation the relation object
* @param filter the property filter string
* @param includeAllowableActions true if the allowable actions should be included
* @param includeAcl true if the ACL entries should be included
*
* @return the object data
*/
protected ObjectData collectObjectData(
CmsCmisCallContext context,
CmsObject cms,
CmsResource resource,
CmsRelation relation,
Set filter,
boolean includeAllowableActions,
boolean includeAcl) {
ObjectDataImpl result = new ObjectDataImpl();
ObjectInfoImpl objectInfo = new ObjectInfoImpl();
result.setProperties(collectProperties(cms, resource, relation, filter, objectInfo));
if (includeAllowableActions) {
result.setAllowableActions(collectAllowableActions(cms, resource, relation));
}
if (includeAcl) {
result.setAcl(collectAcl(cms, resource, true));
result.setIsExactAcl(Boolean.FALSE);
}
if (context.isObjectInfoRequired()) {
objectInfo.setObject(result);
context.getObjectInfoHandler().addObjectInfo(objectInfo);
}
return result;
}
/**
* Gathers all base properties of a file or folder.
*
* @param cms the current CMS context
* @param resource the file for which we want the properties
* @param relation the relation object
* @param orgfilter the property filter
* @param objectInfo the object info handler
*
* @return the properties for the given resource
*/
protected Properties collectProperties(
CmsObject cms,
CmsResource resource,
CmsRelation relation,
Set orgfilter,
ObjectInfoImpl objectInfo) {
CmsCmisTypeManager tm = m_repository.getTypeManager();
if (resource == null) {
throw new IllegalArgumentException("Resource may not be null.");
}
// copy filter
Set filter = (orgfilter == null ? null : new LinkedHashSet(orgfilter));
// find base type
String typeId = "opencms:" + relation.getType().getName();
objectInfo.setBaseType(BaseTypeId.CMIS_RELATIONSHIP);
objectInfo.setTypeId(typeId);
objectInfo.setContentType(null);
objectInfo.setFileName(null);
objectInfo.setHasAcl(false);
objectInfo.setHasContent(false);
objectInfo.setVersionSeriesId(null);
objectInfo.setIsCurrentVersion(true);
objectInfo.setRelationshipSourceIds(null);
objectInfo.setRelationshipTargetIds(null);
objectInfo.setRenditionInfos(null);
objectInfo.setSupportsDescendants(false);
objectInfo.setSupportsFolderTree(false);
objectInfo.setSupportsPolicies(false);
objectInfo.setSupportsRelationships(false);
objectInfo.setWorkingCopyId(null);
objectInfo.setWorkingCopyOriginalId(null);
// let's do it
try {
PropertiesImpl result = new PropertiesImpl();
// id
String id = createKey(relation);
addPropertyId(tm, result, typeId, filter, PropertyIds.OBJECT_ID, id);
objectInfo.setId(id);
// name
String name = createReadableName(relation);
addPropertyString(tm, result, typeId, filter, PropertyIds.NAME, name);
objectInfo.setName(name);
// created and modified by
CmsUUID creatorId = resource.getUserCreated();
CmsUUID modifierId = resource.getUserLastModified();
String creatorName = creatorId.toString();
String modifierName = modifierId.toString();
try {
CmsUser user = cms.readUser(creatorId);
creatorName = user.getName();
} catch (CmsException e) {
// ignore, use id as name
}
try {
CmsUser user = cms.readUser(modifierId);
modifierName = user.getName();
} catch (CmsException e) {
// ignore, use id as name
}
addPropertyString(tm, result, typeId, filter, PropertyIds.CREATED_BY, creatorName);
addPropertyString(tm, result, typeId, filter, PropertyIds.LAST_MODIFIED_BY, modifierName);
objectInfo.setCreatedBy(creatorName);
addPropertyId(tm, result, typeId, filter, PropertyIds.SOURCE_ID, relation.getSourceId().toString());
addPropertyId(tm, result, typeId, filter, PropertyIds.TARGET_ID, relation.getTargetId().toString());
// creation and modification date
GregorianCalendar lastModified = millisToCalendar(resource.getDateLastModified());
GregorianCalendar created = millisToCalendar(resource.getDateCreated());
addPropertyDateTime(tm, result, typeId, filter, PropertyIds.CREATION_DATE, created);
addPropertyDateTime(tm, result, typeId, filter, PropertyIds.LAST_MODIFICATION_DATE, lastModified);
objectInfo.setCreationDate(created);
objectInfo.setLastModificationDate(lastModified);
// change token - always null
addPropertyString(tm, result, typeId, filter, PropertyIds.CHANGE_TOKEN, null);
// base type and type name
addPropertyId(tm, result, typeId, filter, PropertyIds.BASE_TYPE_ID, BaseTypeId.CMIS_RELATIONSHIP.value());
addPropertyId(tm, result, typeId, filter, PropertyIds.OBJECT_TYPE_ID, typeId);
objectInfo.setHasParent(false);
return result;
} catch (Exception e) {
if (e instanceof CmisBaseException) {
throw (CmisBaseException)e;
}
throw new CmisRuntimeException(e.getMessage(), e);
}
}
/**
* Creates a user-readable name from the given relation object.
*
* @param relation the relation object
*
* @return the readable name
*/
protected String createReadableName(CmsRelation relation) {
return relation.getType().getName()
+ "[ "
+ relation.getSourcePath()
+ " -> "
+ relation.getTargetPath()
+ " ]";
}
/**
* Extracts the source/target ids and the type from a relation id.
*
* @param id the relation id
*
* @return the relation key object
*/
protected RelationKey parseRelationKey(String id) {
Matcher matcher = RELATION_PATTERN.matcher(id);
matcher.find();
CmsUUID src = new CmsUUID(matcher.group(1));
CmsUUID tgt = new CmsUUID(matcher.group(2));
String tp = matcher.group(3);
return new RelationKey(src, tgt, tp);
}
/**
* Creates a relation id from the given OpenCms relation object.
*
* @param relation the OpenCms relation object
*
* @return the relation id
*/
String createKey(CmsRelation relation) {
return createKey(relation.getSourceId(), relation.getTargetId(), relation.getType().getName());
}
}