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 10b38dbb87..8c8f7293a8 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
@@ -159,7 +159,7 @@
[#if cmisproperty(node, "ParentId")?is_string]
-
+
[/#if]
@@ -342,9 +342,9 @@
[/#macro]
[#macro typedefCMISLinks typedef]
-
+
[#if typedef.parentType??]
-
+
[/#if]
@@ -508,7 +508,7 @@
[/#if]
${propdef.dataType.label}
${propdef.cardinality.label}
- ${propdef.updatability.label}
+ ${propdef.updatability.label}
${inherited?string}
${propdef.required?string}
${propdef.queryable?string}
@@ -525,37 +525,37 @@
[#if choices?exists]
[#list choices as choice]
[#if type == "STRING"]
-
+
[@cmisChoices choice.children type/]
[@stringvalue choice.value/]
[#elseif type == "INTEGER"]
-
+
[@cmisChoices choice.children type/]
[@stringvalue choice.value/]
[#elseif type == "DECIMAL"]
-
+
[@cmisChoices choice.children type/]
[@stringvalue choice.value/]
[#elseif type == "BOOLEAN"]
-
+
[@cmisChoices choice.children type/]
[@stringvalue choice.value/]
[#elseif type == "DATETIME"]
-
+
[@cmisChoices choice.children type/]
[@stringvalue choice.value/]
[#elseif type == "URI"]
-
+
[@cmisChoices choice.children type/]
[@stringvalue choice.value/]
[#elseif type == "ID"]
-
+
[@cmisChoices choice.children type/]
[@stringvalue choice.value/]
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 55d7b4907c..2eb560afc5 100644
--- a/config/alfresco/templates/webscripts/org/alfresco/cmis/atomentry.lib.js
+++ b/config/alfresco/templates/webscripts/org/alfresco/cmis/atomentry.lib.js
@@ -58,7 +58,7 @@ function createNode(parent, entry, slug)
// update node properties (excluding object type & name)
var exclude = [ "ObjectTypeId", "Name" ];
- var updated = updateNode(node, entry, exclude, true);
+ var updated = updateNode(node, entry, exclude, function(propDef) {return patchValidator(propDef, true);});
// only return node if updated successfully
return (updated == null) ? null : node;
@@ -71,10 +71,10 @@ function createNode(parent, entry, slug)
// @param node Alfresco node to update
// @param entry Atom entry to update from
// @param exclude property names to exclude
-// @param pwc true => node represents private working copy
+// @param validator function callback for validating property update
// @return true => node has been updated (or null, in case of error)
//
-function updateNode(node, entry, exclude, pwc)
+function updateNode(node, entry, exclude, validator)
{
// check update is allowed
if (!node.hasPermission("WriteProperties") || !node.hasPermission("WriteContent"))
@@ -83,16 +83,13 @@ function updateNode(node, entry, exclude, pwc)
status.message = "Permission to update is denied";
status.redirect = true;
return null;
- }
+ }
var updated = false;
var object = entry.getExtension(atom.names.cmis_object);
var props = (object == null) ? null : object.properties;
var vals = new Object();
- // apply defaults
- if (pwc == null) pwc = false;
-
// calculate list of properties to update
// TODO: consider array form of properties.names
var updateProps = (props == null) ? new Array() : props.names.toArray().filter(function(element, index, array) {return true;});
@@ -118,31 +115,16 @@ function updateNode(node, entry, exclude, pwc)
return null;
}
-// TODO: disabled for now to allow for PUT semantics - CMIS will move to POST for update
- // is the property write-able?
- if (propDef.updatability === Packages.org.alfresco.cmis.CMISUpdatabilityEnum.READ_ONLY)
+ // validate property update
+ var valid = validator(propDef);
+ if (valid == null)
{
-// status.code = 500;
-// status.message = "Property " + propName + " cannot be updated. It is read only."
-// status.redirect = true;
-// return null;
- continue;
+ // error, abort update
+ return null;
}
- if (!pwc && propDef.updatability === Packages.org.alfresco.cmis.CMISUpdatabilityEnum.READ_AND_WRITE_WHEN_CHECKED_OUT)
+ if (valid == false)
{
-// status.code = 500;
-// status.message = "Property " + propName + " can only be updated on a private working copy.";
-// status.redirect = true;
-// return null;
- continue;
- }
- var mappedProperty = propDef.propertyAccessor.mappedProperty;
- if (mappedProperty == null)
- {
-// status.code = 500;
-// status.message = "Internal error: Property " + propName + " does not map to a write-able Alfresco property";
-// status.redirect = true;
-// return null;
+ // ignore property
continue;
}
@@ -174,7 +156,7 @@ function updateNode(node, entry, exclude, pwc)
val = entry.title;
}
- vals[mappedProperty.toString()] = val;
+ vals[propDef.propertyAccessor.mappedProperty.toString()] = val;
}
}
@@ -217,6 +199,61 @@ function updateNode(node, entry, exclude, pwc)
}
+// callback for validating property update for patch
+// return null => update not allowed, abort update
+// true => update allowed
+// false => update not allowed, ignore property
+function patchValidator(propDef, pwc)
+{
+ // is the property write-able?
+ if (propDef.updatability === Packages.org.alfresco.cmis.CMISUpdatabilityEnum.READ_ONLY)
+ {
+ status.code = 500;
+ status.message = "Property " + propName + " cannot be updated. It is read only."
+ status.redirect = true;
+ return null;
+ }
+ if (!pwc && propDef.updatability === Packages.org.alfresco.cmis.CMISUpdatabilityEnum.READ_AND_WRITE_WHEN_CHECKED_OUT)
+ {
+ status.code = 500;
+ status.message = "Property " + propName + " can only be updated on a private working copy.";
+ status.redirect = true;
+ return null;
+ }
+ var mappedProperty = propDef.propertyAccessor.mappedProperty;
+ if (mappedProperty == null)
+ {
+ status.code = 500;
+ status.message = "Internal error: Property " + propName + " does not map to a write-able Alfresco property";
+ status.redirect = true;
+ return null;
+ }
+ return true;
+}
+
+//callback for validating property update for put
+//return null => update not allowed, abort update
+// true => update allowed
+// false => update not allowed, ignore property
+function putValidator(propDef, pwc)
+{
+ // is the property write-able?
+ if (propDef.updatability === Packages.org.alfresco.cmis.CMISUpdatabilityEnum.READ_ONLY)
+ {
+ return false;
+ }
+ if (!pwc && propDef.updatability === Packages.org.alfresco.cmis.CMISUpdatabilityEnum.READ_AND_WRITE_WHEN_CHECKED_OUT)
+ {
+ return false;
+ }
+ var mappedProperty = propDef.propertyAccessor.mappedProperty;
+ if (mappedProperty == null)
+ {
+ return false;
+ }
+ return true;
+}
+
// callback function for determining if property name should be excluded
// note: this refers to array of property names to exclude
function includeProperty(element, index, array)
diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/repository.get.atom.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/repository.get.atom.ftl
index c070ef179c..8808a50ffe 100644
--- a/config/alfresco/templates/webscripts/org/alfresco/repository/repository.get.atom.ftl
+++ b/config/alfresco/templates/webscripts/org/alfresco/repository/repository.get.atom.ftl
@@ -27,6 +27,9 @@
none
false
[#-- TODO: --]
+ [#-- TODO: wait for ACL proposal before implementing --]
+ none
+ [#-- TODO: --]
${cmisVersion}
diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/store/item.patch.atom.js b/config/alfresco/templates/webscripts/org/alfresco/repository/store/item.patch.atom.js
new file mode 100644
index 0000000000..5a0a6795c8
--- /dev/null
+++ b/config/alfresco/templates/webscripts/org/alfresco/repository/store/item.patch.atom.js
@@ -0,0 +1,32 @@
+
+
+script:
+{
+ // ensure atom entry is posted
+ if (entry === null)
+ {
+ status.code = 400;
+ status.message = "Expected atom entry";
+ status.redirect = true;
+ break 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.code = 404;
+ status.message = "Repository " + pathSegments[2] + " " + reference.join("/") + " not found";
+ status.redirect = true;
+ break script;
+ }
+
+ // update properties
+ var updated = updateNode(model.node, entry, null, function(propDef) {return patchValidator(propDef, false);});
+ if (updated)
+ {
+ model.node.save();
+ }
+}
diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/store/item.patch.atomentry.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/store/item.patch.atomentry.ftl
new file mode 100644
index 0000000000..ba1a5032ef
--- /dev/null
+++ b/config/alfresco/templates/webscripts/org/alfresco/repository/store/item.patch.atomentry.ftl
@@ -0,0 +1,15 @@
+[#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]
+
+[#if node.isDocument]
+ [@entryLib.document node=node includeallowableactions=true includerelationships="none" ns=namespace/]
+[#else]
+ [@entryLib.folder node=node includeallowableactions=true includerelationships="none" ns=namespace/]
+[/#if]
+
+[/#compress]
\ No newline at end of file
diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/store/item.patch.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/repository/store/item.patch.desc.xml
new file mode 100644
index 0000000000..7a986afb29
--- /dev/null
+++ b/config/alfresco/templates/webscripts/org/alfresco/repository/store/item.patch.desc.xml
@@ -0,0 +1,35 @@
+
+ Update properties (updateProperties)
+
+
+
+Inputs:
+
+ID objectId
+(Optional) String changeToken
+Collection propertyCollection - Subset list of Properties to update
+
+Outputs:
+
+ID objectId
+
+Notes:
+
+Preserves the ID of the object
+Subset of properties: Properties not specified in this list are not changed
+To remove a property, specify property with no value
+If an attempt is made to update a read-only property, throw ConstraintViolationException.
+If a ChangeToken is provided by the repository when the object is retrieved, the change token MUST be included as-is when calling updateProperties.
+For Multi-Value properties, the whole list of values MUST be provided on every update.
+Use getAllowableActions to identify whether older version specified by ID is updatable.
+If this is a private working copy, some repositories may not support updates.
+Because repositories MAY automatically create new Document Versions on a user’s behalf, the objectId returned may not match the one provided as an input to this method.
+]]>
+
+ /api/node/{store_type}/{store_id}/{id}
+ /api/path/{store_type}/{store_id}/{id}
+ user
+ argument
+ CMIS
+
\ No newline at end of file
diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/store/item.put.atom.js b/config/alfresco/templates/webscripts/org/alfresco/repository/store/item.put.atom.js
index e63fba85ac..7554f9db7a 100644
--- a/config/alfresco/templates/webscripts/org/alfresco/repository/store/item.put.atom.js
+++ b/config/alfresco/templates/webscripts/org/alfresco/repository/store/item.put.atom.js
@@ -24,7 +24,7 @@ script:
}
// update properties
- var updated = updateNode(model.node, entry, null, false);
+ var updated = updateNode(model.node, entry, null, function(propDef) {return putValidator(propDef, false);});
if (updated)
{
model.node.save();
diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/store/pwc.patch.atom.js b/config/alfresco/templates/webscripts/org/alfresco/repository/store/pwc.patch.atom.js
new file mode 100644
index 0000000000..a747cee700
--- /dev/null
+++ b/config/alfresco/templates/webscripts/org/alfresco/repository/store/pwc.patch.atom.js
@@ -0,0 +1,49 @@
+
+
+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("node", reference);
+ if (model.node === null || !model.node.hasAspect("cm:workingcopy"))
+ {
+ status.code = 404;
+ status.message = "Private working copy " + reference.join("/") + " not found";
+ status.redirect = true;
+ break script;
+ }
+
+ // check permissions
+ model.checkin = args[cmis.ARG_CHECKIN] == "true" ? true : false;
+ if (model.checkin && !model.node.hasPermission("CheckIn"))
+ {
+ status.code = 403;
+ status.message = "Permission to checkin is denied";
+ status.redirect = true;
+ break script;
+ }
+
+ if (entry !== null)
+ {
+ // update properties
+ var updated = updateNode(model.node, entry, null, function(propDef) {return patchValidator(propDef, true);});
+ if (updated === null)
+ {
+ break script;
+ }
+ if (updated)
+ {
+ model.node.save();
+ }
+ }
+
+ // checkin
+ if (model.checkin)
+ {
+ var comment = args[cmis.ARG_CHECKIN_COMMENT];
+ var major = args[cmis.ARG_MAJOR];
+ major = (major === null || major == "true") ? true : false;
+ model.node = model.node.checkin(comment === null ? "" : comment, major);
+ }
+}
diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/store/pwc.patch.atomentry.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/store/pwc.patch.atomentry.ftl
new file mode 100644
index 0000000000..7b7b1f35ae
--- /dev/null
+++ b/config/alfresco/templates/webscripts/org/alfresco/repository/store/pwc.patch.atomentry.ftl
@@ -0,0 +1,15 @@
+[#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]
+
+[#if checkin]
+ [@entryLib.document node=node includeallowableactions=true includerelationships="none" ns=namespace/]
+[#else]
+ [@entryLib.pwc node=node includeallowableactions=true includerelationships="none" ns=namespace/]
+[/#if]
+
+[/#compress]
\ No newline at end of file
diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/store/pwc.patch.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/repository/store/pwc.patch.desc.xml
new file mode 100644
index 0000000000..9e0543e198
--- /dev/null
+++ b/config/alfresco/templates/webscripts/org/alfresco/repository/store/pwc.patch.desc.xml
@@ -0,0 +1,32 @@
+
+ Checkin Private Working Copy (checkin)
+
+
+
+Inputs:
+
+ID documentId: ObjectId of the private working copy
+Optional) Boolean major: True (Default)
+(Optional) Property bag
+(Optional) ContentStream stream
+(Optional) String CheckinComment
+
+Outputs:
+
+ID documentId: ID for the new version of the document.
+
+Notes:
+
+It is left to the repository to determine who can check-in a document.
+CheckinComment is persisted if specified.
+For repositories that do not support updating private working copies, all updates MUST be set on the check-in service.
+If Document is not checked out, throw OperationNotSupportedException.
+If the Document has “Content_Stream_Allowed” set to FALSE, and a call is made to checkIn that includes a content-stream, throw ConstraintViolationException.
+]]>
+
+ /api/pwc/{store_type}/{store_id}/{id}?checkinComment={checkinComment?}&major={major?}&checkin={checkin?}
+ user
+
+ CMIS
+
\ No newline at end of file
diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/store/pwc.put.atom.js b/config/alfresco/templates/webscripts/org/alfresco/repository/store/pwc.put.atom.js
index 87c39487af..5bffabf360 100644
--- a/config/alfresco/templates/webscripts/org/alfresco/repository/store/pwc.put.atom.js
+++ b/config/alfresco/templates/webscripts/org/alfresco/repository/store/pwc.put.atom.js
@@ -27,7 +27,7 @@ script:
if (entry !== null)
{
// update properties
- var updated = updateNode(model.node, entry, null, false);
+ var updated = updateNode(model.node, entry, null, function(propDef) {return putValidator(propDef, true);});
if (updated === null)
{
break script;
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 e36110c393..87398e5cac 100644
--- a/source/java/org/alfresco/repo/cmis/rest/test/CMISCustomTypeTest.java
+++ b/source/java/org/alfresco/repo/cmis/rest/test/CMISCustomTypeTest.java
@@ -31,6 +31,7 @@ import org.alfresco.util.GUID;
import org.alfresco.web.scripts.Format;
import org.alfresco.web.scripts.TestWebScriptServer.DeleteRequest;
import org.alfresco.web.scripts.TestWebScriptServer.GetRequest;
+import org.alfresco.web.scripts.TestWebScriptServer.PatchRequest;
import org.alfresco.web.scripts.TestWebScriptServer.PostRequest;
import org.alfresco.web.scripts.TestWebScriptServer.PutRequest;
import org.alfresco.web.scripts.TestWebScriptServer.Response;
@@ -123,15 +124,52 @@ public class CMISCustomTypeTest extends BaseCMISWebScriptTest
assertEquals(false, multiValues.get(1));
}
- public void testUpdate()
+ public void testUpdatePatch()
throws Exception
{
// retrieve test folder for update
- Entry testFolder = createTestFolder("testUpdateCustomDocument");
+ Entry testFolder = createTestFolder("testUpdatePatchCustomDocument");
Link childrenLink = testFolder.getLink(CMISConstants.REL_CHILDREN);
// create document for update
- Entry document = createDocument(childrenLink.getHref(), "testUpdateCustomDocument", "/org/alfresco/repo/cmis/rest/test/createcustomdocument.atomentry.xml");
+ Entry document = createDocument(childrenLink.getHref(), "testUpdatePatchCustomDocument", "/org/alfresco/repo/cmis/rest/test/createcustomdocument.atomentry.xml");
+ assertNotNull(document);
+
+ // update
+ String updateFile = loadString("/org/alfresco/repo/cmis/rest/test/updatecustomdocument.atomentry.xml");
+ String guid = GUID.generate();
+ updateFile = updateFile.replace("${NAME}", guid);
+ Response res = sendRequest(new PatchRequest(document.getSelfLink().getHref().toString(), updateFile, Format.ATOMENTRY.mimetype()), 200, getAtomValidator());
+ assertNotNull(res);
+ Entry updated = getAbdera().parseEntry(new StringReader(res.getContentAsString()), null);
+
+ // ensure update occurred
+ assertEquals(document.getId(), updated.getId());
+ assertEquals(document.getPublished(), updated.getPublished());
+ assertEquals("Updated Title " + guid, updated.getTitle());
+ CMISObject object = updated.getExtension(CMISConstants.OBJECT);
+ assertEquals("D/cmiscustom_document", object.getObjectTypeId().getStringValue());
+ CMISProperty customProp = object.getProperties().find("cmiscustom_docprop_string");
+ assertNotNull(customProp);
+ assertEquals("custom " + guid, customProp.getStringValue());
+ CMISProperty multiProp = object.getProperties().find("cmiscustom_docprop_boolean_multi");
+ assertNotNull(multiProp);
+ List