Allow management of Alfresco Aspects through CMIS REST and SOAP APIs

- In CMIS methods that allow setting of node properties, the <cmis:properties> element may carry an <alf:setAspects> extension that lists
  - aspectsToRemove    
  - aspectsToAdd
  - properties (properties to set belonging to aspects rather than the node type)
- In CMIS methods that allow retrieval of node properties, the <cmis:properties> carries an <alf:getAspects> extension that lists
  - appliedAspects
  - properties (properties belonging to aspects rather than the node type)
- Added extension types to Alfresco-Core.xsd and referenced in extended WSDL
- Plumbed in to Web Service and REST APIs

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@19037 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Dave Ward
2010-03-03 18:03:58 +00:00
parent 5573c80e8d
commit 9cc0148e8c
16 changed files with 2798 additions and 2249 deletions

View File

@@ -0,0 +1,38 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco 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 3 of the License, or
* (at your option) any later version.
*
* Alfresco 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.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.cmis.rest;
import org.apache.chemistry.abdera.ext.CMISExtensionFactory;
import org.apache.chemistry.abdera.ext.CMISProperties;
/**
* A CMIS extension factory that is also aware of Alfresco extensions.
*
* @author dward
*/
public class AlfrescoCMISExtensionFactory extends CMISExtensionFactory
{
public AlfrescoCMISExtensionFactory()
{
super();
addImpl(SetAspectsExtension.QNAME, SetAspectsExtension.class);
addImpl(SetAspectsExtension.PROPERTIES, CMISProperties.class);
}
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco 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 3 of the License, or
* (at your option) any later version.
*
* Alfresco 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.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.cmis.rest;
import java.util.Collections;
import java.util.List;
import org.alfresco.cmis.CMISServices;
import org.alfresco.cmis.CMISTypeDefinition;
import org.alfresco.repo.template.TemplateNode;
import org.alfresco.service.cmr.repository.NodeRef;
import freemarker.ext.beans.BeanModel;
import freemarker.template.TemplateMethodModelEx;
import freemarker.template.TemplateModelException;
/**
* Custom FreeMarker Template language method.
* <p>
* Gets the type definitions of a TemplateNode's aspects
* <p>
* Usage: cmisaspects(TemplateNode node)
*
* @author dward
*/
public class CMISAspectsMethod implements TemplateMethodModelEx
{
private CMISServices cmisService;
/**
* Construct
*/
public CMISAspectsMethod(CMISServices cmisService)
{
this.cmisService = cmisService;
}
@SuppressWarnings("unchecked")
public Object exec(List args) throws TemplateModelException
{
NodeRef nodeRef = null;
try
{
int i = 0;
// extract node ref
Object arg = args.get(i++);
if (arg instanceof BeanModel)
{
Object wrapped = ((BeanModel) arg).getWrappedObject();
if (wrapped != null)
{
if (wrapped instanceof TemplateNode)
{
nodeRef = ((TemplateNode) wrapped).getNodeRef();
}
}
}
}
catch (IndexOutOfBoundsException e)
{
// Ignore optional arguments
}
// query aspects
if (nodeRef != null)
{
return cmisService.getAspects(nodeRef);
}
return Collections.<CMISTypeDefinition> emptySet();
}
}

View File

@@ -114,8 +114,8 @@ public class CMISPropertyValueMethod implements TemplateMethodModelEx
Object result = null;
if (wrapped != null && wrapped instanceof TemplateNode)
{
// retrieve property value from node
result = cmisService.getProperty(((TemplateNode) wrapped).getNodeRef(), propertyName);
// retrieve property value from node, allowing aspect properties
result = cmisService.getProperty(((TemplateNode) wrapped).getNodeRef(), null, propertyName);
}
else if (wrapped != null && wrapped instanceof TemplateAssociation)
{

View File

@@ -563,6 +563,30 @@ public class CMISScript extends BaseScopableProcessorExtension
return cmisDictionaryService.findProperty(propertyName, null);
}
/**
* Sets the aspects on a node (Alfresco extension).
*
* @param node
* the node
* @param aspectsToRemove
* the aspects to remove
* @param aspectsToAdd
* the aspects to add
* @throws WebScriptException
* if an argument is invalid
*/
public void setAspects(ScriptNode node, Iterable<String> aspectsToRemove, Iterable<String> aspectsToAdd)
{
try
{
cmisService.setAspects(node.getNodeRef(), aspectsToRemove, aspectsToAdd);
}
catch (CMISInvalidArgumentException e)
{
throw new WebScriptException(e.getStatusCode(), e.getMessage(), e);
}
}
//
// SQL Query
//

View File

@@ -0,0 +1,120 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco 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 3 of the License, or
* (at your option) any later version.
*
* Alfresco 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.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.cmis.rest;
import java.util.Set;
import java.util.TreeSet;
import javax.xml.namespace.QName;
import org.apache.abdera.factory.Factory;
import org.apache.abdera.model.Element;
import org.apache.abdera.model.ExtensibleElementWrapper;
import org.apache.chemistry.abdera.ext.CMISConstants;
import org.apache.chemistry.abdera.ext.CMISProperties;
/**
* Alfresco CMIS extension for controlling aspects and their properties.
*
* @author dward
*/
public class SetAspectsExtension extends ExtensibleElementWrapper
{
/** The Alfresco extension namespace. */
private static final String NAMESPACE = "http://www.alfresco.org";
/** The name of this element. */
public static final QName QNAME = new QName(NAMESPACE, "setAspects");
/** The name of the element containing the aspect properties. */
public static final QName PROPERTIES = new QName(NAMESPACE, "properties");
/** The name of the element containing the aspects to add. */
private static final QName ASPECTS_TO_ADD = new QName(NAMESPACE, "aspectsToAdd");
/** The name of the element containing the aspects to remove. */
private static final QName ASPECTS_TO_REMOVE = new QName(NAMESPACE, "aspectsToRemove");
/**
* The Constructor.
*
* @param internal
* the internal element
*/
public SetAspectsExtension(Element internal)
{
super(internal);
}
/**
* The Constructor.
*
* @param factory
* the factory
*/
public SetAspectsExtension(Factory factory)
{
super(factory, CMISConstants.OBJECT);
}
/**
* Gets the aspects to add.
*
* @return the aspects to add
*/
public Set<String> getAspectsToAdd()
{
Set<String> aspects = new TreeSet<String>();
for (Element aspect = getFirstChild(ASPECTS_TO_ADD); aspect != null; aspect = aspect
.getNextSibling(ASPECTS_TO_ADD))
{
aspects.add(aspect.getText());
}
return aspects;
}
/**
* Gets the aspects to remove.
*
* @return the aspects to remove
*/
public Set<String> getAspectsToRemove()
{
Set<String> aspects = new TreeSet<String>();
for (Element aspect = getFirstChild(ASPECTS_TO_REMOVE); aspect != null; aspect = aspect
.getNextSibling(ASPECTS_TO_REMOVE))
{
aspects.add(aspect.getText());
}
return aspects;
}
/**
* Gets all aspect properties
*
* @return aspect properties
*/
public CMISProperties getProperties()
{
return (CMISProperties) getFirstChild(PROPERTIES);
}
}

View File

@@ -45,7 +45,6 @@ import org.alfresco.repo.cmis.ws.utils.ExceptionUtil;
import org.alfresco.repo.web.util.paging.Cursor;
import org.alfresco.service.cmr.dictionary.AssociationDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.dictionary.TypeDefinition;
import org.alfresco.service.cmr.model.FileExistsException;
import org.alfresco.service.cmr.model.FileInfo;
import org.alfresco.service.cmr.model.FileNotFoundException;
@@ -213,14 +212,16 @@ public class DMObjectServicePort extends DMAbstractServicePort implements Object
Map<String, Object> propertiesMap = propertiesUtil.getPropertiesMap(properties);
String typeId = extractAndAssertTypeId(propertiesMap);
CMISTypeDefinition type = cmisService.getTypeDefinition(typeId);
TypeDefinition nativeType = dictionaryService.getType(nodeService.getType(folderNodeRef));
if (type == null || type.getTypeId() == null || type.getTypeId().getScope() != CMISScope.FOLDER)
{
throw ExceptionUtil.createCmisException("The typeID is not an Object-Type whose baseType is 'Folder': " + typeId, EnumServiceException.CONSTRAINT);
}
String name = propertiesUtil.getCmisPropertyValue(properties, CMISDictionaryModel.PROP_NAME, null);
propertiesUtil.checkProperty(nativeType, type, CMISDictionaryModel.PROP_NAME, name, false);
if (null == name)
{
throw ExceptionUtil.createCmisException("Name property not found", EnumServiceException.INVALID_ARGUMENT);
}
try
{
@@ -819,8 +820,6 @@ public class DMObjectServicePort extends DMAbstractServicePort implements Object
{
throw ExceptionUtil.createCmisException("Name property not found", EnumServiceException.INVALID_ARGUMENT);
}
propertiesUtil.checkProperty(null, typeDef, CMISDictionaryModel.PROP_NAME, result, (EnumVersioningState.CHECKEDOUT == versioningState));
return result;
}

View File

@@ -25,6 +25,7 @@ import java.util.Collection;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.datatype.DatatypeConfigurationException;
@@ -37,9 +38,9 @@ import org.alfresco.cmis.CMISDictionaryService;
import org.alfresco.cmis.CMISInvalidArgumentException;
import org.alfresco.cmis.CMISPropertyDefinition;
import org.alfresco.cmis.CMISScope;
import org.alfresco.cmis.CMISServiceException;
import org.alfresco.cmis.CMISServices;
import org.alfresco.cmis.CMISTypeDefinition;
import org.alfresco.cmis.CMISUpdatabilityEnum;
import org.alfresco.repo.cmis.PropertyFilter;
import org.alfresco.repo.cmis.ws.CmisException;
import org.alfresco.repo.cmis.ws.CmisPropertiesType;
@@ -53,13 +54,13 @@ import org.alfresco.repo.cmis.ws.CmisPropertyInteger;
import org.alfresco.repo.cmis.ws.CmisPropertyString;
import org.alfresco.repo.cmis.ws.CmisPropertyUri;
import org.alfresco.repo.cmis.ws.EnumServiceException;
import org.alfresco.repo.cmis.ws.GetAspects;
import org.alfresco.repo.cmis.ws.SetAspects;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
import org.alfresco.service.cmr.dictionary.TypeDefinition;
import org.alfresco.service.cmr.repository.AssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.version.Version;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
@@ -89,7 +90,6 @@ public class PropertyUtil
private static final String BASE_TYPE_PROPERTY_NAME = "BaseType";
private NodeService nodeService;
private DictionaryService dictionaryService;
private NamespaceService namespaceService;
private CMISServices cmisService;
@@ -99,11 +99,6 @@ public class PropertyUtil
{
}
public void setNodeService(NodeService nodeService)
{
this.nodeService = nodeService;
}
public void setDictionaryService(DictionaryService dictionaryService)
{
this.dictionaryService = dictionaryService;
@@ -348,59 +343,71 @@ public class PropertyUtil
/**
* Sets and checks all properties' fields for specified node
*
* @param nodeRef - <b>NodeRef</b> for node for those properties must be setted
* @param properties - <b>CmisPropertiesType</b> instance that contains all the necessary properties' fields
* @param ignoringPropertiesFilter - <b>PropertyFilter</b> instance. This filter determines which properties should be ignored and not setted without exception. If this
* parameter is <b>null</b> all properties will be processed in common flow
* @param nodeRef
* - <b>NodeRef</b> for node for those properties must be setted
* @param properties
* - <b>CmisPropertiesType</b> instance that contains all the necessary properties' fields
* @param ignoringPropertiesFilter
* - <b>PropertyFilter</b> instance. This filter determines which properties should be ignored and not
* setted without exception. If this parameter is <b>null</b> all properties will be processed in common
* flow
*/
public void setProperties(NodeRef nodeRef, CmisPropertiesType properties, PropertyFilter ignoringPropertiesFilter) throws CmisException
public void setProperties(NodeRef nodeRef, CmisPropertiesType properties, PropertyFilter ignoringPropertiesFilter)
throws CmisException
{
// TODO: WARINING!!! This is WRONG behavior!!! Each CMIS object type and each property MUST be described in appropriate to CMIS manner
if ((null == nodeRef) || (null == properties) || (null == properties.getProperty()))
if (nodeRef == null || properties == null)
{
return;
}
String typeId = getProperty(nodeRef, CMISDictionaryModel.PROP_OBJECT_TYPE_ID, null);
boolean checkedOut = getProperty(nodeRef, CMISDictionaryModel.PROP_IS_VERSION_SERIES_CHECKED_OUT, false);
CMISTypeDefinition cmisObjectType = cmisDictionaryService.findType(typeId);
TypeDefinition nativeObjectType = dictionaryService.getType(nodeService.getType(nodeRef));
if ((null == cmisObjectType) && (null == nativeObjectType))
try
{
throw ExceptionUtil.createCmisException(("Can't find type definition for current object with \"" + typeId + "\" type Id"), EnumServiceException.INVALID_ARGUMENT);
for (CmisProperty property : properties.getProperty())
{
String propertyName = getPropertyName(property);
if ((null == propertyName)
|| ((null != ignoringPropertiesFilter) && ignoringPropertiesFilter.allow(propertyName)))
{
continue;
}
Object value = getValue(property);
cmisService.setProperty(nodeRef, propertyName, (Serializable) value);
}
// Process Alfresco aspect-setting extension
for (Object extensionObj : properties.getAny())
{
if (!(extensionObj instanceof SetAspects))
{
continue;
}
SetAspects setAspects = (SetAspects) extensionObj;
cmisService.setAspects(nodeRef, setAspects.getAspectsToRemove(), setAspects.getAspectsToAdd());
CmisPropertiesType extensionProperties = setAspects.getProperties();
if (extensionProperties == null)
{
continue;
}
for (CmisProperty property : extensionProperties.getProperty())
{
String propertyName = getPropertyName(property);
if ((null == propertyName)
|| ((null != ignoringPropertiesFilter) && ignoringPropertiesFilter.allow(propertyName)))
{
continue;
}
Object value = getValue(property);
// This time, call setProperty without constraining the owning type
cmisService.setProperty(nodeRef, null, propertyName, (Serializable) value);
}
}
}
for (CmisProperty property : properties.getProperty())
catch (CMISServiceException e)
{
String propertyName = getPropertyName(property);
if ((null == propertyName) || ((null != ignoringPropertiesFilter) && ignoringPropertiesFilter.allow(propertyName)))
{
continue;
}
Object value = getValue(property);
QName alfrescoPropertyName = null;
switch (checkProperty(nativeObjectType, cmisObjectType, propertyName, value, checkedOut))
{
case PROPERTY_CHECKED:
{
alfrescoPropertyName = cmisDictionaryService.findProperty(propertyName, cmisObjectType).getPropertyAccessor().getMappedProperty();
break;
}
case PROPERTY_NATIVE:
{
alfrescoPropertyName = createQName(propertyName);
break;
}
case PROPERTY_NOT_UPDATABLE:
{
throw ExceptionUtil.createCmisException(("\"" + propertyName + "\" property is not updatable by repository for specified Object id"),
EnumServiceException.CONSTRAINT);
}
}
nodeService.setProperty(nodeRef, alfrescoPropertyName, (Serializable) value);
throw ExceptionUtil.createCmisException(e);
}
}
@@ -432,82 +439,6 @@ public class PropertyUtil
return qname;
}
/**
* Checks any CMIS property on constraints conforming
*
* @param type - <b>CMISTypeDefinition</b> instance. This value must not be <b>null</b>
* @param propertyName - <b>String</b> instance that represents name of the property
* @param value - instance of a <b>Serializable</b> object that represents value of the property
* @return <b>true</b> if property was checked and <b>false</b> if property can't be checked
* @throws <b>CmisException</b> if some constraint is not satisfied
*/
public PropertyCheckingStateEnum checkProperty(TypeDefinition nativeObjectType, CMISTypeDefinition cmisObjectType, String propertyName, Object value, boolean checkedOut)
throws CmisException
{
CMISPropertyDefinition propertyDefinition = cmisDictionaryService.findProperty(propertyName, cmisObjectType);
if (null == propertyDefinition)
{
// TODO: WARINING!!! This is WRONG behavior!!! Each CMIS object type and each property MUST be described in appropriate CMIS manner
QName qualifiedName = createQName(propertyName);
Map<QName, PropertyDefinition> properties = (null != nativeObjectType) ? (nativeObjectType.getProperties()) : (null);
if ((null == qualifiedName) || (null == properties) || properties.containsKey(qualifiedName))
{
return PropertyCheckingStateEnum.PROPERTY_NOT_UPDATABLE;
}
return PropertyCheckingStateEnum.PROPERTY_NATIVE;
}
// [FIXED BUG] condition and first calculating value were swapped
boolean updatable = ((CMISUpdatabilityEnum.READ_AND_WRITE_WHEN_CHECKED_OUT == propertyDefinition.getUpdatability()) ? (checkedOut)
: (CMISUpdatabilityEnum.READ_AND_WRITE == propertyDefinition.getUpdatability()));
if (!updatable)
{
return PropertyCheckingStateEnum.PROPERTY_NOT_UPDATABLE;
}
if (propertyDefinition.isRequired() && (null == value))
{
throw ExceptionUtil.createCmisException((propertyName + " property required"), EnumServiceException.CONSTRAINT);
}
switch (propertyDefinition.getDataType())
{
case STRING:
{
checkStringProperty(propertyDefinition, propertyName, (String) value);
break;
}
case INTEGER:
case DECIMAL:
{
checkNumberProperty(propertyDefinition, propertyName, (Number) value);
break;
}
}
return PropertyCheckingStateEnum.PROPERTY_CHECKED;
}
public enum PropertyCheckingStateEnum
{
PROPERTY_CHECKED, PROPERTY_NATIVE, PROPERTY_NOT_UPDATABLE;
}
private void checkNumberProperty(CMISPropertyDefinition propertyDefinition, String propertyName, Number value)
{
// TODO: if max and min value properties will be added to CMISPropertyDefinition
}
private void checkStringProperty(CMISPropertyDefinition propertyDefinition, String propertyName, String value) throws CmisException
{
if (value != null && (propertyDefinition.getMaximumLength() > 0) && (value.length() > propertyDefinition.getMaximumLength()))
{
throw ExceptionUtil.createCmisException((propertyName + " property value is too long"), EnumServiceException.CONSTRAINT);
}
}
/**
* Get CMIS properties for object
*
@@ -517,24 +448,37 @@ public class PropertyUtil
*/
public CmisPropertiesType getProperties(Object object, PropertyFilter filter) throws CmisException
{
if (object instanceof Version)
{
object = ((Version) object).getFrozenStateNodeRef();
}
try
{
CmisPropertiesType result = new CmisPropertiesType();
Map<String, Serializable> properties;
if (object instanceof NodeRef)
{
properties = cmisService.getProperties((NodeRef) object);
}
else if (object instanceof Version)
{
properties = cmisService.getProperties(((Version) object).getFrozenStateNodeRef());
// Handle fetching of aspects and their properties with Alfresco extension
GetAspects extension = new GetAspects();
result.getAny().add(extension);
List<String> aspects = extension.getAppliedAspects();
Map<String, Serializable> aspectProperties = new HashMap<String, Serializable>(97);
for (CMISTypeDefinition typeDef : cmisService.getAspects((NodeRef)object))
{
aspects.add(typeDef.getTypeId().getId());
aspectProperties.putAll(cmisService.getProperties((NodeRef)object, typeDef));
}
CmisPropertiesType aspectResult = new CmisPropertiesType();
convertToCmisProperties(aspectProperties, filter, aspectResult);
extension.setProperties(aspectResult);
}
else
{
properties = createBaseRelationshipProperties((AssociationRef) object);
}
CmisPropertiesType result = new CmisPropertiesType();
convertToCmisProperties(properties, filter, result);
convertToCmisProperties(properties, filter, result);
return result;
}
catch (CMISInvalidArgumentException e)
@@ -542,7 +486,7 @@ public class PropertyUtil
throw ExceptionUtil.createCmisException(e.getMessage(), EnumServiceException.INVALID_ARGUMENT, e);
}
}
private Map<String, Serializable> createBaseRelationshipProperties(AssociationRef association)
{
Map<String, Serializable> result = new HashMap<String, Serializable>();