diff --git a/config/alfresco/templates/webscripts/org/alfresco/cmis/atomentry.lib.atom.ftl b/config/alfresco/templates/webscripts/org/alfresco/cmis/atomentry.lib.atom.ftl
index 8c8f7293a8..6b18d1c1d5 100644
--- a/config/alfresco/templates/webscripts/org/alfresco/cmis/atomentry.lib.atom.ftl
+++ b/config/alfresco/templates/webscripts/org/alfresco/cmis/atomentry.lib.atom.ftl
@@ -10,6 +10,19 @@
[/#macro]
+[#macro objectCMISProps object propfilter]
+
+ [#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]
+
+[/#macro]
+
[#-- --]
[#-- ATOM Entry for Document --]
@@ -30,7 +43,7 @@
${node.name}
${xmldate(node.properties.modified)}
-[@documentCMISProps node propfilter/]
+[@objectCMISProps node propfilter/]
[#if includeallowableactions][@allowableactions node/][/#if]
@@ -41,7 +54,7 @@
[#macro documentCMISLinks node]
-
+
[@linkstream node "stream"/]
@@ -49,18 +62,6 @@
[/#macro]
-[#macro documentCMISProps node propfilter]
-
- [#-- 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]
-
-[/#macro]
-
[#-- --]
[#-- ATOM Entry for Version --]
@@ -79,7 +80,7 @@
${node.name}
${xmldate(node.properties.modified)}
-[@documentCMISProps node propfilter/]
+[@objectCMISProps node propfilter/]
${xmldate(node.properties.modified)}
@@ -107,7 +108,7 @@
${node.name}
${xmldate(node.properties.modified)}
-[@documentCMISProps node propfilter/]
+[@objectCMISProps node propfilter/]
[#if includeallowableactions][@allowableactions node/][/#if]
@@ -136,7 +137,7 @@
${xmldate(node.properties.modified)}
[#-- recurse for depth greater than 1 --]
-[@folderCMISProps node propfilter/]
+[@objectCMISProps node propfilter/]
[#if includeallowableactions][@allowableactions node/][/#if]
[#if depth < maxdepth || depth == -1]
@@ -157,7 +158,7 @@
[#macro folderCMISLinks node]
-
+
[#if cmisproperty(node, "ParentId")?is_string]
[/#if]
@@ -167,16 +168,38 @@
[/#macro]
-[#macro folderCMISProps node propfilter]
-
- [#-- 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]
- [@filter propfilter propdef.propertyId.name][@prop propdef.propertyId.name node propdef.dataType/][/@filter]
- [/#list]
-
+[#-- --]
+[#-- ATOM Entry for Relationship --]
+[#-- --]
+
+[#macro assoc assoc propfilter="*" includeallowableactions=false ns=""]
+[@entry ns]
+${xmldate(date)} [#-- TODO: [@namedvalue "CreatedBy" assoc "STRING"/] --]
+[@namedvalue "ObjectId" assoc "ID"/] [#-- TODO: spec id, how to map? --]
+[@namedvalue "ObjectId" assoc "ID"/] [#-- TODO: id compliant --]
+
+
+[@assocCMISLinks assoc=assoc/]
+${xmldate(date)} [#-- TODO: [@namedvalue "CreationDate" assoc "DATETIME"/] --]
+[@namedvalue "ObjectId" assoc "ID"/] [#-- TODO: spec id, how to map? --]
+[@namedvalue "ObjectId" assoc "ID"/] [#-- TODO: spec id, how to map? --]
+${xmldate(date)} [#-- TODO: [@namedvalue "LastModificationDate" assoc "DATETIME"/] --]
+
+[@objectCMISProps assoc propfilter/]
+[#-- TODO: [#if includeallowableactions][@allowableactions node/][/#if] --]
+
+
+${xmldate(date)} [#-- TODO: [@namedvalue "LastModificationDate" assoc "DATETIME"/] --]
+[/@entry]
+[/#macro]
+
+[#macro assocCMISLinks assoc]
+[#-- TODO: --]
+
+
+
+
[/#macro]
@@ -239,8 +262,8 @@
[#if filter == "*" || filter?index_of(value) != -1 || filter?matches(value,'i')][#nested][/#if]
[/#macro]
-[#macro prop name node type]
-[#assign value=cmisproperty(node, name)/]
+[#macro prop name object type]
+[#assign value=cmisproperty(object, name)/]
[#if value?is_string || value?is_number || value?is_boolean || value?is_date || value?is_enumerable]
[@propvalue name value type/]
[#elseif value.class.canonicalName?ends_with("NULL")]
@@ -250,20 +273,20 @@
[#macro propvalue name value type]
[#if type == "STRING"]
-[@values value;v][@stringvalue v/][/@values]
+[@values value;v][@stringvalue v/][/@values]
[#elseif type == "INTEGER"]
-[@values value;v][@integervalue v/][/@values]
+[@values value;v][@integervalue v/][/@values]
[#elseif type == "DECIMAL"]
-[@values value;v][@decimalvalue v/][/@values]
+[@values value;v][@decimalvalue v/][/@values]
[#elseif type == "BOOLEAN"]
-[@values value;v][@booleanvalue v/][/@values]
+[@values value;v][@booleanvalue v/][/@values]
[#elseif type == "DATETIME"]
-[@values value;v][@datetimevalue v/][/@values]
+[@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]
+[@values value;v][@urivalue absurl(url.serviceContext) + v/][/@values]
[#elseif type == "ID"]
-[@values value;v][@idvalue v/][/@values]
+[@values value;v][@idvalue v/][/@values]
[#-- TODO: remaining property types --]
[/#if]
[/#macro]
@@ -292,15 +315,40 @@
[#-- 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 stringvalue value]${value}[/#macro]
-[#macro integervalue value]${value?c}[/#macro]
-[#macro decimalvalue value]${value?c}[/#macro]
-[#macro booleanvalue value]${value?string}[/#macro]
-[#macro datetimevalue value]${xmldate(value)}[/#macro]
-[#macro urivalue value]${value}[/#macro]
-[#macro idvalue value]${value}[/#macro]
+[#macro stringvalue value]${value}[/#macro]
+[#macro integervalue value]${value?c}[/#macro]
+[#macro decimalvalue value]${value?c}[/#macro]
+[#macro booleanvalue value]${value?string}[/#macro]
+[#macro datetimevalue value]${xmldate(value)}[/#macro]
+[#macro urivalue value]${value}[/#macro]
+[#macro idvalue value]${value}[/#macro]
[#-- --]
@@ -527,37 +575,37 @@
[#if type == "STRING"]
[@cmisChoices choice.children type/]
-[@stringvalue choice.value/]
+[@stringvalue choice.value/]
[#elseif type == "INTEGER"]
[@cmisChoices choice.children type/]
-[@stringvalue choice.value/]
+[@stringvalue choice.value/]
[#elseif type == "DECIMAL"]
[@cmisChoices choice.children type/]
-[@stringvalue choice.value/]
+[@stringvalue choice.value/]
[#elseif type == "BOOLEAN"]
[@cmisChoices choice.children type/]
-[@stringvalue choice.value/]
+[@stringvalue choice.value/]
[#elseif type == "DATETIME"]
[@cmisChoices choice.children type/]
-[@stringvalue choice.value/]
+[@stringvalue choice.value/]
[#elseif type == "URI"]
[@cmisChoices choice.children type/]
-[@stringvalue choice.value/]
+[@stringvalue choice.value/]
[#elseif type == "ID"]
[@cmisChoices choice.children type/]
-[@stringvalue choice.value/]
+[@stringvalue choice.value/]
[#-- TODO: remaining property types --]
[/#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]
[#-- Helper to render Alfresco service document uri --]
-[#macro serviceuri]${absurl(url.serviceContext)}/api/repository[/#macro]
\ No newline at end of file
+[#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]
diff --git a/config/alfresco/templates/webscripts/org/alfresco/cmis/atomentry.lib.js b/config/alfresco/templates/webscripts/org/alfresco/cmis/atomentry.lib.js
index 2eb560afc5..0a843fb38a 100644
--- a/config/alfresco/templates/webscripts/org/alfresco/cmis/atomentry.lib.js
+++ b/config/alfresco/templates/webscripts/org/alfresco/cmis/atomentry.lib.js
@@ -1,4 +1,3 @@
-
//
// 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
// return null => update not allowed, abort update
// true => update allowed
diff --git a/config/alfresco/templates/webscripts/org/alfresco/cmis/constants.lib.js b/config/alfresco/templates/webscripts/org/alfresco/cmis/constants.lib.js
new file mode 100644
index 0000000000..8620873b57
--- /dev/null
+++ b/config/alfresco/templates/webscripts/org/alfresco/cmis/constants.lib.js
@@ -0,0 +1,6 @@
+//
+// Constants
+//
+
+// CMIS Enums
+CMISRelationshipDirectionEnum = Packages.org.alfresco.cmis.CMISRelationshipDirectionEnum;
diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/store/relationship.get.atomentry.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/store/relationship.get.atomentry.ftl
new file mode 100644
index 0000000000..c63fc7ba2c
--- /dev/null
+++ b/config/alfresco/templates/webscripts/org/alfresco/repository/store/relationship.get.atomentry.ftl
@@ -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]
+
+
+[#assign namespace][@nsLib.entryNS/][/#assign]
+
+[@entryLib.assoc assoc=assoc propfilter=filter includeallowableactions=includeAllowableActions ns=namespace/]
+
+[/#compress]
diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/store/relationship.get.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/repository/store/relationship.get.desc.xml
new file mode 100644
index 0000000000..8ab15c4c9c
--- /dev/null
+++ b/config/alfresco/templates/webscripts/org/alfresco/repository/store/relationship.get.desc.xml
@@ -0,0 +1,9 @@
+
+ Retrieve relationship (getProperties)
+
+
+ /api/rel/{store_type}/{store_id}/{id}/type/{rel_type}/target/{target_store_type}/{target_store_id}/{target_id}
+ guest
+ argument
+ CMIS
+
\ No newline at end of file
diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/store/relationship.get.js b/config/alfresco/templates/webscripts/org/alfresco/repository/store/relationship.get.js
new file mode 100644
index 0000000000..2298d2cd09
--- /dev/null
+++ b/config/alfresco/templates/webscripts/org/alfresco/repository/store/relationship.get.js
@@ -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;
+ }
+}
diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/store/relationships.get.atomfeed.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/store/relationships.get.atomfeed.ftl
new file mode 100644
index 0000000000..45002aee62
--- /dev/null
+++ b/config/alfresco/templates/webscripts/org/alfresco/repository/store/relationships.get.atomfeed.ftl
@@ -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]
+
+
+
+
+[@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/]
+
+
+
+[/#compress]
\ No newline at end of file
diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/store/relationships.get.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/repository/store/relationships.get.desc.xml
new file mode 100644
index 0000000000..0c52275ea0
--- /dev/null
+++ b/config/alfresco/templates/webscripts/org/alfresco/repository/store/relationships.get.desc.xml
@@ -0,0 +1,10 @@
+
+ Retrieve list of relationships (getRelationships)
+
+
+ /api/node/{store_type}/{store_id}/{id}/rels?filter={filter?}&relationshipType={relationshipType?}&includeSubRelationshipTypes={includeSubRelationshipTypes?}&direction={direction?}&skipCount={skipCount?}&maxItems={maxItems?}&includeAllowableActions={includeAllowableActions?}
+ /api/path/{store_type}/{store_id}/{id}/rels?filter={filter?}&relationshipType={relationshipType?}&includeSubRelationshipTypes={includeSubRelationshipTypes?}&direction={direction?}&skipCount={skipCount?}&maxItems={maxItems?}&includeAllowableActions={includeAllowableActions?}
+ guest
+ argument
+ CMIS
+
\ No newline at end of file
diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/store/relationships.get.js b/config/alfresco/templates/webscripts/org/alfresco/repository/store/relationships.get.js
new file mode 100644
index 0000000000..be27d6b317
--- /dev/null
+++ b/config/alfresco/templates/webscripts/org/alfresco/repository/store/relationships.get.js
@@ -0,0 +1,59 @@
+
+
+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;
+}
diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/store/relationships.post.atom.js b/config/alfresco/templates/webscripts/org/alfresco/repository/store/relationships.post.atom.js
new file mode 100644
index 0000000000..b2bea9fab1
--- /dev/null
+++ b/config/alfresco/templates/webscripts/org/alfresco/repository/store/relationships.post.atom.js
@@ -0,0 +1,40 @@
+
+
+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;
+}
diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/store/relationships.post.atomentry.201.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/store/relationships.post.atomentry.201.ftl
new file mode 100644
index 0000000000..c63fc7ba2c
--- /dev/null
+++ b/config/alfresco/templates/webscripts/org/alfresco/repository/store/relationships.post.atomentry.201.ftl
@@ -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]
+
+
+[#assign namespace][@nsLib.entryNS/][/#assign]
+
+[@entryLib.assoc assoc=assoc propfilter=filter includeallowableactions=includeAllowableActions ns=namespace/]
+
+[/#compress]
diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/store/relationships.post.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/repository/store/relationships.post.desc.xml
new file mode 100644
index 0000000000..a1181ff6a7
--- /dev/null
+++ b/config/alfresco/templates/webscripts/org/alfresco/repository/store/relationships.post.desc.xml
@@ -0,0 +1,10 @@
+
+ Create relationship (createRelationship)
+
+
+ /api/node/{store_type}/{store_id}/{id}/rels
+ /api/path/{store_type}/{store_id}/{id}/rels
+ guest
+ argument
+ CMIS
+
\ No newline at end of file
diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/store/types.get.js b/config/alfresco/templates/webscripts/org/alfresco/repository/store/types.get.js
index ce543be686..d8af28258a 100644
--- a/config/alfresco/templates/webscripts/org/alfresco/repository/store/types.get.js
+++ b/config/alfresco/templates/webscripts/org/alfresco/repository/store/types.get.js
@@ -17,13 +17,13 @@ script:
{
// query a specific type and its descendants
var typedef = cmis.queryType(typeId);
- if (typedef === null)
- {
- status.code = 404;
- status.message = "Type " + typeId + " not found";
- status.redirect = true;
- break script;
- }
+ if (typedef === null)
+ {
+ status.code = 404;
+ status.message = "Type " + typeId + " not found";
+ status.redirect = true;
+ break script;
+ }
var paged = cmis.queryTypeHierarchy(typedef, true, page);
model.results = paged.results;
model.cursor = paged.cursor;
diff --git a/source/java/org/alfresco/repo/cmis/rest/CMISPropertyValueMethod.java b/source/java/org/alfresco/repo/cmis/rest/CMISPropertyValueMethod.java
index 9d92a775a5..7bb8933372 100644
--- a/source/java/org/alfresco/repo/cmis/rest/CMISPropertyValueMethod.java
+++ b/source/java/org/alfresco/repo/cmis/rest/CMISPropertyValueMethod.java
@@ -27,6 +27,7 @@ package org.alfresco.repo.cmis.rest;
import java.util.List;
import org.alfresco.cmis.CMISServices;
+import org.alfresco.repo.template.TemplateAssociation;
import org.alfresco.repo.template.TemplateNode;
import freemarker.ext.beans.BeanModel;
@@ -76,14 +77,6 @@ public final class CMISPropertyValueMethod implements TemplateMethodModelEx
Object arg0 = args.get(0);
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
String propertyName = null;
Object arg1 = args.get(1);
@@ -92,8 +85,18 @@ public final class CMISPropertyValueMethod implements TemplateMethodModelEx
propertyName = ((TemplateScalarModel)arg1).getAsString();
}
- // retrieve property value
- result = cmisService.getProperty(node.getNodeRef(), propertyName);
+ // extract node
+ 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);
+ }
}
}
diff --git a/source/java/org/alfresco/repo/cmis/rest/CMISScript.java b/source/java/org/alfresco/repo/cmis/rest/CMISScript.java
index e600ad880c..1f5959c160 100644
--- a/source/java/org/alfresco/repo/cmis/rest/CMISScript.java
+++ b/source/java/org/alfresco/repo/cmis/rest/CMISScript.java
@@ -33,11 +33,13 @@ import org.alfresco.cmis.CMISPropertyDefinition;
import org.alfresco.cmis.CMISQueryEnum;
import org.alfresco.cmis.CMISQueryOptions;
import org.alfresco.cmis.CMISQueryService;
+import org.alfresco.cmis.CMISRelationshipDirectionEnum;
import org.alfresco.cmis.CMISResultSet;
import org.alfresco.cmis.CMISServices;
import org.alfresco.cmis.CMISTypeDefinition;
import org.alfresco.cmis.CMISTypesFilterEnum;
import org.alfresco.error.AlfrescoRuntimeException;
+import org.alfresco.repo.jscript.Association;
import org.alfresco.repo.jscript.BaseScopableProcessorExtension;
import org.alfresco.repo.jscript.ScriptNode;
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.Paging;
import org.alfresco.service.ServiceRegistry;
+import org.alfresco.service.cmr.repository.AssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
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_PROPERTY_DEFINITIONS = "includePropertyDefinitions";
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_MAJOR = "major";
public static final String ARG_MAJOR_VERSION = "majorVersion";
@@ -197,7 +200,7 @@ public class CMISScript extends BaseScopableProcessorExtension
*/
public String getDefaultTypesFilter()
{
- return CMISTypesFilterEnum.FACTORY.defaultLabel();
+ return CMISTypesFilterEnum.FACTORY.getDefaultLabel();
}
/**
@@ -242,6 +245,25 @@ public class CMISScript extends BaseScopableProcessorExtension
}
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
@@ -267,6 +289,31 @@ public class CMISScript extends BaseScopableProcessorExtension
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
*
diff --git a/source/java/org/alfresco/repo/cmis/rest/CMISTypeDefinitionMethod.java b/source/java/org/alfresco/repo/cmis/rest/CMISTypeDefinitionMethod.java
index b022d01ffe..1188907da9 100644
--- a/source/java/org/alfresco/repo/cmis/rest/CMISTypeDefinitionMethod.java
+++ b/source/java/org/alfresco/repo/cmis/rest/CMISTypeDefinitionMethod.java
@@ -27,7 +27,10 @@ package org.alfresco.repo.cmis.rest;
import java.util.List;
import org.alfresco.cmis.CMISDictionaryService;
+import org.alfresco.cmis.CMISScope;
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.service.namespace.QName;
@@ -47,8 +50,10 @@ import freemarker.template.TemplateModelException;
*/
public class CMISTypeDefinitionMethod implements TemplateMethodModelEx
{
+ private static CMISScope[] EMPTY_SCOPES = new CMISScope[] {};
private CMISDictionaryService dictionaryService;
+
/**
* Construct
*/
@@ -71,6 +76,7 @@ public class CMISTypeDefinitionMethod implements TemplateMethodModelEx
if (arg0 instanceof BeanModel)
{
// extract node type qname
+ CMISScope[] matchingScopes = EMPTY_SCOPES;
QName nodeType = null;
Object wrapped = ((BeanModel)arg0).getWrappedObject();
if (wrapped != null)
@@ -79,6 +85,11 @@ public class CMISTypeDefinitionMethod implements TemplateMethodModelEx
{
nodeType = ((TemplateNode)wrapped).getType();
}
+ else if (wrapped instanceof TemplateAssociation)
+ {
+ nodeType = ((TemplateAssociation)wrapped).getTypeQName();
+ matchingScopes = new CMISScope[] { CMISScope.RELATIONSHIP };
+ }
else if (wrapped instanceof QName)
{
nodeType = (QName)wrapped;
@@ -88,7 +99,7 @@ public class CMISTypeDefinitionMethod implements TemplateMethodModelEx
// convert to CMIS type
if (nodeType != null)
{
- result = dictionaryService.findTypeForClass(nodeType);
+ result = dictionaryService.findTypeForClass(nodeType, matchingScopes);
}
}
}
diff --git a/source/java/org/alfresco/repo/cmis/rest/test/BaseCMISWebScriptTest.java b/source/java/org/alfresco/repo/cmis/rest/test/BaseCMISWebScriptTest.java
index 4ff446ec75..d7fbfcf2db 100644
--- a/source/java/org/alfresco/repo/cmis/rest/test/BaseCMISWebScriptTest.java
+++ b/source/java/org/alfresco/repo/cmis/rest/test/BaseCMISWebScriptTest.java
@@ -501,6 +501,31 @@ public class BaseCMISWebScriptTest extends BaseWebScriptTest
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
//
diff --git a/source/java/org/alfresco/repo/cmis/rest/test/CMISCustomTypeTest.java b/source/java/org/alfresco/repo/cmis/rest/test/CMISCustomTypeTest.java
index 58c57a6a76..a17a9a7741 100644
--- a/source/java/org/alfresco/repo/cmis/rest/test/CMISCustomTypeTest.java
+++ b/source/java/org/alfresco/repo/cmis/rest/test/CMISCustomTypeTest.java
@@ -25,12 +25,15 @@
package org.alfresco.repo.cmis.rest.test;
import java.io.StringReader;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import org.alfresco.abdera.ext.cmis.CMISConstants;
import org.alfresco.abdera.ext.cmis.CMISObject;
import org.alfresco.abdera.ext.cmis.CMISProperties;
import org.alfresco.abdera.ext.cmis.CMISProperty;
+import org.alfresco.repo.cmis.rest.CMISScript;
import org.alfresco.util.GUID;
import org.alfresco.web.scripts.Format;
import org.alfresco.web.scripts.TestWebScriptServer.DeleteRequest;
@@ -105,11 +108,11 @@ public class CMISCustomTypeTest extends BaseCMISWebScriptTest
Feed children = getFeed(childrenLink.getHref());
assertNotNull(children);
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());
int entriesAfter = feedFolderAfter.getEntries().size();
assertEquals(entriesBefore +1, entriesAfter);
- Entry entry = feedFolderAfter.getEntry(folder.getId().toString());
+ Entry entry = feedFolderAfter.getEntry(document.getId().toString());
CMISObject object = entry.getExtension(CMISConstants.OBJECT);
assertEquals("D/cmiscustom_document", object.getObjectTypeId().getStringValue());
CMISProperty customProp = object.getProperties().find("cmiscustom_docprop_string");
@@ -315,5 +318,91 @@ public class CMISCustomTypeTest extends BaseCMISWebScriptTest
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 args = new HashMap();
+ 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());
+ }
}
diff --git a/source/java/org/alfresco/repo/cmis/rest/test/createdocument.atomentry.xml b/source/java/org/alfresco/repo/cmis/rest/test/createdocument.atomentry.xml
index 4d416d12d3..ded8931397 100644
--- a/source/java/org/alfresco/repo/cmis/rest/test/createdocument.atomentry.xml
+++ b/source/java/org/alfresco/repo/cmis/rest/test/createdocument.atomentry.xml
@@ -4,8 +4,8 @@
${NAME} (summary)
${CONTENT}
-
- document
-
+
+ document
+
diff --git a/source/java/org/alfresco/repo/cmis/rest/test/createrelationship.atomentry.xml b/source/java/org/alfresco/repo/cmis/rest/test/createrelationship.atomentry.xml
new file mode 100644
index 0000000000..1d474bf87b
--- /dev/null
+++ b/source/java/org/alfresco/repo/cmis/rest/test/createrelationship.atomentry.xml
@@ -0,0 +1,9 @@
+
+
+
+
+ ${RELTYPE}
+ ${TARGETID}
+
+
+
diff --git a/source/java/org/alfresco/repo/cmis/rest/xsd/CMISSchemaTest.java b/source/java/org/alfresco/repo/cmis/rest/xsd/CMISSchemaTest.java
index 4f80a65277..40d2b01c16 100644
--- a/source/java/org/alfresco/repo/cmis/rest/xsd/CMISSchemaTest.java
+++ b/source/java/org/alfresco/repo/cmis/rest/xsd/CMISSchemaTest.java
@@ -208,6 +208,13 @@ public class CMISSchemaTest extends TestCase
assertValidXML(xml, cmisValidator.getCMISAtomValidator());
}
+ public void testAtomEntry()
+ throws Exception
+ {
+ String xml = getXML("example_atomentry.xml");
+ assertValidXML(xml, cmisValidator.getCMISAtomValidator());
+ }
+
//
// Missing from v0.61
//
diff --git a/source/java/org/alfresco/repo/cmis/rest/xsd/example_atomentry.xml b/source/java/org/alfresco/repo/cmis/rest/xsd/example_atomentry.xml
new file mode 100644
index 0000000000..45e06be6ec
--- /dev/null
+++ b/source/java/org/alfresco/repo/cmis/rest/xsd/example_atomentry.xml
@@ -0,0 +1,26 @@
+
+
+
+ Al Brown
+ http://www.ibm.com/
+ albertcbrown@us.ibm.com
+
+
+ urn:uuid:70cfe57f-5d59-4293-9cbc-842107117d65
+
+
+
+
+
+
+
+
+
+
+
+ 2009-04-17T13:50:58.656-07:00
+ HTML summary of Entry 70cfe57f-5d59-4293-9cbc-842107117d65
+ CMIS Example Document
+ 2009-04-17T13:50:58.656-07:00
+
+
diff --git a/source/test-resources/cmis/cmisCustomModel.xml b/source/test-resources/cmis/cmisCustomModel.xml
index 044a975f04..ccfd2e7231 100644
--- a/source/test-resources/cmis/cmisCustomModel.xml
+++ b/source/test-resources/cmis/cmisCustomModel.xml
@@ -42,6 +42,19 @@
true
+
+
+
+ false
+ false
+
+
+ cm:content
+ false
+ true
+
+
+