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

@@ -313,7 +313,6 @@
<bean id="propertiesUtils" class="org.alfresco.repo.cmis.ws.utils.PropertyUtil"> <bean id="propertiesUtils" class="org.alfresco.repo.cmis.ws.utils.PropertyUtil">
<property name="cmisDictionaryService" ref="CMISDictionaryService" /> <property name="cmisDictionaryService" ref="CMISDictionaryService" />
<property name="cmisService" ref="CMISService" /> <property name="cmisService" ref="CMISService" />
<property name="nodeService" ref="NodeService" />
<property name="dictionaryService" ref="DictionaryService" /> <property name="dictionaryService" ref="DictionaryService" />
<property name="namespaceService" ref="NamespaceService" /> <property name="namespaceService" ref="NamespaceService" />
</bean> </bean>

View File

@@ -10,14 +10,33 @@
</entry> </entry>
[/#macro] [/#macro]
[#macro nodeCMISProps node propfilter]
<cmis:properties>
[@typeCMISProps node cmistype(node) propfilter/]
[#-- Nest the Alfresco extension for aspects and their properties --]
<alf:getAspects>
[#list cmisaspects(node) as aspectdef]
<alf:appliedAspects>${aspectdef.typeId.id}</alf:appliedAspects>
[/#list]
<alf:properties>
[#list cmisaspects(node) as aspectdef]
[@typeCMISProps node aspectdef propfilter/]
[/#list]
</alf:properties>
</alf:getAspects>
</cmis:properties>
[/#macro]
[#macro objectCMISProps object propfilter] [#macro objectCMISProps object propfilter]
<cmis:properties> <cmis:properties>
[#assign typedef = cmistype(object)] [@typeCMISProps object cmistype(object) propfilter/]
</cmis:properties>
[/#macro]
[#macro typeCMISProps object typedef propfilter]
[#list typedef.propertyDefinitions?values as propdef] [#list typedef.propertyDefinitions?values as propdef]
[@filter propfilter propdef.queryName][@prop object propdef/][/@filter] [@filter propfilter propdef.queryName][@prop object propdef/][/@filter]
[/#list] [/#list]
</cmis:properties>
[/#macro] [/#macro]
@@ -42,7 +61,7 @@
<app:edited>${xmldate(node.properties.modified)}</app:edited> <app:edited>${xmldate(node.properties.modified)}</app:edited>
<alf:icon>${absurl(url.context)}${node.icon16}</alf:icon> <alf:icon>${absurl(url.context)}${node.icon16}</alf:icon>
<cmisra:object> <cmisra:object>
[@objectCMISProps node propfilter/] [@nodeCMISProps node propfilter/]
[#if includeallowableactions][@allowableactions node/][/#if] [#if includeallowableactions][@allowableactions node/][/#if]
[@relationships node includerelationships includeallowableactions propfilter/] [@relationships node includerelationships includeallowableactions propfilter/]
[#if includeacl][@aclreport node/][/#if] [#if includeacl][@aclreport node/][/#if]
@@ -95,7 +114,7 @@
<app:edited>${xmldate(node.properties.modified)}</app:edited> <app:edited>${xmldate(node.properties.modified)}</app:edited>
<alf:icon>${absurl(url.context)}${node.icon16}</alf:icon> <alf:icon>${absurl(url.context)}${node.icon16}</alf:icon>
<cmisra:object> <cmisra:object>
[@objectCMISProps node propfilter/] [@nodeCMISProps node propfilter/]
[#if includeallowableactions][@allowableactions node/][/#if] [#if includeallowableactions][@allowableactions node/][/#if]
[@relationships node includerelationships includeallowableactions propfilter/] [@relationships node includerelationships includeallowableactions propfilter/]
[#if includeacl][@aclreport node/][/#if] [#if includeacl][@aclreport node/][/#if]

View File

@@ -89,76 +89,30 @@ function updateNode(node, entry, exclude, validator)
var updated = false; var updated = false;
var object = entry.getExtension(atom.names.cmisra_object); var object = entry.getExtension(atom.names.cmisra_object);
var props = (object == null) ? null : object.properties;
var vals = new Object(); var vals = new Object();
// calculate list of properties to update
// TODO: consider array form of properties.names
var updateProps = (props == null) ? new Array() : props.ids.toArray().filter(function(element, index, array) {return true;});
updateProps.push(PROP_NAME); // mapped to entry.title
var exclude = (exclude == null) ? new Array() : exclude; var exclude = (exclude == null) ? new Array() : exclude;
exclude.push(PROP_BASE_TYPE_ID); exclude.push(PROP_BASE_TYPE_ID);
updateProps = updateProps.filter(includeProperty, exclude);
// build values to update
if (updateProps.length > 0)
{
var typeDef = cmis.queryType(node); var typeDef = cmis.queryType(node);
var propDefs = typeDef.propertyDefinitions;
for each (propName in updateProps)
{
// is this a valid property?
var propDef = propDefs[propName];
if (propDef == null)
{
status.code = 400;
status.message = "Property " + propName + " is not a known property for type " + typeDef.typeId;
status.redirect = true;
return null;
}
// validate property update // Apply the provided properties of the node type
var valid = validator(propDef); unpackProperties(node, typeDef, object == null ? null : object.properties, exclude, validator, vals);
if (valid == null)
{
// error, abort update
return null;
}
if (valid == false)
{
// ignore property
continue;
}
// extract value
var val = null;
var prop = (props == null) ? null : props.find(propName);
if (prop != null && !prop.isNull())
{
if (prop.isMultiValued())
{
if (propDef.updatability === CMISCardinalityEnum.MULTI_VALUED)
{
status.code = 500;
status.message = "Property " + propName + " is single valued."
status.redirect = true;
return null;
}
val = prop.nativeValues;
}
else
{
val = prop.nativeValue;
}
}
// NOTE: special case name: entry.title overrides cmis:name // NOTE: special case name: entry.title overrides cmis:name
if (propName === PROP_NAME) var propDef = typeDef.propertyDefinitions[PROP_NAME];
{ vals[propDef.propertyAccessor.mappedProperty.toString()] = entry.title;
val = entry.title;
}
vals[propDef.propertyAccessor.mappedProperty.toString()] = val; // Handle Alfresco aspects extension
if (object != null)
{
var extension = object.properties.getExtension(atom.names.alf_setAspects);
if (extension != null)
{
// Add and remove aspects
cmis.setAspects(node, extension.aspectsToAdd, extension.aspectsToRemove);
// Apply the provided properties of the aspects
unpackProperties(node, null, extension.properties, exclude, validator, vals);
} }
} }
@@ -235,7 +189,81 @@ function updateNode(node, entry, exclude, validator)
return updated; return updated;
} }
function unpackProperties(node, typeDef, props, exclude, validator, vals)
{
// calculate list of properties to update
// TODO: consider array form of properties.names
var updateProps = (props == null) ? new Array() : props.ids.toArray().filter(function(element, index, array) {return true;});
updateProps = updateProps.filter(includeProperty, exclude);
// build values to update
if (updateProps.length > 0)
{
for each (propName in updateProps)
{
// is this a valid property?
var propDef = typeDef == null ? cmis.queryProperty(propName) : typeDef.propertyDefinitions[propName];
if (propDef == null)
{
status.code = 400;
if (typeDef == null)
{
status.message = "Property " + propName + " is not a known property";
}
else
{
status.message = "Property " + propName + " is not a known property for type " + typeDef.typeId;
}
status.redirect = true;
return null;
}
// validate property update
var valid = validator(propDef);
if (valid == null)
{
// error, abort update
return null;
}
if (valid == false)
{
// ignore property
continue;
}
// extract value
var val = null;
var prop = (props == null) ? null : props.find(propName);
if (prop != null && !prop.isNull())
{
if (prop.isMultiValued())
{
if (propDef.updatability === CMISCardinalityEnum.MULTI_VALUED)
{
status.code = 500;
status.message = "Property " + propName + " is single valued."
status.redirect = true;
return null;
}
val = prop.nativeValues;
}
else
{
val = prop.nativeValue;
}
}
// NOTE: special case name: entry.title overrides cmis:name
if (propName === PROP_NAME)
{
val = entry.title;
}
vals[propDef.propertyAccessor.mappedProperty.toString()] = val;
}
}
}
// //
// Create Alfresco Association from Atom Entry // Create Alfresco Association from Atom Entry
// //

View File

@@ -174,6 +174,11 @@
<constructor-arg><ref bean="webscripts.repo.imageresolver"/></constructor-arg> <constructor-arg><ref bean="webscripts.repo.imageresolver"/></constructor-arg>
</bean> </bean>
</entry> </entry>
<entry key="cmisaspects">
<bean class="org.alfresco.repo.cmis.rest.CMISAspectsMethod">
<constructor-arg><ref bean="CMISService"/></constructor-arg>
</bean>
</entry>
</map> </map>
</property> </property>
<property name="registryFactory"> <property name="registryFactory">
@@ -241,11 +246,12 @@
<prop key="cmisra_object">{http://docs.oasis-open.org/ns/cmis/restatom/200908/}object</prop> <prop key="cmisra_object">{http://docs.oasis-open.org/ns/cmis/restatom/200908/}object</prop>
<prop key="cmisra_content">{http://docs.oasis-open.org/ns/cmis/restatom/200908/}content</prop> <prop key="cmisra_content">{http://docs.oasis-open.org/ns/cmis/restatom/200908/}content</prop>
<prop key="cmisra_repositoryInfo">{http://docs.oasis-open.org/ns/cmis/restatom/200908/}repositoryInfo</prop> <prop key="cmisra_repositoryInfo">{http://docs.oasis-open.org/ns/cmis/restatom/200908/}repositoryInfo</prop>
<prop key="alf_setAspects">{http://www.alfresco.org}setAspects</prop>
</props> </props>
</property> </property>
<property name="extensionFactories"> <property name="extensionFactories">
<list> <list>
<bean class="org.apache.chemistry.abdera.ext.CMISExtensionFactory"/> <bean class="org.alfresco.repo.cmis.rest.AlfrescoCMISExtensionFactory"/>
</list> </list>
</property> </property>
</bean> </bean>

View File

@@ -0,0 +1,99 @@
package org.alfresco.repo.cmis.ws;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/**
* <p>Java class for anonymous complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* &lt;complexType>
* &lt;complexContent>
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* &lt;sequence>
* &lt;element name="appliedAspects" type="{http://www.w3.org/2001/XMLSchema}string" maxOccurs="unbounded" minOccurs="0"/>
* &lt;element name="properties" type="{http://docs.oasis-open.org/ns/cmis/core/200908/}cmisPropertiesType" minOccurs="0"/>
* &lt;/sequence>
* &lt;/restriction>
* &lt;/complexContent>
* &lt;/complexType>
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"appliedAspects",
"properties"
})
@XmlRootElement(name = "getAspects", namespace = "http://www.alfresco.org")
public class GetAspects {
@XmlElement(namespace = "http://www.alfresco.org")
protected List<String> appliedAspects;
@XmlElement(namespace = "http://www.alfresco.org")
protected CmisPropertiesType properties;
/**
* Gets the value of the appliedAspects property.
*
* <p>
* This accessor method returns a reference to the live list,
* not a snapshot. Therefore any modification you make to the
* returned list will be present inside the JAXB object.
* This is why there is not a <CODE>set</CODE> method for the appliedAspects property.
*
* <p>
* For example, to add a new item, do as follows:
* <pre>
* getAppliedAspects().add(newItem);
* </pre>
*
*
* <p>
* Objects of the following type(s) are allowed in the list
* {@link String }
*
*
*/
public List<String> getAppliedAspects() {
if (appliedAspects == null) {
appliedAspects = new ArrayList<String>();
}
return this.appliedAspects;
}
/**
* Gets the value of the properties property.
*
* @return
* possible object is
* {@link CmisPropertiesType }
*
*/
public CmisPropertiesType getProperties() {
return properties;
}
/**
* Sets the value of the properties property.
*
* @param value
* allowed object is
* {@link CmisPropertiesType }
*
*/
public void setProperties(CmisPropertiesType value) {
this.properties = value;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,132 @@
package org.alfresco.repo.cmis.ws;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/**
* <p>Java class for anonymous complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* &lt;complexType>
* &lt;complexContent>
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* &lt;sequence>
* &lt;element name="aspectsToAdd" type="{http://www.w3.org/2001/XMLSchema}string" maxOccurs="unbounded" minOccurs="0"/>
* &lt;element name="aspectsToRemove" type="{http://www.w3.org/2001/XMLSchema}string" maxOccurs="unbounded" minOccurs="0"/>
* &lt;element name="properties" type="{http://docs.oasis-open.org/ns/cmis/core/200908/}cmisPropertiesType" minOccurs="0"/>
* &lt;/sequence>
* &lt;/restriction>
* &lt;/complexContent>
* &lt;/complexType>
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"aspectsToAdd",
"aspectsToRemove",
"properties"
})
@XmlRootElement(name = "setAspects", namespace = "http://www.alfresco.org")
public class SetAspects {
@XmlElement(namespace = "http://www.alfresco.org")
protected List<String> aspectsToAdd;
@XmlElement(namespace = "http://www.alfresco.org")
protected List<String> aspectsToRemove;
@XmlElement(namespace = "http://www.alfresco.org")
protected CmisPropertiesType properties;
/**
* Gets the value of the aspectsToAdd property.
*
* <p>
* This accessor method returns a reference to the live list,
* not a snapshot. Therefore any modification you make to the
* returned list will be present inside the JAXB object.
* This is why there is not a <CODE>set</CODE> method for the aspectsToAdd property.
*
* <p>
* For example, to add a new item, do as follows:
* <pre>
* getAspectsToAdd().add(newItem);
* </pre>
*
*
* <p>
* Objects of the following type(s) are allowed in the list
* {@link String }
*
*
*/
public List<String> getAspectsToAdd() {
if (aspectsToAdd == null) {
aspectsToAdd = new ArrayList<String>();
}
return this.aspectsToAdd;
}
/**
* Gets the value of the aspectsToRemove property.
*
* <p>
* This accessor method returns a reference to the live list,
* not a snapshot. Therefore any modification you make to the
* returned list will be present inside the JAXB object.
* This is why there is not a <CODE>set</CODE> method for the aspectsToRemove property.
*
* <p>
* For example, to add a new item, do as follows:
* <pre>
* getAspectsToRemove().add(newItem);
* </pre>
*
*
* <p>
* Objects of the following type(s) are allowed in the list
* {@link String }
*
*
*/
public List<String> getAspectsToRemove() {
if (aspectsToRemove == null) {
aspectsToRemove = new ArrayList<String>();
}
return this.aspectsToRemove;
}
/**
* Gets the value of the properties property.
*
* @return
* possible object is
* {@link CmisPropertiesType }
*
*/
public CmisPropertiesType getProperties() {
return properties;
}
/**
* Sets the value of the properties property.
*
* @param value
* allowed object is
* {@link CmisPropertiesType }
*
*/
public void setProperties(CmisPropertiesType value) {
this.properties = value;
}
}

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; Object result = null;
if (wrapped != null && wrapped instanceof TemplateNode) if (wrapped != null && wrapped instanceof TemplateNode)
{ {
// retrieve property value from node // retrieve property value from node, allowing aspect properties
result = cmisService.getProperty(((TemplateNode) wrapped).getNodeRef(), propertyName); result = cmisService.getProperty(((TemplateNode) wrapped).getNodeRef(), null, propertyName);
} }
else if (wrapped != null && wrapped instanceof TemplateAssociation) else if (wrapped != null && wrapped instanceof TemplateAssociation)
{ {

View File

@@ -563,6 +563,30 @@ public class CMISScript extends BaseScopableProcessorExtension
return cmisDictionaryService.findProperty(propertyName, null); 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 // 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.repo.web.util.paging.Cursor;
import org.alfresco.service.cmr.dictionary.AssociationDefinition; import org.alfresco.service.cmr.dictionary.AssociationDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryService; 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.FileExistsException;
import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.model.FileInfo;
import org.alfresco.service.cmr.model.FileNotFoundException; 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); Map<String, Object> propertiesMap = propertiesUtil.getPropertiesMap(properties);
String typeId = extractAndAssertTypeId(propertiesMap); String typeId = extractAndAssertTypeId(propertiesMap);
CMISTypeDefinition type = cmisService.getTypeDefinition(typeId); CMISTypeDefinition type = cmisService.getTypeDefinition(typeId);
TypeDefinition nativeType = dictionaryService.getType(nodeService.getType(folderNodeRef));
if (type == null || type.getTypeId() == null || type.getTypeId().getScope() != CMISScope.FOLDER) 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); 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); 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 try
{ {
@@ -819,8 +820,6 @@ public class DMObjectServicePort extends DMAbstractServicePort implements Object
{ {
throw ExceptionUtil.createCmisException("Name property not found", EnumServiceException.INVALID_ARGUMENT); throw ExceptionUtil.createCmisException("Name property not found", EnumServiceException.INVALID_ARGUMENT);
} }
propertiesUtil.checkProperty(null, typeDef, CMISDictionaryModel.PROP_NAME, result, (EnumVersioningState.CHECKEDOUT == versioningState));
return result; return result;
} }

View File

@@ -25,6 +25,7 @@ import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import javax.xml.datatype.DatatypeConfigurationException; import javax.xml.datatype.DatatypeConfigurationException;
@@ -37,9 +38,9 @@ import org.alfresco.cmis.CMISDictionaryService;
import org.alfresco.cmis.CMISInvalidArgumentException; import org.alfresco.cmis.CMISInvalidArgumentException;
import org.alfresco.cmis.CMISPropertyDefinition; import org.alfresco.cmis.CMISPropertyDefinition;
import org.alfresco.cmis.CMISScope; import org.alfresco.cmis.CMISScope;
import org.alfresco.cmis.CMISServiceException;
import org.alfresco.cmis.CMISServices; import org.alfresco.cmis.CMISServices;
import org.alfresco.cmis.CMISTypeDefinition; import org.alfresco.cmis.CMISTypeDefinition;
import org.alfresco.cmis.CMISUpdatabilityEnum;
import org.alfresco.repo.cmis.PropertyFilter; import org.alfresco.repo.cmis.PropertyFilter;
import org.alfresco.repo.cmis.ws.CmisException; import org.alfresco.repo.cmis.ws.CmisException;
import org.alfresco.repo.cmis.ws.CmisPropertiesType; 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.CmisPropertyString;
import org.alfresco.repo.cmis.ws.CmisPropertyUri; import org.alfresco.repo.cmis.ws.CmisPropertyUri;
import org.alfresco.repo.cmis.ws.EnumServiceException; 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.repo.security.authentication.AuthenticationUtil;
import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.dictionary.PropertyDefinition; 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.AssociationRef;
import org.alfresco.service.cmr.repository.NodeRef; 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.cmr.version.Version;
import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QName;
@@ -89,7 +90,6 @@ public class PropertyUtil
private static final String BASE_TYPE_PROPERTY_NAME = "BaseType"; private static final String BASE_TYPE_PROPERTY_NAME = "BaseType";
private NodeService nodeService;
private DictionaryService dictionaryService; private DictionaryService dictionaryService;
private NamespaceService namespaceService; private NamespaceService namespaceService;
private CMISServices cmisService; private CMISServices cmisService;
@@ -99,11 +99,6 @@ public class PropertyUtil
{ {
} }
public void setNodeService(NodeService nodeService)
{
this.nodeService = nodeService;
}
public void setDictionaryService(DictionaryService dictionaryService) public void setDictionaryService(DictionaryService dictionaryService)
{ {
this.dictionaryService = dictionaryService; this.dictionaryService = dictionaryService;
@@ -348,59 +343,71 @@ public class PropertyUtil
/** /**
* Sets and checks all properties' fields for specified node * Sets and checks all properties' fields for specified node
* *
* @param nodeRef - <b>NodeRef</b> for node for those properties must be setted * @param nodeRef
* @param properties - <b>CmisPropertiesType</b> instance that contains all the necessary properties' fields * - <b>NodeRef</b> for node for those properties must be setted
* @param ignoringPropertiesFilter - <b>PropertyFilter</b> instance. This filter determines which properties should be ignored and not setted without exception. If this * @param properties
* parameter is <b>null</b> all properties will be processed in common flow * - <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 (nodeRef == null || properties == null)
if ((null == nodeRef) || (null == properties) || (null == properties.getProperty()))
{ {
return; return;
} }
String typeId = getProperty(nodeRef, CMISDictionaryModel.PROP_OBJECT_TYPE_ID, null); try
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))
{ {
throw ExceptionUtil.createCmisException(("Can't find type definition for current object with \"" + typeId + "\" type Id"), EnumServiceException.INVALID_ARGUMENT);
}
for (CmisProperty property : properties.getProperty()) for (CmisProperty property : properties.getProperty())
{ {
String propertyName = getPropertyName(property); String propertyName = getPropertyName(property);
if ((null == propertyName) || ((null != ignoringPropertiesFilter) && ignoringPropertiesFilter.allow(propertyName))) 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; continue;
} }
for (CmisProperty property : extensionProperties.getProperty())
{
String propertyName = getPropertyName(property);
if ((null == propertyName)
|| ((null != ignoringPropertiesFilter) && ignoringPropertiesFilter.allow(propertyName)))
{
continue;
}
Object value = getValue(property); Object value = getValue(property);
QName alfrescoPropertyName = null;
switch (checkProperty(nativeObjectType, cmisObjectType, propertyName, value, checkedOut)) // This time, call setProperty without constraining the owning type
{ cmisService.setProperty(nodeRef, null, propertyName, (Serializable) value);
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); }
catch (CMISServiceException e)
{
throw ExceptionUtil.createCmisException(e);
} }
} }
@@ -432,82 +439,6 @@ public class PropertyUtil
return qname; 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 * Get CMIS properties for object
* *
@@ -517,23 +448,36 @@ public class PropertyUtil
*/ */
public CmisPropertiesType getProperties(Object object, PropertyFilter filter) throws CmisException public CmisPropertiesType getProperties(Object object, PropertyFilter filter) throws CmisException
{ {
if (object instanceof Version)
{
object = ((Version) object).getFrozenStateNodeRef();
}
try try
{ {
CmisPropertiesType result = new CmisPropertiesType();
Map<String, Serializable> properties; Map<String, Serializable> properties;
if (object instanceof NodeRef) if (object instanceof NodeRef)
{ {
properties = cmisService.getProperties((NodeRef) object); properties = cmisService.getProperties((NodeRef) object);
}
else if (object instanceof Version) // 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))
{ {
properties = cmisService.getProperties(((Version) object).getFrozenStateNodeRef()); aspects.add(typeDef.getTypeId().getId());
aspectProperties.putAll(cmisService.getProperties((NodeRef)object, typeDef));
}
CmisPropertiesType aspectResult = new CmisPropertiesType();
convertToCmisProperties(aspectProperties, filter, aspectResult);
extension.setProperties(aspectResult);
} }
else else
{ {
properties = createBaseRelationshipProperties((AssociationRef) object); properties = createBaseRelationshipProperties((AssociationRef) object);
} }
CmisPropertiesType result = new CmisPropertiesType();
convertToCmisProperties(properties, filter, result); convertToCmisProperties(properties, filter, result);
return result; return result;
} }

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns:alf="http://www.alfresco.org" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:ns="http://schemas.xmlsoap.org/soap/encoding/" targetNamespace="http://www.alfresco.org"
name="AlfrescoCMISWebServices">
<import namespace="http://docs.oasis-open.org/ns/cmis/ws/200908/" location="CMISWS-Service.wsdl" />
<import namespace="http://www.alfresco.org" location="Alfresco-Core.xsd" />
</definitions>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" targetNamespace="http://www.alfresco.org"
xmlns:atom="http://www.w3.org/2005/Atom" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc" jaxb:extensionBindingPrefixes="xjc" jaxb:version="2.1"
xmlns:cmis="http://docs.oasis-open.org/ns/cmis/core/200908/" version="1.0">
<xs:import schemaLocation="CMIS-Core.xsd" namespace="http://docs.oasis-open.org/ns/cmis/core/200908/" />
<xs:element name="setAspects">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" name="aspectsToAdd" type="xs:string" />
<xs:element minOccurs="0" maxOccurs="unbounded" name="aspectsToRemove" type="xs:string" />
<xs:element name="properties" type="cmis:cmisPropertiesType" minOccurs="0" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="getAspects">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" name="appliedAspects" type="xs:string" />
<xs:element name="properties" type="cmis:cmisPropertiesType" minOccurs="0" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>