mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-14 17:58:59 +00:00
CMIS Relationship support in AtomPub binding
- getRelationships - getRelationship - createRelationship - unit tests for above TODO: delete relationship, includeRelationships flag git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@14461 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -10,6 +10,19 @@
|
|||||||
</entry>
|
</entry>
|
||||||
[/#macro]
|
[/#macro]
|
||||||
|
|
||||||
|
[#macro objectCMISProps object propfilter]
|
||||||
|
<cmis:properties>
|
||||||
|
[#assign typedef = cmistype(object)]
|
||||||
|
|
||||||
|
[#-- TODO: Spec issue: BaseType not a property --]
|
||||||
|
[@filter propfilter "BaseType"][@propvalue "BaseType" typedef.typeId.baseTypeId.id "STRING"/][/@filter]
|
||||||
|
|
||||||
|
[#list typedef.propertyDefinitions?values as propdef]
|
||||||
|
[@filter propfilter propdef.propertyId.name][@prop propdef.propertyId.name object propdef.dataType/][/@filter]
|
||||||
|
[/#list]
|
||||||
|
</cmis:properties>
|
||||||
|
[/#macro]
|
||||||
|
|
||||||
|
|
||||||
[#-- --]
|
[#-- --]
|
||||||
[#-- ATOM Entry for Document --]
|
[#-- ATOM Entry for Document --]
|
||||||
@@ -30,7 +43,7 @@
|
|||||||
<title>${node.name}</title>
|
<title>${node.name}</title>
|
||||||
<updated>${xmldate(node.properties.modified)}</updated>
|
<updated>${xmldate(node.properties.modified)}</updated>
|
||||||
<cmis:object>
|
<cmis:object>
|
||||||
[@documentCMISProps node propfilter/]
|
[@objectCMISProps node propfilter/]
|
||||||
[#if includeallowableactions][@allowableactions node/][/#if]
|
[#if includeallowableactions][@allowableactions node/][/#if]
|
||||||
</cmis:object>
|
</cmis:object>
|
||||||
<cmis:terminator/>
|
<cmis:terminator/>
|
||||||
@@ -41,7 +54,7 @@
|
|||||||
|
|
||||||
[#macro documentCMISLinks node]
|
[#macro documentCMISLinks node]
|
||||||
<link rel="allowableactions" href="${absurl(url.serviceContext)}/api/node/${node.nodeRef.storeRef.protocol}/${node.nodeRef.storeRef.identifier}/${node.nodeRef.id}/permissions"/>
|
<link rel="allowableactions" href="${absurl(url.serviceContext)}/api/node/${node.nodeRef.storeRef.protocol}/${node.nodeRef.storeRef.identifier}/${node.nodeRef.id}/permissions"/>
|
||||||
<link rel="relationships" href="${absurl(url.serviceContext)}/api/node/${node.nodeRef.storeRef.protocol}/${node.nodeRef.storeRef.identifier}/${node.nodeRef.id}/associations"/>
|
<link rel="relationships" href="${absurl(url.serviceContext)}/api/node/${node.nodeRef.storeRef.protocol}/${node.nodeRef.storeRef.identifier}/${node.nodeRef.id}/rels"/>
|
||||||
<link rel="parents" href="${absurl(url.serviceContext)}/api/node/${node.nodeRef.storeRef.protocol}/${node.nodeRef.storeRef.identifier}/${node.nodeRef.id}/parents"/>
|
<link rel="parents" href="${absurl(url.serviceContext)}/api/node/${node.nodeRef.storeRef.protocol}/${node.nodeRef.storeRef.identifier}/${node.nodeRef.id}/parents"/>
|
||||||
<link rel="allversions" href="${absurl(url.serviceContext)}/api/node/${node.nodeRef.storeRef.protocol}/${node.nodeRef.storeRef.identifier}/${node.nodeRef.id}/versions"/>
|
<link rel="allversions" href="${absurl(url.serviceContext)}/api/node/${node.nodeRef.storeRef.protocol}/${node.nodeRef.storeRef.identifier}/${node.nodeRef.id}/versions"/>
|
||||||
[@linkstream node "stream"/]
|
[@linkstream node "stream"/]
|
||||||
@@ -49,18 +62,6 @@
|
|||||||
<link rel="repository" href="[@serviceuri/]"/>
|
<link rel="repository" href="[@serviceuri/]"/>
|
||||||
[/#macro]
|
[/#macro]
|
||||||
|
|
||||||
[#macro documentCMISProps node propfilter]
|
|
||||||
<cmis:properties>
|
|
||||||
[#-- TODO: Spec issue: BaseType not a property --]
|
|
||||||
[@filter propfilter "BaseType"][@propvalue "BaseType" "document" "STRING"/][/@filter]
|
|
||||||
|
|
||||||
[#assign typedef = cmistype(node)]
|
|
||||||
[#list typedef.propertyDefinitions?values as propdef]
|
|
||||||
[@filter propfilter propdef.propertyId.name][@prop propdef.propertyId.name node propdef.dataType/][/@filter]
|
|
||||||
[/#list]
|
|
||||||
</cmis:properties>
|
|
||||||
[/#macro]
|
|
||||||
|
|
||||||
|
|
||||||
[#-- --]
|
[#-- --]
|
||||||
[#-- ATOM Entry for Version --]
|
[#-- ATOM Entry for Version --]
|
||||||
@@ -79,7 +80,7 @@
|
|||||||
<title>${node.name}</title>
|
<title>${node.name}</title>
|
||||||
<updated>${xmldate(node.properties.modified)}</updated>
|
<updated>${xmldate(node.properties.modified)}</updated>
|
||||||
<cmis:object>
|
<cmis:object>
|
||||||
[@documentCMISProps node propfilter/]
|
[@objectCMISProps node propfilter/]
|
||||||
</cmis:object>
|
</cmis:object>
|
||||||
<cmis:terminator/>
|
<cmis:terminator/>
|
||||||
<app:edited>${xmldate(node.properties.modified)}</app:edited>
|
<app:edited>${xmldate(node.properties.modified)}</app:edited>
|
||||||
@@ -107,7 +108,7 @@
|
|||||||
<title>${node.name}</title>
|
<title>${node.name}</title>
|
||||||
<updated>${xmldate(node.properties.modified)}</updated>
|
<updated>${xmldate(node.properties.modified)}</updated>
|
||||||
<cmis:object>
|
<cmis:object>
|
||||||
[@documentCMISProps node propfilter/]
|
[@objectCMISProps node propfilter/]
|
||||||
[#if includeallowableactions][@allowableactions node/][/#if]
|
[#if includeallowableactions][@allowableactions node/][/#if]
|
||||||
</cmis:object>
|
</cmis:object>
|
||||||
<cmis:terminator/>
|
<cmis:terminator/>
|
||||||
@@ -136,7 +137,7 @@
|
|||||||
<updated>${xmldate(node.properties.modified)}</updated>
|
<updated>${xmldate(node.properties.modified)}</updated>
|
||||||
<cmis:object>
|
<cmis:object>
|
||||||
[#-- recurse for depth greater than 1 --]
|
[#-- recurse for depth greater than 1 --]
|
||||||
[@folderCMISProps node propfilter/]
|
[@objectCMISProps node propfilter/]
|
||||||
[#if includeallowableactions][@allowableactions node/][/#if]
|
[#if includeallowableactions][@allowableactions node/][/#if]
|
||||||
</cmis:object>
|
</cmis:object>
|
||||||
[#if depth < maxdepth || depth == -1]
|
[#if depth < maxdepth || depth == -1]
|
||||||
@@ -157,7 +158,7 @@
|
|||||||
|
|
||||||
[#macro folderCMISLinks node]
|
[#macro folderCMISLinks node]
|
||||||
<link rel="allowableactions" href="${absurl(url.serviceContext)}/api/node/${node.nodeRef.storeRef.protocol}/${node.nodeRef.storeRef.identifier}/${node.nodeRef.id}/permissions"/>
|
<link rel="allowableactions" href="${absurl(url.serviceContext)}/api/node/${node.nodeRef.storeRef.protocol}/${node.nodeRef.storeRef.identifier}/${node.nodeRef.id}/permissions"/>
|
||||||
<link rel="relationships" href="${absurl(url.serviceContext)}/api/node/${node.nodeRef.storeRef.protocol}/${node.nodeRef.storeRef.identifier}/${node.nodeRef.id}/associations"/>
|
<link rel="relationships" href="${absurl(url.serviceContext)}/api/node/${node.nodeRef.storeRef.protocol}/${node.nodeRef.storeRef.identifier}/${node.nodeRef.id}/rels"/>
|
||||||
[#if cmisproperty(node, "ParentId")?is_string]
|
[#if cmisproperty(node, "ParentId")?is_string]
|
||||||
<link rel="parents" href="${absurl(url.serviceContext)}/api/node/${node.nodeRef.storeRef.protocol}/${node.nodeRef.storeRef.identifier}/${node.nodeRef.id}/parent"/>
|
<link rel="parents" href="${absurl(url.serviceContext)}/api/node/${node.nodeRef.storeRef.protocol}/${node.nodeRef.storeRef.identifier}/${node.nodeRef.id}/parent"/>
|
||||||
[/#if]
|
[/#if]
|
||||||
@@ -167,16 +168,38 @@
|
|||||||
<link rel="repository" href="[@serviceuri/]"/>
|
<link rel="repository" href="[@serviceuri/]"/>
|
||||||
[/#macro]
|
[/#macro]
|
||||||
|
|
||||||
[#macro folderCMISProps node propfilter]
|
|
||||||
<cmis:properties>
|
|
||||||
[#-- TODO: Spec issue: BaseType not a property --]
|
|
||||||
[@filter propfilter "BaseType"][@propvalue "BaseType" "folder" "STRING"/][/@filter]
|
|
||||||
|
|
||||||
[#assign typedef = cmistype(node)]
|
[#-- --]
|
||||||
[#list typedef.propertyDefinitions?values as propdef]
|
[#-- ATOM Entry for Relationship --]
|
||||||
[@filter propfilter propdef.propertyId.name][@prop propdef.propertyId.name node propdef.dataType/][/@filter]
|
[#-- --]
|
||||||
[/#list]
|
|
||||||
</cmis:properties>
|
[#macro assoc assoc propfilter="*" includeallowableactions=false ns=""]
|
||||||
|
[@entry ns]
|
||||||
|
<author><name>${xmldate(date)}</name></author> [#-- TODO: [@namedvalue "CreatedBy" assoc "STRING"/] --]
|
||||||
|
<content>[@namedvalue "ObjectId" assoc "ID"/]</content> [#-- TODO: spec id, how to map? --]
|
||||||
|
<id>[@namedvalue "ObjectId" assoc "ID"/]</id> [#-- TODO: id compliant --]
|
||||||
|
<link rel="self" href="${absurl(url.serviceContext)}[@assocuri assoc/]"/>
|
||||||
|
<link rel="edit" href="${absurl(url.serviceContext)}[@assocuri assoc/]"/>
|
||||||
|
[@assocCMISLinks assoc=assoc/]
|
||||||
|
<published>${xmldate(date)}</published> [#-- TODO: [@namedvalue "CreationDate" assoc "DATETIME"/] --]
|
||||||
|
<summary>[@namedvalue "ObjectId" assoc "ID"/]</summary> [#-- TODO: spec id, how to map? --]
|
||||||
|
<title>[@namedvalue "ObjectId" assoc "ID"/]</title> [#-- TODO: spec id, how to map? --]
|
||||||
|
<updated>${xmldate(date)}</updated> [#-- TODO: [@namedvalue "LastModificationDate" assoc "DATETIME"/] --]
|
||||||
|
<cmis:object>
|
||||||
|
[@objectCMISProps assoc propfilter/]
|
||||||
|
[#-- TODO: [#if includeallowableactions][@allowableactions node/][/#if] --]
|
||||||
|
</cmis:object>
|
||||||
|
<cmis:terminator/>
|
||||||
|
<app:edited>${xmldate(date)}</app:edited> [#-- TODO: [@namedvalue "LastModificationDate" assoc "DATETIME"/] --]
|
||||||
|
[/@entry]
|
||||||
|
[/#macro]
|
||||||
|
|
||||||
|
[#macro assocCMISLinks assoc]
|
||||||
|
[#-- TODO: <link rel="allowableactions" href="${absurl(url.serviceContext)}/api/node/${node.nodeRef.storeRef.protocol}/${node.nodeRef.storeRef.identifier}/${node.nodeRef.id}/permissions"/> --]
|
||||||
|
<link rel="type" href="${absurl(url.serviceContext)}/api/type/${cmistype(assoc).typeId.id!"unknown"}"/>
|
||||||
|
<link rel="source" href="${absurl(url.serviceContext)}[@nodeuri assoc.source/]"/>
|
||||||
|
<link rel="target" href="${absurl(url.serviceContext)}[@nodeuri assoc.target/]"/>
|
||||||
|
<link rel="repository" href="[@serviceuri/]"/>
|
||||||
[/#macro]
|
[/#macro]
|
||||||
|
|
||||||
|
|
||||||
@@ -239,8 +262,8 @@
|
|||||||
[#if filter == "*" || filter?index_of(value) != -1 || filter?matches(value,'i')][#nested][/#if]
|
[#if filter == "*" || filter?index_of(value) != -1 || filter?matches(value,'i')][#nested][/#if]
|
||||||
[/#macro]
|
[/#macro]
|
||||||
|
|
||||||
[#macro prop name node type]
|
[#macro prop name object type]
|
||||||
[#assign value=cmisproperty(node, name)/]
|
[#assign value=cmisproperty(object, name)/]
|
||||||
[#if value?is_string || value?is_number || value?is_boolean || value?is_date || value?is_enumerable]
|
[#if value?is_string || value?is_number || value?is_boolean || value?is_date || value?is_enumerable]
|
||||||
[@propvalue name value type/]
|
[@propvalue name value type/]
|
||||||
[#elseif value.class.canonicalName?ends_with("NULL")]
|
[#elseif value.class.canonicalName?ends_with("NULL")]
|
||||||
@@ -250,20 +273,20 @@
|
|||||||
|
|
||||||
[#macro propvalue name value type]
|
[#macro propvalue name value type]
|
||||||
[#if type == "STRING"]
|
[#if type == "STRING"]
|
||||||
<cmis:propertyString cmis:name="${name}">[@values value;v][@stringvalue v/][/@values]</cmis:propertyString>
|
<cmis:propertyString cmis:name="${name}">[@values value;v]<cmis:value>[@stringvalue v/]</cmis:value>[/@values]</cmis:propertyString>
|
||||||
[#elseif type == "INTEGER"]
|
[#elseif type == "INTEGER"]
|
||||||
<cmis:propertyInteger cmis:name="${name}">[@values value;v][@integervalue v/][/@values]</cmis:propertyInteger>
|
<cmis:propertyInteger cmis:name="${name}">[@values value;v]<cmis:value>[@integervalue v/]</cmis:value>[/@values]</cmis:propertyInteger>
|
||||||
[#elseif type == "DECIMAL"]
|
[#elseif type == "DECIMAL"]
|
||||||
<cmis:propertyDecimal cmis:name="${name}">[@values value;v][@decimalvalue v/][/@values]</cmis:propertyDecimal>
|
<cmis:propertyDecimal cmis:name="${name}">[@values value;v]<cmis:value>[@decimalvalue v/]</cmis:value>[/@values]</cmis:propertyDecimal>
|
||||||
[#elseif type == "BOOLEAN"]
|
[#elseif type == "BOOLEAN"]
|
||||||
<cmis:propertyBoolean cmis:name="${name}">[@values value;v][@booleanvalue v/][/@values]</cmis:propertyBoolean>
|
<cmis:propertyBoolean cmis:name="${name}">[@values value;v]<cmis:value>[@booleanvalue v/]</cmis:value>[/@values]</cmis:propertyBoolean>
|
||||||
[#elseif type == "DATETIME"]
|
[#elseif type == "DATETIME"]
|
||||||
<cmis:propertyDateTime cmis:name="${name}">[@values value;v][@datetimevalue v/][/@values]</cmis:propertyDateTime>
|
<cmis:propertyDateTime cmis:name="${name}">[@values value;v]<cmis:value>[@datetimevalue v/]</cmis:value>[/@values]</cmis:propertyDateTime>
|
||||||
[#elseif type == "URI"]
|
[#elseif type == "URI"]
|
||||||
[#-- TODO: check validity of abs url prefix --]
|
[#-- TODO: check validity of abs url prefix --]
|
||||||
<cmis:propertyUri cmis:name="${name}">[@values value;v][@urivalue absurl(url.serviceContext) + v/][/@values]</cmis:propertyUri>
|
<cmis:propertyUri cmis:name="${name}">[@values value;v]<cmis:value>[@urivalue absurl(url.serviceContext) + v/]</cmis:value>[/@values]</cmis:propertyUri>
|
||||||
[#elseif type == "ID"]
|
[#elseif type == "ID"]
|
||||||
<cmis:propertyId cmis:name="${name}">[@values value;v][@idvalue v/][/@values]</cmis:propertyId>
|
<cmis:propertyId cmis:name="${name}">[@values value;v]<cmis:value>[@idvalue v/]</cmis:value>[/@values]</cmis:propertyId>
|
||||||
[#-- TODO: remaining property types --]
|
[#-- TODO: remaining property types --]
|
||||||
[/#if]
|
[/#if]
|
||||||
[/#macro]
|
[/#macro]
|
||||||
@@ -292,15 +315,40 @@
|
|||||||
[#-- CMIS Values --]
|
[#-- CMIS Values --]
|
||||||
[#-- --]
|
[#-- --]
|
||||||
|
|
||||||
|
[#macro namedvalue name object type]
|
||||||
|
[#assign value=cmisproperty(object, name)/]
|
||||||
|
[#if value?is_string || value?is_number || value?is_boolean || value?is_date || value?is_enumerable][@typedvalue value type/][#elseif value.class.canonicalName?ends_with("NULL")][/#if]
|
||||||
|
[/#macro]
|
||||||
|
|
||||||
|
[#macro typedvalue value type]
|
||||||
|
[#if type == "STRING"]
|
||||||
|
[@values value;v][@stringvalue v/][/@values]
|
||||||
|
[#elseif type == "INTEGER"]
|
||||||
|
[@values value;v][@integervalue v/][/@values]
|
||||||
|
[#elseif type == "DECIMAL"]
|
||||||
|
[@values value;v][@decimalvalue v/][/@values]
|
||||||
|
[#elseif type == "BOOLEAN"]
|
||||||
|
[@values value;v][@booleanvalue v/][/@values]
|
||||||
|
[#elseif type == "DATETIME"]
|
||||||
|
[@values value;v][@datetimevalue v/][/@values]
|
||||||
|
[#elseif type == "URI"]
|
||||||
|
[#-- TODO: check validity of abs url prefix --]
|
||||||
|
[@values value;v][@urivalue absurl(url.serviceContext) + v/][/@values]
|
||||||
|
[#elseif type == "ID"]
|
||||||
|
[@values value;v][@idvalue v/][/@values]
|
||||||
|
[#-- TODO: remaining property types --]
|
||||||
|
[/#if]
|
||||||
|
[/#macro]
|
||||||
|
|
||||||
[#macro values vals][#if vals?is_enumerable][#list vals as val][#nested val][/#list][#else][#nested vals][/#if][/#macro]
|
[#macro values vals][#if vals?is_enumerable][#list vals as val][#nested val][/#list][#else][#nested vals][/#if][/#macro]
|
||||||
|
|
||||||
[#macro stringvalue value]<cmis:value>${value}</cmis:value>[/#macro]
|
[#macro stringvalue value]${value}[/#macro]
|
||||||
[#macro integervalue value]<cmis:value>${value?c}</cmis:value>[/#macro]
|
[#macro integervalue value]${value?c}[/#macro]
|
||||||
[#macro decimalvalue value]<cmis:value>${value?c}</cmis:value>[/#macro]
|
[#macro decimalvalue value]${value?c}[/#macro]
|
||||||
[#macro booleanvalue value]<cmis:value>${value?string}</cmis:value>[/#macro]
|
[#macro booleanvalue value]${value?string}[/#macro]
|
||||||
[#macro datetimevalue value]<cmis:value>${xmldate(value)}</cmis:value>[/#macro]
|
[#macro datetimevalue value]${xmldate(value)}[/#macro]
|
||||||
[#macro urivalue value]<cmis:value>${value}</cmis:value>[/#macro]
|
[#macro urivalue value]${value}[/#macro]
|
||||||
[#macro idvalue value]<cmis:value>${value}</cmis:value>[/#macro]
|
[#macro idvalue value]${value}[/#macro]
|
||||||
|
|
||||||
|
|
||||||
[#-- --]
|
[#-- --]
|
||||||
@@ -527,37 +575,37 @@
|
|||||||
[#if type == "STRING"]
|
[#if type == "STRING"]
|
||||||
<cmis:choiceString cmis:key="${choice.name}">
|
<cmis:choiceString cmis:key="${choice.name}">
|
||||||
[@cmisChoices choice.children type/]
|
[@cmisChoices choice.children type/]
|
||||||
[@stringvalue choice.value/]
|
<cmis:value>[@stringvalue choice.value/]</cmis:value>
|
||||||
</cmis:choiceString>
|
</cmis:choiceString>
|
||||||
[#elseif type == "INTEGER"]
|
[#elseif type == "INTEGER"]
|
||||||
<cmis:choiceInteger cmis:key="${choice.name}">
|
<cmis:choiceInteger cmis:key="${choice.name}">
|
||||||
[@cmisChoices choice.children type/]
|
[@cmisChoices choice.children type/]
|
||||||
[@stringvalue choice.value/]
|
<cmis:value>[@stringvalue choice.value/]</cmis:value>
|
||||||
</cmis:choiceInteger>
|
</cmis:choiceInteger>
|
||||||
[#elseif type == "DECIMAL"]
|
[#elseif type == "DECIMAL"]
|
||||||
<cmis:choiceDecimal cmis:key="${choice.name}">
|
<cmis:choiceDecimal cmis:key="${choice.name}">
|
||||||
[@cmisChoices choice.children type/]
|
[@cmisChoices choice.children type/]
|
||||||
[@stringvalue choice.value/]
|
<cmis:value>[@stringvalue choice.value/]</cmis:value>
|
||||||
</cmis:choiceDecimal>
|
</cmis:choiceDecimal>
|
||||||
[#elseif type == "BOOLEAN"]
|
[#elseif type == "BOOLEAN"]
|
||||||
<cmis:choiceBoolean cmis:key="${choice.name}">
|
<cmis:choiceBoolean cmis:key="${choice.name}">
|
||||||
[@cmisChoices choice.children type/]
|
[@cmisChoices choice.children type/]
|
||||||
[@stringvalue choice.value/]
|
<cmis:value>[@stringvalue choice.value/]</cmis:value>
|
||||||
</cmis:choiceBoolean>
|
</cmis:choiceBoolean>
|
||||||
[#elseif type == "DATETIME"]
|
[#elseif type == "DATETIME"]
|
||||||
<cmis:choiceDateTime cmis:key="${choice.name}">
|
<cmis:choiceDateTime cmis:key="${choice.name}">
|
||||||
[@cmisChoices choice.children type/]
|
[@cmisChoices choice.children type/]
|
||||||
[@stringvalue choice.value/]
|
<cmis:value>[@stringvalue choice.value/]</cmis:value>
|
||||||
</cmis:choiceDateTime>
|
</cmis:choiceDateTime>
|
||||||
[#elseif type == "URI"]
|
[#elseif type == "URI"]
|
||||||
<cmis:choiceUri cmis:key="${choice.name}">
|
<cmis:choiceUri cmis:key="${choice.name}">
|
||||||
[@cmisChoices choice.children type/]
|
[@cmisChoices choice.children type/]
|
||||||
[@stringvalue choice.value/]
|
<cmis:value>[@stringvalue choice.value/]</cmis:value>
|
||||||
</cmis:choiceUri>
|
</cmis:choiceUri>
|
||||||
[#elseif type == "ID"]
|
[#elseif type == "ID"]
|
||||||
<cmis:choiceId cmis:key="${choice.name}">
|
<cmis:choiceId cmis:key="${choice.name}">
|
||||||
[@cmisChoices choice.children type/]
|
[@cmisChoices choice.children type/]
|
||||||
[@stringvalue choice.value/]
|
<cmis:value>[@stringvalue choice.value/]</cmis:value>
|
||||||
</cmis:choiceId>
|
</cmis:choiceId>
|
||||||
[#-- TODO: remaining property types --]
|
[#-- TODO: remaining property types --]
|
||||||
[/#if]
|
[/#if]
|
||||||
@@ -581,4 +629,13 @@
|
|||||||
[#macro contenturi node]${absurl(url.serviceContext)}/api/node/${node.nodeRef.storeRef.protocol}/${node.nodeRef.storeRef.identifier}/${node.nodeRef.id}/content[#if node.properties.name?? && node.properties.name?last_index_of(".") != -1]${encodeuri(node.properties.name?substring(node.properties.name?last_index_of(".")))}[/#if][/#macro]
|
[#macro contenturi node]${absurl(url.serviceContext)}/api/node/${node.nodeRef.storeRef.protocol}/${node.nodeRef.storeRef.identifier}/${node.nodeRef.id}/content[#if node.properties.name?? && node.properties.name?last_index_of(".") != -1]${encodeuri(node.properties.name?substring(node.properties.name?last_index_of(".")))}[/#if][/#macro]
|
||||||
|
|
||||||
[#-- Helper to render Alfresco service document uri --]
|
[#-- Helper to render Alfresco service document uri --]
|
||||||
[#macro serviceuri]${absurl(url.serviceContext)}/api/repository[/#macro]
|
[#macro serviceuri]${absurl(url.serviceContext)}/api/repository[/#macro]
|
||||||
|
|
||||||
|
[#-- Helper to render Node Ref --]
|
||||||
|
[#macro noderef node]${node.nodeRef.storeRef.protocol}/${node.nodeRef.storeRef.identifier}/${node.nodeRef.id}[/#macro]
|
||||||
|
|
||||||
|
[#-- Helper to render Alfresco Node uri --]
|
||||||
|
[#macro nodeuri node]/api/node/[@noderef node/][/#macro]
|
||||||
|
|
||||||
|
[#-- Helper to render Alfresco Assoc uri --]
|
||||||
|
[#macro assocuri assoc]/api/rel/[@noderef assoc.source/]/type/${cmistype(assoc).typeId.id!"undefined"}/target/[@noderef assoc.target/][/#macro]
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
//
|
//
|
||||||
// Create Alfresco Node from Atom Entry
|
// Create Alfresco Node from Atom Entry
|
||||||
//
|
//
|
||||||
@@ -199,6 +198,57 @@ function updateNode(node, entry, exclude, validator)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Create Alfresco Association from Atom Entry
|
||||||
|
//
|
||||||
|
// @param source source node
|
||||||
|
// @param entry atom entry
|
||||||
|
// @return created association (or null, in case of error)
|
||||||
|
//
|
||||||
|
function createAssociation(source, entry)
|
||||||
|
{
|
||||||
|
var object = entry.getExtension(atom.names.cmis_object);
|
||||||
|
var typeId = (object !== null) ? object.objectTypeId.nativeValue : null;
|
||||||
|
|
||||||
|
// locate relationship type definition
|
||||||
|
// TODO: check this against spec - default to Relationship, if not specified
|
||||||
|
var type = cmis.queryType(typeId === null ? "relationship" : typeId);
|
||||||
|
if (type === null)
|
||||||
|
{
|
||||||
|
status.setCode(400, "CMIS object type " + typeId + " not understood");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (type.typeId.baseTypeId.id != "relationship")
|
||||||
|
{
|
||||||
|
status.setCode(400, "CMIS object type " + typeId + " is not a relationship type");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!type.creatable)
|
||||||
|
{
|
||||||
|
status.setCode(400, "Relationship type " + typeId + " is not creatable");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// locate target
|
||||||
|
var targetId = (object !== null) ? object.targetId.nativeValue : null;
|
||||||
|
if (targetId === null)
|
||||||
|
{
|
||||||
|
status.setCode(400, "Target Id has not been specified");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var target = search.findNode(targetId);
|
||||||
|
if (target === null)
|
||||||
|
{
|
||||||
|
status.setCode(400, "Target Id " + targetId + " does not refer to known item");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create association
|
||||||
|
var assoc = source.createAssociation(target, type.typeId.QName.toString());
|
||||||
|
return assoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// callback for validating property update for patch
|
// callback for validating property update for patch
|
||||||
// return null => update not allowed, abort update
|
// return null => update not allowed, abort update
|
||||||
// true => update allowed
|
// true => update allowed
|
||||||
|
@@ -0,0 +1,6 @@
|
|||||||
|
//
|
||||||
|
// Constants
|
||||||
|
//
|
||||||
|
|
||||||
|
// CMIS Enums
|
||||||
|
CMISRelationshipDirectionEnum = Packages.org.alfresco.cmis.CMISRelationshipDirectionEnum;
|
@@ -0,0 +1,11 @@
|
|||||||
|
[#ftl]
|
||||||
|
[#import "/org/alfresco/cmis/ns.lib.atom.ftl" as nsLib/]
|
||||||
|
[#import "/org/alfresco/cmis/atomentry.lib.atom.ftl" as entryLib/]
|
||||||
|
[#compress]
|
||||||
|
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
[#assign namespace][@nsLib.entryNS/][/#assign]
|
||||||
|
|
||||||
|
[@entryLib.assoc assoc=assoc propfilter=filter includeallowableactions=includeAllowableActions ns=namespace/]
|
||||||
|
|
||||||
|
[/#compress]
|
@@ -0,0 +1,9 @@
|
|||||||
|
<webscript>
|
||||||
|
<shortname>Retrieve relationship (getProperties)</shortname>
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
<url>/api/rel/{store_type}/{store_id}/{id}/type/{rel_type}/target/{target_store_type}/{target_store_id}/{target_id}</url>
|
||||||
|
<authentication>guest</authentication>
|
||||||
|
<format default="atomentry">argument</format>
|
||||||
|
<family>CMIS</family>
|
||||||
|
</webscript>
|
@@ -0,0 +1,28 @@
|
|||||||
|
script:
|
||||||
|
{
|
||||||
|
// relationship type
|
||||||
|
var relType = url.templateArgs.rel_type;
|
||||||
|
model.relTypeDef = cmis.queryType(relType);
|
||||||
|
if (model.relTypeDef === null)
|
||||||
|
{
|
||||||
|
status.setCode(400, "Relationship type " + relType + " unknown");
|
||||||
|
break script;
|
||||||
|
}
|
||||||
|
if (model.relTypeDef.baseType.typeId != "relationship")
|
||||||
|
{
|
||||||
|
status.setCode(400, "Type + " + relType + " is not a relationship type");
|
||||||
|
break script;
|
||||||
|
}
|
||||||
|
|
||||||
|
// source and target
|
||||||
|
var source = [url.templateArgs.store_type, url.templateArgs.store_id, url.templateArgs.id];
|
||||||
|
var target = [url.templateArgs.target_store_type, url.templateArgs.target_store_id, url.templateArgs.target_id];
|
||||||
|
|
||||||
|
// locate association
|
||||||
|
model.assoc = cmis.findRelationship(model.relTypeDef, source, target);
|
||||||
|
if (model.assoc === null)
|
||||||
|
{
|
||||||
|
status.setCode(404, "Assoc " + source.join("/") + "/" + relType + "/" + target.join("/") + " not found");
|
||||||
|
break script;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,24 @@
|
|||||||
|
[#ftl]
|
||||||
|
[#import "/org/alfresco/cmis/ns.lib.atom.ftl" as nsLib/]
|
||||||
|
[#import "/org/alfresco/cmis/atomfeed.lib.atom.ftl" as feedLib/]
|
||||||
|
[#import "/org/alfresco/cmis/atomentry.lib.atom.ftl" as entryLib/]
|
||||||
|
[#import "/org/alfresco/paging.lib.atom.ftl" as pagingLib/]
|
||||||
|
[#compress]
|
||||||
|
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<feed [@nsLib.feedNS/]>
|
||||||
|
|
||||||
|
[@feedLib.node node "relationships"]
|
||||||
|
[@pagingLib.links cursor/]
|
||||||
|
[/@feedLib.node]
|
||||||
|
|
||||||
|
[#list results as assoc]
|
||||||
|
[@entryLib.assoc assoc=assoc propfilter=filter includeallowableactions=includeAllowableActions/]
|
||||||
|
[/#list]
|
||||||
|
|
||||||
|
[@feedLib.hasMore cursor/]
|
||||||
|
[@pagingLib.opensearch cursor/]
|
||||||
|
|
||||||
|
</feed>
|
||||||
|
|
||||||
|
[/#compress]
|
@@ -0,0 +1,10 @@
|
|||||||
|
<webscript>
|
||||||
|
<shortname>Retrieve list of relationships (getRelationships)</shortname>
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
<url>/api/node/{store_type}/{store_id}/{id}/rels?filter={filter?}&relationshipType={relationshipType?}&includeSubRelationshipTypes={includeSubRelationshipTypes?}&direction={direction?}&skipCount={skipCount?}&maxItems={maxItems?}&includeAllowableActions={includeAllowableActions?}</url>
|
||||||
|
<url>/api/path/{store_type}/{store_id}/{id}/rels?filter={filter?}&relationshipType={relationshipType?}&includeSubRelationshipTypes={includeSubRelationshipTypes?}&direction={direction?}&skipCount={skipCount?}&maxItems={maxItems?}&includeAllowableActions={includeAllowableActions?}</url>
|
||||||
|
<authentication>guest</authentication>
|
||||||
|
<format default="atomfeed">argument</format>
|
||||||
|
<family>CMIS</family>
|
||||||
|
</webscript>
|
@@ -0,0 +1,59 @@
|
|||||||
|
<import resource="classpath:alfresco/templates/webscripts/org/alfresco/cmis/constants.lib.js">
|
||||||
|
|
||||||
|
script:
|
||||||
|
{
|
||||||
|
// locate node
|
||||||
|
var pathSegments = url.match.split("/");
|
||||||
|
var reference = [ url.templateArgs.store_type, url.templateArgs.store_id ].concat(url.templateArgs.id.split("/"));
|
||||||
|
model.node = cmis.findNode(pathSegments[2], reference);
|
||||||
|
if (model.node === null)
|
||||||
|
{
|
||||||
|
status.setCode(404, "Repository " + pathSegments[2] + " " + reference.join("/") + " not found");
|
||||||
|
break script;
|
||||||
|
}
|
||||||
|
|
||||||
|
// property filter
|
||||||
|
model.filter = args[cmis.ARG_FILTER];
|
||||||
|
if (model.filter === null)
|
||||||
|
{
|
||||||
|
model.filter = "*";
|
||||||
|
}
|
||||||
|
|
||||||
|
// relationship type
|
||||||
|
var relType = args[cmis.ARG_RELATIONSHIP_TYPE];
|
||||||
|
if (relType != null)
|
||||||
|
{
|
||||||
|
model.relTypeDef = cmis.queryType(relType);
|
||||||
|
if (model.relTypeDef === null)
|
||||||
|
{
|
||||||
|
status.setCode(400, "Relationship type " + relType + " unknown");
|
||||||
|
break script;
|
||||||
|
}
|
||||||
|
if (model.relTypeDef.baseType.typeId != "relationship")
|
||||||
|
{
|
||||||
|
status.setCode(400, "Type + " + relType + " is not a relationship type");
|
||||||
|
break script;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// include sub relationship types
|
||||||
|
model.includeSubRelationshipTypes = args[cmis.ARG_INCLUDE_SUB_RELATIONSHIP_TYPES] == "true" ? true : false;
|
||||||
|
|
||||||
|
// direction
|
||||||
|
var direction = args[cmis.ARG_DIRECTION];
|
||||||
|
if (direction !== null && !CMISRelationshipDirectionEnum.FACTORY.validLabel(direction))
|
||||||
|
{
|
||||||
|
status.setCode(400, "Direction " + direction + " unknown");
|
||||||
|
break script;
|
||||||
|
}
|
||||||
|
model.direction = CMISRelationshipDirectionEnum.FACTORY.toEnum(direction);
|
||||||
|
|
||||||
|
// include allowable actions
|
||||||
|
model.includeAllowableActions = args[cmis.ARG_INCLUDE_ALLOWABLE_ACTIONS] == "true" ? true : false;
|
||||||
|
|
||||||
|
// retrieve relationships
|
||||||
|
var page = paging.createPageOrWindow(args);
|
||||||
|
var paged = cmis.queryRelationships(model.node, model.relDefType, model.includeSubRelationshipTypes, model.direction, page);
|
||||||
|
model.results = paged.results;
|
||||||
|
model.cursor = paged.cursor;
|
||||||
|
}
|
@@ -0,0 +1,40 @@
|
|||||||
|
<import resource="classpath:alfresco/templates/webscripts/org/alfresco/cmis/atomentry.lib.js">
|
||||||
|
|
||||||
|
script:
|
||||||
|
{
|
||||||
|
// ensure atom entry is posted
|
||||||
|
if (entry === null)
|
||||||
|
{
|
||||||
|
status.code = 400;
|
||||||
|
status.message = "Expected atom entry";
|
||||||
|
status.redirect = true;
|
||||||
|
break script;
|
||||||
|
}
|
||||||
|
|
||||||
|
// locate source node
|
||||||
|
var pathSegments = url.match.split("/");
|
||||||
|
var reference = [ url.templateArgs.store_type, url.templateArgs.store_id ].concat(url.templateArgs.id.split("/"));
|
||||||
|
model.source = cmis.findNode(pathSegments[2], reference);
|
||||||
|
if (model.source === null)
|
||||||
|
{
|
||||||
|
status.code = 404;
|
||||||
|
status.message = "Repository " + pathSegments[2] + " " + reference.join("/") + " not found";
|
||||||
|
status.redirect = true;
|
||||||
|
break script;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create
|
||||||
|
var assoc = createAssociation(model.source, entry);
|
||||||
|
if (assoc == null)
|
||||||
|
{
|
||||||
|
break script;
|
||||||
|
}
|
||||||
|
|
||||||
|
// success
|
||||||
|
model.assoc = assoc;
|
||||||
|
// TODO: set Content-Location
|
||||||
|
status.code = 201;
|
||||||
|
// TODO: complete url mapping
|
||||||
|
status.location = url.server + url.serviceContext + "/api/rel/" + model.source.nodeRef.storeRef.protocol + "/" + model.source.nodeRef.storeRef.identifier + "/" + model.source.nodeRef.id;
|
||||||
|
status.redirect = true;
|
||||||
|
}
|
@@ -0,0 +1,11 @@
|
|||||||
|
[#ftl]
|
||||||
|
[#import "/org/alfresco/cmis/ns.lib.atom.ftl" as nsLib/]
|
||||||
|
[#import "/org/alfresco/cmis/atomentry.lib.atom.ftl" as entryLib/]
|
||||||
|
[#compress]
|
||||||
|
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
[#assign namespace][@nsLib.entryNS/][/#assign]
|
||||||
|
|
||||||
|
[@entryLib.assoc assoc=assoc propfilter=filter includeallowableactions=includeAllowableActions ns=namespace/]
|
||||||
|
|
||||||
|
[/#compress]
|
@@ -0,0 +1,10 @@
|
|||||||
|
<webscript>
|
||||||
|
<shortname>Create relationship (createRelationship)</shortname>
|
||||||
|
<description>
|
||||||
|
</description>
|
||||||
|
<url>/api/node/{store_type}/{store_id}/{id}/rels</url>
|
||||||
|
<url>/api/path/{store_type}/{store_id}/{id}/rels</url>
|
||||||
|
<authentication>guest</authentication>
|
||||||
|
<format default="atomentry">argument</format>
|
||||||
|
<family>CMIS</family>
|
||||||
|
</webscript>
|
@@ -17,13 +17,13 @@ script:
|
|||||||
{
|
{
|
||||||
// query a specific type and its descendants
|
// query a specific type and its descendants
|
||||||
var typedef = cmis.queryType(typeId);
|
var typedef = cmis.queryType(typeId);
|
||||||
if (typedef === null)
|
if (typedef === null)
|
||||||
{
|
{
|
||||||
status.code = 404;
|
status.code = 404;
|
||||||
status.message = "Type " + typeId + " not found";
|
status.message = "Type " + typeId + " not found";
|
||||||
status.redirect = true;
|
status.redirect = true;
|
||||||
break script;
|
break script;
|
||||||
}
|
}
|
||||||
var paged = cmis.queryTypeHierarchy(typedef, true, page);
|
var paged = cmis.queryTypeHierarchy(typedef, true, page);
|
||||||
model.results = paged.results;
|
model.results = paged.results;
|
||||||
model.cursor = paged.cursor;
|
model.cursor = paged.cursor;
|
||||||
|
@@ -27,6 +27,7 @@ package org.alfresco.repo.cmis.rest;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.alfresco.cmis.CMISServices;
|
import org.alfresco.cmis.CMISServices;
|
||||||
|
import org.alfresco.repo.template.TemplateAssociation;
|
||||||
import org.alfresco.repo.template.TemplateNode;
|
import org.alfresco.repo.template.TemplateNode;
|
||||||
|
|
||||||
import freemarker.ext.beans.BeanModel;
|
import freemarker.ext.beans.BeanModel;
|
||||||
@@ -76,14 +77,6 @@ public final class CMISPropertyValueMethod implements TemplateMethodModelEx
|
|||||||
Object arg0 = args.get(0);
|
Object arg0 = args.get(0);
|
||||||
if (arg0 instanceof BeanModel)
|
if (arg0 instanceof BeanModel)
|
||||||
{
|
{
|
||||||
// extract node
|
|
||||||
TemplateNode node = null;
|
|
||||||
Object wrapped = ((BeanModel)arg0).getWrappedObject();
|
|
||||||
if (wrapped != null && wrapped instanceof TemplateNode)
|
|
||||||
{
|
|
||||||
node = (TemplateNode)wrapped;
|
|
||||||
}
|
|
||||||
|
|
||||||
// extract property name
|
// extract property name
|
||||||
String propertyName = null;
|
String propertyName = null;
|
||||||
Object arg1 = args.get(1);
|
Object arg1 = args.get(1);
|
||||||
@@ -92,8 +85,18 @@ public final class CMISPropertyValueMethod implements TemplateMethodModelEx
|
|||||||
propertyName = ((TemplateScalarModel)arg1).getAsString();
|
propertyName = ((TemplateScalarModel)arg1).getAsString();
|
||||||
}
|
}
|
||||||
|
|
||||||
// retrieve property value
|
// extract node
|
||||||
result = cmisService.getProperty(node.getNodeRef(), propertyName);
|
Object wrapped = ((BeanModel)arg0).getWrappedObject();
|
||||||
|
if (wrapped != null && wrapped instanceof TemplateNode)
|
||||||
|
{
|
||||||
|
// retrieve property value from node
|
||||||
|
result = cmisService.getProperty(((TemplateNode)wrapped).getNodeRef(), propertyName);
|
||||||
|
}
|
||||||
|
else if (wrapped != null && wrapped instanceof TemplateAssociation)
|
||||||
|
{
|
||||||
|
// retrieve property value from node
|
||||||
|
result = cmisService.getProperty(((TemplateAssociation)wrapped).getAssociationRef(), propertyName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -33,11 +33,13 @@ import org.alfresco.cmis.CMISPropertyDefinition;
|
|||||||
import org.alfresco.cmis.CMISQueryEnum;
|
import org.alfresco.cmis.CMISQueryEnum;
|
||||||
import org.alfresco.cmis.CMISQueryOptions;
|
import org.alfresco.cmis.CMISQueryOptions;
|
||||||
import org.alfresco.cmis.CMISQueryService;
|
import org.alfresco.cmis.CMISQueryService;
|
||||||
|
import org.alfresco.cmis.CMISRelationshipDirectionEnum;
|
||||||
import org.alfresco.cmis.CMISResultSet;
|
import org.alfresco.cmis.CMISResultSet;
|
||||||
import org.alfresco.cmis.CMISServices;
|
import org.alfresco.cmis.CMISServices;
|
||||||
import org.alfresco.cmis.CMISTypeDefinition;
|
import org.alfresco.cmis.CMISTypeDefinition;
|
||||||
import org.alfresco.cmis.CMISTypesFilterEnum;
|
import org.alfresco.cmis.CMISTypesFilterEnum;
|
||||||
import org.alfresco.error.AlfrescoRuntimeException;
|
import org.alfresco.error.AlfrescoRuntimeException;
|
||||||
|
import org.alfresco.repo.jscript.Association;
|
||||||
import org.alfresco.repo.jscript.BaseScopableProcessorExtension;
|
import org.alfresco.repo.jscript.BaseScopableProcessorExtension;
|
||||||
import org.alfresco.repo.jscript.ScriptNode;
|
import org.alfresco.repo.jscript.ScriptNode;
|
||||||
import org.alfresco.repo.model.Repository;
|
import org.alfresco.repo.model.Repository;
|
||||||
@@ -46,6 +48,7 @@ import org.alfresco.repo.web.util.paging.Page;
|
|||||||
import org.alfresco.repo.web.util.paging.PagedResults;
|
import org.alfresco.repo.web.util.paging.PagedResults;
|
||||||
import org.alfresco.repo.web.util.paging.Paging;
|
import org.alfresco.repo.web.util.paging.Paging;
|
||||||
import org.alfresco.service.ServiceRegistry;
|
import org.alfresco.service.ServiceRegistry;
|
||||||
|
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.namespace.QName;
|
import org.alfresco.service.namespace.QName;
|
||||||
|
|
||||||
@@ -73,7 +76,7 @@ public class CMISScript extends BaseScopableProcessorExtension
|
|||||||
public static final String ARG_INCLUDE_ALLOWABLE_ACTIONS = "includeAllowableActions";
|
public static final String ARG_INCLUDE_ALLOWABLE_ACTIONS = "includeAllowableActions";
|
||||||
public static final String ARG_INCLUDE_PROPERTY_DEFINITIONS = "includePropertyDefinitions";
|
public static final String ARG_INCLUDE_PROPERTY_DEFINITIONS = "includePropertyDefinitions";
|
||||||
public static final String ARG_INCLUDE_RELATIONSHIPS = "includeRelationships";
|
public static final String ARG_INCLUDE_RELATIONSHIPS = "includeRelationships";
|
||||||
public static final String ARG_INCLUDE_SUB_RELATIONSHIP_TYPES = "includeSubrelationshipTypes";
|
public static final String ARG_INCLUDE_SUB_RELATIONSHIP_TYPES = "includeSubRelationshipTypes";
|
||||||
public static final String ARG_LENGTH = "length";
|
public static final String ARG_LENGTH = "length";
|
||||||
public static final String ARG_MAJOR = "major";
|
public static final String ARG_MAJOR = "major";
|
||||||
public static final String ARG_MAJOR_VERSION = "majorVersion";
|
public static final String ARG_MAJOR_VERSION = "majorVersion";
|
||||||
@@ -197,7 +200,7 @@ public class CMISScript extends BaseScopableProcessorExtension
|
|||||||
*/
|
*/
|
||||||
public String getDefaultTypesFilter()
|
public String getDefaultTypesFilter()
|
||||||
{
|
{
|
||||||
return CMISTypesFilterEnum.FACTORY.defaultLabel();
|
return CMISTypesFilterEnum.FACTORY.getDefaultLabel();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -242,6 +245,25 @@ public class CMISScript extends BaseScopableProcessorExtension
|
|||||||
}
|
}
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds an Association
|
||||||
|
*
|
||||||
|
* @param sourceType
|
||||||
|
* @param source
|
||||||
|
* @param relDef
|
||||||
|
* @param targetType
|
||||||
|
* @param target
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Association findRelationship(CMISTypeDefinition relDef, String[] sourceRef, String[] targetRef)
|
||||||
|
{
|
||||||
|
NodeRef source = new NodeRef(sourceRef[0], sourceRef[1], sourceRef[2]);
|
||||||
|
NodeRef target = new NodeRef(targetRef[0], targetRef[1], targetRef[2]);
|
||||||
|
AssociationRef assocRef = cmisService.getRelationship(relDef, source, target);
|
||||||
|
return (assocRef == null) ? null : new Association(services, assocRef);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query for node children
|
* Query for node children
|
||||||
@@ -267,6 +289,31 @@ public class CMISScript extends BaseScopableProcessorExtension
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query for node relationships
|
||||||
|
*
|
||||||
|
* @param node
|
||||||
|
* @param relDef
|
||||||
|
* @param includeSubTypes
|
||||||
|
* @param direction
|
||||||
|
* @param page
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public PagedResults queryRelationships(ScriptNode node, CMISTypeDefinition relDef, boolean includeSubTypes, CMISRelationshipDirectionEnum direction, Page page)
|
||||||
|
{
|
||||||
|
AssociationRef[] relationships = cmisService.getRelationships(node.getNodeRef(), relDef, includeSubTypes, direction);
|
||||||
|
|
||||||
|
Cursor cursor = paging.createCursor(relationships.length, page);
|
||||||
|
Association[] assocs = new Association[cursor.getRowCount()];
|
||||||
|
for (int i = cursor.getStartRow(); i <= cursor.getEndRow(); i++)
|
||||||
|
{
|
||||||
|
assocs[i - cursor.getStartRow()] = new Association(services, relationships[i], getScope());
|
||||||
|
}
|
||||||
|
|
||||||
|
PagedResults results = paging.createPagedResults(assocs, cursor);
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query for items checked-out to user
|
* Query for items checked-out to user
|
||||||
*
|
*
|
||||||
|
@@ -27,7 +27,10 @@ package org.alfresco.repo.cmis.rest;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.alfresco.cmis.CMISDictionaryService;
|
import org.alfresco.cmis.CMISDictionaryService;
|
||||||
|
import org.alfresco.cmis.CMISScope;
|
||||||
import org.alfresco.cmis.CMISTypeDefinition;
|
import org.alfresco.cmis.CMISTypeDefinition;
|
||||||
|
import org.alfresco.cmis.mapping.CMISMapping;
|
||||||
|
import org.alfresco.repo.template.TemplateAssociation;
|
||||||
import org.alfresco.repo.template.TemplateNode;
|
import org.alfresco.repo.template.TemplateNode;
|
||||||
import org.alfresco.service.namespace.QName;
|
import org.alfresco.service.namespace.QName;
|
||||||
|
|
||||||
@@ -47,8 +50,10 @@ import freemarker.template.TemplateModelException;
|
|||||||
*/
|
*/
|
||||||
public class CMISTypeDefinitionMethod implements TemplateMethodModelEx
|
public class CMISTypeDefinitionMethod implements TemplateMethodModelEx
|
||||||
{
|
{
|
||||||
|
private static CMISScope[] EMPTY_SCOPES = new CMISScope[] {};
|
||||||
private CMISDictionaryService dictionaryService;
|
private CMISDictionaryService dictionaryService;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct
|
* Construct
|
||||||
*/
|
*/
|
||||||
@@ -71,6 +76,7 @@ public class CMISTypeDefinitionMethod implements TemplateMethodModelEx
|
|||||||
if (arg0 instanceof BeanModel)
|
if (arg0 instanceof BeanModel)
|
||||||
{
|
{
|
||||||
// extract node type qname
|
// extract node type qname
|
||||||
|
CMISScope[] matchingScopes = EMPTY_SCOPES;
|
||||||
QName nodeType = null;
|
QName nodeType = null;
|
||||||
Object wrapped = ((BeanModel)arg0).getWrappedObject();
|
Object wrapped = ((BeanModel)arg0).getWrappedObject();
|
||||||
if (wrapped != null)
|
if (wrapped != null)
|
||||||
@@ -79,6 +85,11 @@ public class CMISTypeDefinitionMethod implements TemplateMethodModelEx
|
|||||||
{
|
{
|
||||||
nodeType = ((TemplateNode)wrapped).getType();
|
nodeType = ((TemplateNode)wrapped).getType();
|
||||||
}
|
}
|
||||||
|
else if (wrapped instanceof TemplateAssociation)
|
||||||
|
{
|
||||||
|
nodeType = ((TemplateAssociation)wrapped).getTypeQName();
|
||||||
|
matchingScopes = new CMISScope[] { CMISScope.RELATIONSHIP };
|
||||||
|
}
|
||||||
else if (wrapped instanceof QName)
|
else if (wrapped instanceof QName)
|
||||||
{
|
{
|
||||||
nodeType = (QName)wrapped;
|
nodeType = (QName)wrapped;
|
||||||
@@ -88,7 +99,7 @@ public class CMISTypeDefinitionMethod implements TemplateMethodModelEx
|
|||||||
// convert to CMIS type
|
// convert to CMIS type
|
||||||
if (nodeType != null)
|
if (nodeType != null)
|
||||||
{
|
{
|
||||||
result = dictionaryService.findTypeForClass(nodeType);
|
result = dictionaryService.findTypeForClass(nodeType, matchingScopes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -501,6 +501,31 @@ public class BaseCMISWebScriptTest extends BaseWebScriptTest
|
|||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected Entry createRelationship(IRI parent, String type, String targetId)
|
||||||
|
throws Exception
|
||||||
|
{
|
||||||
|
return createRelationship(parent, type, targetId, "/org/alfresco/repo/cmis/rest/test/createrelationship.atomentry.xml");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Entry createRelationship(IRI parent, String type, String targetId, String atomEntryFile)
|
||||||
|
throws Exception
|
||||||
|
{
|
||||||
|
String createFile = loadString(atomEntryFile);
|
||||||
|
createFile = createFile.replace("${RELTYPE}", type);
|
||||||
|
createFile = createFile.replace("${TARGETID}", targetId);
|
||||||
|
Response res = sendRequest(new PostRequest(parent.toString(), createFile, Format.ATOMENTRY.mimetype()), 201, getAtomValidator());
|
||||||
|
assertNotNull(res);
|
||||||
|
String xml = res.getContentAsString();
|
||||||
|
Entry entry = abdera.parseEntry(new StringReader(xml), null);
|
||||||
|
assertNotNull(entry);
|
||||||
|
CMISObject object = entry.getExtension(CMISConstants.OBJECT);
|
||||||
|
assertEquals("relationship", object.getBaseType().getStringValue());
|
||||||
|
assertEquals(targetId, object.getTargetId().getStringValue());
|
||||||
|
String testFileHREF = (String)res.getHeader("Location");
|
||||||
|
assertNotNull(testFileHREF);
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// General Test Helpers
|
// General Test Helpers
|
||||||
//
|
//
|
||||||
|
@@ -25,12 +25,15 @@
|
|||||||
package org.alfresco.repo.cmis.rest.test;
|
package org.alfresco.repo.cmis.rest.test;
|
||||||
|
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.alfresco.abdera.ext.cmis.CMISConstants;
|
import org.alfresco.abdera.ext.cmis.CMISConstants;
|
||||||
import org.alfresco.abdera.ext.cmis.CMISObject;
|
import org.alfresco.abdera.ext.cmis.CMISObject;
|
||||||
import org.alfresco.abdera.ext.cmis.CMISProperties;
|
import org.alfresco.abdera.ext.cmis.CMISProperties;
|
||||||
import org.alfresco.abdera.ext.cmis.CMISProperty;
|
import org.alfresco.abdera.ext.cmis.CMISProperty;
|
||||||
|
import org.alfresco.repo.cmis.rest.CMISScript;
|
||||||
import org.alfresco.util.GUID;
|
import org.alfresco.util.GUID;
|
||||||
import org.alfresco.web.scripts.Format;
|
import org.alfresco.web.scripts.Format;
|
||||||
import org.alfresco.web.scripts.TestWebScriptServer.DeleteRequest;
|
import org.alfresco.web.scripts.TestWebScriptServer.DeleteRequest;
|
||||||
@@ -105,11 +108,11 @@ public class CMISCustomTypeTest extends BaseCMISWebScriptTest
|
|||||||
Feed children = getFeed(childrenLink.getHref());
|
Feed children = getFeed(childrenLink.getHref());
|
||||||
assertNotNull(children);
|
assertNotNull(children);
|
||||||
int entriesBefore = children.getEntries().size();
|
int entriesBefore = children.getEntries().size();
|
||||||
Entry folder = createDocument(children.getSelfLink().getHref(), "testCreateCustomDocument", "/org/alfresco/repo/cmis/rest/test/createcustomdocument.atomentry.xml");
|
Entry document = createDocument(children.getSelfLink().getHref(), "testCreateCustomDocument", "/org/alfresco/repo/cmis/rest/test/createcustomdocument.atomentry.xml");
|
||||||
Feed feedFolderAfter = getFeed(childrenLink.getHref());
|
Feed feedFolderAfter = getFeed(childrenLink.getHref());
|
||||||
int entriesAfter = feedFolderAfter.getEntries().size();
|
int entriesAfter = feedFolderAfter.getEntries().size();
|
||||||
assertEquals(entriesBefore +1, entriesAfter);
|
assertEquals(entriesBefore +1, entriesAfter);
|
||||||
Entry entry = feedFolderAfter.getEntry(folder.getId().toString());
|
Entry entry = feedFolderAfter.getEntry(document.getId().toString());
|
||||||
CMISObject object = entry.getExtension(CMISConstants.OBJECT);
|
CMISObject object = entry.getExtension(CMISConstants.OBJECT);
|
||||||
assertEquals("D/cmiscustom_document", object.getObjectTypeId().getStringValue());
|
assertEquals("D/cmiscustom_document", object.getObjectTypeId().getStringValue());
|
||||||
CMISProperty customProp = object.getProperties().find("cmiscustom_docprop_string");
|
CMISProperty customProp = object.getProperties().find("cmiscustom_docprop_string");
|
||||||
@@ -315,5 +318,91 @@ public class CMISCustomTypeTest extends BaseCMISWebScriptTest
|
|||||||
assertEquals(false, result2multiValues.get(1));
|
assertEquals(false, result2multiValues.get(1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testCreateRelationship()
|
||||||
|
throws Exception
|
||||||
|
{
|
||||||
|
Entry testFolder = createTestFolder("testCreateCustomRelationship");
|
||||||
|
Link childrenLink = testFolder.getLink(CMISConstants.REL_CHILDREN);
|
||||||
|
assertNotNull(childrenLink);
|
||||||
|
Feed children = getFeed(childrenLink.getHref());
|
||||||
|
assertNotNull(children);
|
||||||
|
Entry source = createDocument(children.getSelfLink().getHref(), "testSource", "/org/alfresco/repo/cmis/rest/test/createcustomdocument.atomentry.xml");
|
||||||
|
assertNotNull(source);
|
||||||
|
Entry target = createDocument(children.getSelfLink().getHref(), "testTarget", "/org/alfresco/repo/cmis/rest/test/createcustomdocument.atomentry.xml");
|
||||||
|
assertNotNull(target);
|
||||||
|
|
||||||
|
// retrieve relationships feed on source
|
||||||
|
Link relsLink = source.getLink(CMISConstants.REL_RELATIONSHIPS);
|
||||||
|
assertNotNull(relsLink);
|
||||||
|
Feed relsBefore = getFeed(relsLink.getHref());
|
||||||
|
assertNotNull(relsBefore);
|
||||||
|
assertEquals(0, relsBefore.getEntries().size());
|
||||||
|
|
||||||
|
// create relationship between source and target documents
|
||||||
|
CMISObject targetObject = target.getExtension(CMISConstants.OBJECT);
|
||||||
|
assertNotNull(targetObject);
|
||||||
|
String targetId = targetObject.getObjectId().getStringValue();
|
||||||
|
assertNotNull(targetId);
|
||||||
|
Entry rel = createRelationship(relsLink.getHref(), "R/cmiscustom_assoc", targetId);
|
||||||
|
assertNotNull(rel);
|
||||||
|
|
||||||
|
// check created relationship
|
||||||
|
CMISObject sourceObject = source.getExtension(CMISConstants.OBJECT);
|
||||||
|
assertNotNull(sourceObject);
|
||||||
|
String sourceId = sourceObject.getObjectId().getStringValue();
|
||||||
|
assertNotNull(sourceId);
|
||||||
|
CMISObject relObject = rel.getExtension(CMISConstants.OBJECT);
|
||||||
|
assertNotNull(relObject);
|
||||||
|
assertEquals("R/cmiscustom_assoc", relObject.getObjectTypeId().getStringValue());
|
||||||
|
assertEquals(sourceId, relObject.getSourceId().getStringValue());
|
||||||
|
assertEquals(targetId, relObject.getTargetId().getStringValue());
|
||||||
|
assertEquals(source.getSelfLink().getHref(), rel.getLink(CMISConstants.REL_SOURCE).getHref());
|
||||||
|
assertEquals(target.getSelfLink().getHref(), rel.getLink(CMISConstants.REL_TARGET).getHref());
|
||||||
|
|
||||||
|
// check relationships for created item
|
||||||
|
Map<String, String> args = new HashMap<String, String>();
|
||||||
|
args.put(CMISScript.ARG_INCLUDE_SUB_RELATIONSHIP_TYPES, "true");
|
||||||
|
Feed relsAfter = getFeed(relsLink.getHref(), args);
|
||||||
|
assertNotNull(relsAfter);
|
||||||
|
assertEquals(1, relsAfter.getEntries().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testGetRelationship()
|
||||||
|
throws Exception
|
||||||
|
{
|
||||||
|
Entry testFolder = createTestFolder("testGetCustomRelationship");
|
||||||
|
Link childrenLink = testFolder.getLink(CMISConstants.REL_CHILDREN);
|
||||||
|
assertNotNull(childrenLink);
|
||||||
|
Feed children = getFeed(childrenLink.getHref());
|
||||||
|
assertNotNull(children);
|
||||||
|
Entry source = createDocument(children.getSelfLink().getHref(), "testSource", "/org/alfresco/repo/cmis/rest/test/createcustomdocument.atomentry.xml");
|
||||||
|
assertNotNull(source);
|
||||||
|
Entry target = createDocument(children.getSelfLink().getHref(), "testTarget", "/org/alfresco/repo/cmis/rest/test/createcustomdocument.atomentry.xml");
|
||||||
|
assertNotNull(target);
|
||||||
|
|
||||||
|
// retrieve relationships feed on source
|
||||||
|
Link relsLink = source.getLink(CMISConstants.REL_RELATIONSHIPS);
|
||||||
|
assertNotNull(relsLink);
|
||||||
|
|
||||||
|
// create relationship between source and target documents
|
||||||
|
CMISObject targetObject = target.getExtension(CMISConstants.OBJECT);
|
||||||
|
assertNotNull(targetObject);
|
||||||
|
String targetId = targetObject.getObjectId().getStringValue();
|
||||||
|
assertNotNull(targetId);
|
||||||
|
Entry rel = createRelationship(relsLink.getHref(), "R/cmiscustom_assoc", targetId);
|
||||||
|
assertNotNull(rel);
|
||||||
|
|
||||||
|
// get created relationship
|
||||||
|
Entry relEntry = getEntry(rel.getSelfLink().getHref());
|
||||||
|
CMISObject relEntryObject = rel.getExtension(CMISConstants.OBJECT);
|
||||||
|
CMISObject relObject = rel.getExtension(CMISConstants.OBJECT);
|
||||||
|
assertNotNull(relObject);
|
||||||
|
assertEquals(relObject.getObjectTypeId().getStringValue(), relEntryObject.getObjectTypeId().getStringValue());
|
||||||
|
assertEquals(relObject.getSourceId().getStringValue(), relEntryObject.getSourceId().getStringValue());
|
||||||
|
assertEquals(relObject.getTargetId().getStringValue(), relEntryObject.getTargetId().getStringValue());
|
||||||
|
assertEquals(source.getSelfLink().getHref(), relEntry.getLink(CMISConstants.REL_SOURCE).getHref());
|
||||||
|
assertEquals(target.getSelfLink().getHref(), relEntry.getLink(CMISConstants.REL_TARGET).getHref());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -4,8 +4,8 @@
|
|||||||
<summary>${NAME} (summary)</summary>
|
<summary>${NAME} (summary)</summary>
|
||||||
<content type="text/html">${CONTENT}</content>
|
<content type="text/html">${CONTENT}</content>
|
||||||
<cmis:object>
|
<cmis:object>
|
||||||
<cmis:properties>
|
<cmis:properties>
|
||||||
<cmis:propertyString cmis:name="ObjectTypeId"><cmis:value>document</cmis:value></cmis:propertyString>
|
<cmis:propertyString cmis:name="ObjectTypeId"><cmis:value>document</cmis:value></cmis:propertyString>
|
||||||
</cmis:properties>
|
</cmis:properties>
|
||||||
</cmis:object>
|
</cmis:object>
|
||||||
</entry>
|
</entry>
|
||||||
|
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<entry xmlns="http://www.w3.org/2005/Atom" xmlns:cmis="http://docs.oasis-open.org/ns/cmis/core/200901">
|
||||||
|
<cmis:object>
|
||||||
|
<cmis:properties>
|
||||||
|
<cmis:propertyString cmis:name="ObjectTypeId"><cmis:value>${RELTYPE}</cmis:value></cmis:propertyString>
|
||||||
|
<cmis:propertyId cmis:name="TargetId"><cmis:value>${TARGETID}</cmis:value></cmis:propertyId>
|
||||||
|
</cmis:properties>
|
||||||
|
</cmis:object>
|
||||||
|
</entry>
|
@@ -208,6 +208,13 @@ public class CMISSchemaTest extends TestCase
|
|||||||
assertValidXML(xml, cmisValidator.getCMISAtomValidator());
|
assertValidXML(xml, cmisValidator.getCMISAtomValidator());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testAtomEntry()
|
||||||
|
throws Exception
|
||||||
|
{
|
||||||
|
String xml = getXML("example_atomentry.xml");
|
||||||
|
assertValidXML(xml, cmisValidator.getCMISAtomValidator());
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Missing from v0.61
|
// Missing from v0.61
|
||||||
//
|
//
|
||||||
|
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<ns3:entry xmlns:ns1="http://docs.oasis-open.org/ns/cmis/core/200901" xmlns:ns2="http://docs.oasis-open.org/ns/cmis/messaging/200901" xmlns:ns3="http://www.w3.org/2005/Atom" xmlns:ns4="http://www.w3.org/2007/app">
|
||||||
|
<ns3:author>
|
||||||
|
<ns3:name>Al Brown</ns3:name>
|
||||||
|
<ns3:uri>http://www.ibm.com/</ns3:uri>
|
||||||
|
<ns3:email>albertcbrown@us.ibm.com</ns3:email>
|
||||||
|
</ns3:author>
|
||||||
|
<ns3:content src="http://cmisexample.oasis-open.org/rep1/70cfe57f-5d59-4293-9cbc-842107117d65"/>
|
||||||
|
<ns3:id>urn:uuid:70cfe57f-5d59-4293-9cbc-842107117d65</ns3:id>
|
||||||
|
<ns3:link rel="self" href="http://cmisexample.oasis-open.org/rep1/70cfe57f-5d59-4293-9cbc-842107117d65"/>
|
||||||
|
<ns3:link rel="edit" href="http://cmisexample.oasis-open.org/rep1/70cfe57f-5d59-4293-9cbc-842107117d65"/>
|
||||||
|
<ns3:link type="application/cmis+xml;type=allowableActions" rel="allowableactions" href="http://cmisexample.oasis-open.org/rep1/70cfe57f-5d59-4293-9cbc-842107117d65/allowableactions"/>
|
||||||
|
<ns3:link type="application/atom+xml;type=entry" rel="type" href="http://cmisexample.oasis-open.org/rep1/70cfe57f-5d59-4293-9cbc-842107117d65/type"/>
|
||||||
|
<ns3:link rel="edit-media" href="http://cmisexample.oasis-open.org/rep1/70cfe57f-5d59-4293-9cbc-842107117d65/edit-media"/>
|
||||||
|
<ns3:link rel="alternate" href="http://cmisexample.oasis-open.org/rep1/70cfe57f-5d59-4293-9cbc-842107117d65/alternate"/>
|
||||||
|
<ns3:link type="application/atom+xml;type=feed" rel="parents" href="http://cmisexample.oasis-open.org/rep1/70cfe57f-5d59-4293-9cbc-842107117d65/parents"/>
|
||||||
|
<ns3:link type="application/atom+xml;type=feed" rel="allversions" href="http://cmisexample.oasis-open.org/rep1/70cfe57f-5d59-4293-9cbc-842107117d65/allversions"/>
|
||||||
|
<ns3:link type="application/atom+xml;type=entry" rel="latestversion" href="http://cmisexample.oasis-open.org/rep1/70cfe57f-5d59-4293-9cbc-842107117d65/latestversions"/>
|
||||||
|
<ns3:link length="4123" type="text/plain" rel="stream" href="http://cmisexample.oasis-open.org/rep1/70cfe57f-5d59-4293-9cbc-842107117d65media"/>
|
||||||
|
<ns3:link type="application/atom+xml;type=feed" rel="relationships" href="http://cmisexample.oasis-open.org/rep1/70cfe57f-5d59-4293-9cbc-842107117d65/relationships"/>
|
||||||
|
<ns3:published>2009-04-17T13:50:58.656-07:00</ns3:published>
|
||||||
|
<ns3:summary type="html">HTML summary of Entry 70cfe57f-5d59-4293-9cbc-842107117d65</ns3:summary>
|
||||||
|
<ns3:title type="text">CMIS Example Document</ns3:title>
|
||||||
|
<ns3:updated>2009-04-17T13:50:58.656-07:00</ns3:updated>
|
||||||
|
<ns1:terminator xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
|
||||||
|
</ns3:entry>
|
@@ -42,6 +42,19 @@
|
|||||||
<multiple>true</multiple>
|
<multiple>true</multiple>
|
||||||
</property>
|
</property>
|
||||||
</properties>
|
</properties>
|
||||||
|
<associations>
|
||||||
|
<association name="cmiscustom:assoc">
|
||||||
|
<source>
|
||||||
|
<mandatory>false</mandatory>
|
||||||
|
<many>false</many>
|
||||||
|
</source>
|
||||||
|
<target>
|
||||||
|
<class>cm:content</class>
|
||||||
|
<mandatory>false</mandatory>
|
||||||
|
<many>true</many>
|
||||||
|
</target>
|
||||||
|
</association>
|
||||||
|
</associations>
|
||||||
</type>
|
</type>
|
||||||
</types>
|
</types>
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user