diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/blogs/blogpost.lib.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/blogs/blogpost.lib.ftl index fdf1995b23..b8757165a7 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/blogs/blogpost.lib.ftl +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/blogs/blogpost.lib.ftl @@ -47,8 +47,8 @@ "username": "${item.node.properties.creator}" }, - "createdOn": "${xmldate(item.createdDate)}", - "modifiedOn": "${xmldate(item.modifiedDate)}", + "createdOn": "${formatDateRFC822(item.createdDate)}", + "modifiedOn": "${formatDateRFC822(item.modifiedDate)}", "permissions": { "edit": ${item.node.hasPermission("Write")?string}, @@ -59,16 +59,16 @@ <#-- draft vs internal published --> "isDraft": ${item.isDraft?string}, <#if (! item.isDraft)> - "releasedOn": "${xmldate(item.releasedDate)}", + "releasedOn": "${formatDateRFC822(item.releasedDate)}", <#-- true if the post has been updated --> "isUpdated": ${item.isUpdated?string}, <#if (item.isUpdated)> - "updatedOn": "${xmldate(item.updatedDate)}", + "updatedOn": "${formatDateRFC822(item.updatedDate)}", <#if (item.node.properties["blg:published"]?? && item.node.properties["blg:published"] == true)> - "publishedOn": "${xmldate(item.node.properties["blg:posted"])}", - "updatedOn": "${xmldate(item.node.properties["blg:lastUpdate"])}", + "publishedOn": "${formatDateRFC822(item.node.properties["blg:posted"])}", + "updatedOn": "${formatDateRFC822(item.node.properties["blg:lastUpdate"])}", "postId": "${item.node.properties["blg:postId"]!''}", "postLink": "${item.node.properties["blg:link"]!''}", "outOfDate": ${item.outOfDate?string}, @@ -105,4 +105,11 @@ }, "item": <@blogpostJSON item=item /> } - \ No newline at end of file + + +<#function formatDateRFC822 dateItem> + <# local temp=${.locale} --> + <#setting locale="en_US"> + <#return dateItem?datetime?string("EEE, d MMM yyyy HH:mm:ss Z")> + <# setting locale=temp --> + \ No newline at end of file diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/category/category.post.json.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/category/category.post.json.ftl index 1bd7b22c2e..4bcca57ef5 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/category/category.post.json.ftl +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/category/category.post.json.ftl @@ -1,6 +1,6 @@ { <#if redirect??>"redirect": "${redirect}", - <#if persistedObject??>"persistedObject": "${persistedObject?replace("\t", "")?string}", + <#if persistedObject??>"persistedObject": "${persistedObject.nodeRef?replace("\t", "")?string}", "message": "${msg(messageKey)}", "name": "${name}" } \ No newline at end of file diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/site/site.lib.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/site/site.lib.ftl index 0e31ca9deb..d92621f184 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/site/site.lib.ftl +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/site/site.lib.ftl @@ -51,6 +51,7 @@ ], + <#nested> "isPublic": ${site.isPublic?string("true", "false")}, "visibility": "${site.visibility}" } diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/site/sites.get.json.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/site/sites.get.json.ftl index 6f208688e8..e5f5920077 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/site/sites.get.json.ftl +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/site/sites.get.json.ftl @@ -3,7 +3,7 @@ [ <#list sites?sort_by("shortName") as site> <@siteLib.siteJSONManagers site=site roles=roles> - "isMemberOfGroup": ${site.isMemberOfGroup(person.properties.userName)?string}, + "isMemberOfGroup": <#if site.getMembersRoleInfo(person.properties.userName)?has_content>${site.getMembersRoleInfo(person.properties.userName).isMemberOfGroup()?string}<#else>false, <#if site_has_next>, diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/wcm/webproject.put.json.js b/config/alfresco/templates/webscripts/org/alfresco/repository/wcm/webproject.put.json.js index 64cc33cf38..d75757b6dd 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/wcm/webproject.put.json.js +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/wcm/webproject.put.json.js @@ -42,7 +42,7 @@ function main() { if(json.has("isTemplate")) { var isTemplate = json.get("isTemplate"); - webproject.setDescription(isTemplate); + webproject.setTemplate(isTemplate); } // update the web project diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/workflow/workflow.lib.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/workflow/workflow.lib.ftl index b95e3f954a..d01ee434f5 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/workflow/workflow.lib.ftl +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/workflow/workflow.lib.ftl @@ -4,7 +4,7 @@ { "id": "${task.id}", "url": "${task.url}", - "name": "${task.name}", + "name": "${task.name!""}", "title": "${task.title!""}", "description": "${task.description!""}", "state": "${task.state}", diff --git a/config/alfresco/templates/webscripts/org/alfresco/slingshot/wiki/pagelist.get.json.ftl b/config/alfresco/templates/webscripts/org/alfresco/slingshot/wiki/pagelist.get.json.ftl index 4f7bd9a31d..069fa4dd77 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/slingshot/wiki/pagelist.get.json.ftl +++ b/config/alfresco/templates/webscripts/org/alfresco/slingshot/wiki/pagelist.get.json.ftl @@ -1,62 +1,70 @@ <#macro dateFormat date>${xmldate(date)} <#escape x as jsonUtils.encodeJSONString(x)> { - "totalPages": ${wiki.pages?size?c}, - "permissions": - { - "create": ${wiki.container.hasPermission("CreateChildren")?string} - }, - "pages": - [ - <#if pageMetaOnly> - <#list wiki.pages as p> - <#assign page = p.page> - { - "name": "${p.name}", - "title": "<#if p.title?has_content>${p.title}<#else>${p.name?replace("_", " ")}", - }<#if p_has_next>, - - <#else> - <#list wiki.pages?sort_by(['modified'])?reverse as p> - <#assign node = p.node> - <#assign page = p.page> - { - "name": "${p.name}", - "title": "<#if p.title?has_content>${p.title}<#else>${p.name?replace("_", " ")}", - "text": "${page.contents}", - "tags": [ - <#list p.tags as tag> - "${tag}"<#if tag_has_next>, - - ], - "createdOn": "<@dateFormat p.created />", - <#if p.createdBy??> - <#assign createdBy = (p.createdBy.properties.firstName!"" + " " + p.createdBy.properties.lastName!"")?trim> - <#assign createdByUser = p.createdBy.properties.userName> - <#else> - <#assign createdBy=""> - <#assign createdByUser=""> - - "createdBy": "${createdBy}", - "createdByUser": "${createdByUser}", - "modifiedOn": "<@dateFormat p.modified />", - <#if p.modifiedBy??> - <#assign modifiedBy = (p.modifiedBy.properties.firstName!"" + " " + p.modifiedBy.properties.lastName!"")?trim> - <#assign modifiedByUser = p.modifiedBy.properties.userName> - <#else> - <#assign modifiedBy=""> - <#assign modifiedByUser=""> - - "modifiedBy": "${modifiedBy}", - "modifiedByUser": "${modifiedByUser}", - "permissions": - { - "edit": ${node.hasPermission("Write")?string}, - "delete": ${node.hasPermission("Delete")?string} - } - }<#if p_has_next>, - - - ] + "totalPages": ${wiki.pages?size?c}, + "permissions": + { + "create": ${wiki.container.hasPermission("CreateChildren")?string} + }, + "pages": + [ + <#if pageMetaOnly> + <#list wiki.pages as p> + <#assign page = p.page> + { + "name": "${p.name}", + "title": "<#if p.title?has_content>${p.title}<#else>${p.name?replace("_", " ")}", + }<#if p_has_next>, + + <#else> + <#list wiki.pages?sort_by(['modified'])?reverse as p> + <#assign node = p.node> + <#assign page = p.page> + { + "name": "${p.name}", + "title": "<#if p.title?has_content>${p.title}<#else>${p.name?replace("_", " ")}", + "text": "${page.contents}", + "tags": [ + <#list p.tags as tag> + "${tag}"<#if tag_has_next>, + + ], + "createdOn": "<@dateFormat p.created />", + <#if p.createdBy??> + <#assign createdBy = (p.createdBy.properties.firstName!"" + " " + p.createdBy.properties.lastName!"")?trim> + <#assign createdByUser = p.createdBy.properties.userName> + <#else> + <#assign createdBy=""> + <#assign createdByUser=""> + + "createdBy": "${createdBy}", + "createdByUser": "${createdByUser}", + "modifiedOn": "<@dateFormat p.modified />", + <#if p.modifiedBy??> + <#assign modifiedBy = (p.modifiedBy.properties.firstName!"" + " " + p.modifiedBy.properties.lastName!"")?trim> + <#assign modifiedByUser = p.modifiedBy.properties.userName> + <#else> + <#assign modifiedBy=""> + <#assign modifiedByUser=""> + + "modifiedBy": "${modifiedBy}", + "modifiedByUser": "${modifiedByUser}", + "permissions": + { + "edit": ${node.hasPermission("Write")?string}, + "delete": ${node.hasPermission("Delete")?string} + } + }<#if p_has_next>, + + + ], + "pageTitles": + [ + <#if wiki.pageTitles??> + <#list wiki.pageTitles as title> + "${title}"<#if title_has_next>, + + + ] } diff --git a/config/alfresco/web-scripts-application-context.xml b/config/alfresco/web-scripts-application-context.xml index d4cbdff724..f952cc4806 100644 --- a/config/alfresco/web-scripts-application-context.xml +++ b/config/alfresco/web-scripts-application-context.xml @@ -492,6 +492,7 @@ + @@ -501,6 +502,7 @@ + @@ -510,6 +512,7 @@ + @@ -1056,6 +1059,7 @@ + ${trashcan.MaxSize} children = nodeService.getChildAssocs(archiveRootNode); + List children = nodeService.getChildAssocs(archiveRootNode, null, null, maxSizeView, true); // We must get the sys:archived children in order of their archiving. Comparator archivedNodeSorter = new ArchivedDateComparator(); diff --git a/source/java/org/alfresco/repo/web/scripts/bean/ADMRemoteStore.java b/source/java/org/alfresco/repo/web/scripts/bean/ADMRemoteStore.java index bb86479ef1..af1ad1dcad 100644 --- a/source/java/org/alfresco/repo/web/scripts/bean/ADMRemoteStore.java +++ b/source/java/org/alfresco/repo/web/scripts/bean/ADMRemoteStore.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2012 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -26,7 +26,9 @@ import java.io.Writer; import java.net.SocketException; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; @@ -47,6 +49,7 @@ import org.alfresco.query.PagingRequest; import org.alfresco.query.PagingResults; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.model.filefolder.HiddenAspect; +import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.security.permissions.AccessDeniedException; @@ -119,6 +122,7 @@ public class ADMRemoteStore extends BaseRemoteStore protected SiteService siteService; protected ContentService contentService; protected HiddenAspect hiddenAspect; + private BehaviourFilter behaviourFilter; /** * Date format pattern used to parse HTTP date headers in RFC 1123 format. @@ -179,7 +183,12 @@ public class ADMRemoteStore extends BaseRemoteStore { this.hiddenAspect = hiddenAspect; } - + + public void setBehaviourFilter(BehaviourFilter behaviourFilter) + { + this.behaviourFilter = behaviourFilter; + } + /** * Gets the last modified timestamp for the document. *

@@ -459,17 +468,30 @@ public class ADMRemoteStore extends BaseRemoteStore { throw new IllegalStateException("Unable to aquire parent folder reference for path: " + path); } - FileInfo fileInfo = fileFolderService.create( - parentFolder.getNodeRef(), encpath.substring(off + 1), ContentModel.TYPE_CONTENT); - Map aspectProperties = new HashMap(1, 1.0f); - aspectProperties.put(ContentModel.PROP_IS_INDEXED, false); - unprotNodeService.addAspect(fileInfo.getNodeRef(), ContentModel.ASPECT_INDEX_CONTROL, aspectProperties); - ContentWriter writer = contentService.getWriter( - fileInfo.getNodeRef(), ContentModel.PROP_CONTENT, true); - writer.guessMimetype(fileInfo.getName()); - writer.putContent(content); - if (logger.isDebugEnabled()) - logger.debug("createDocument: " + fileInfo.toString()); + + // ALF-17729 / ALF-17796 - disable auditable on parent folder + NodeRef parentFolderRef = parentFolder.getNodeRef(); + behaviourFilter.disableBehaviour(parentFolderRef, ContentModel.ASPECT_AUDITABLE); + + try + { + FileInfo fileInfo = fileFolderService.create( + parentFolderRef, encpath.substring(off + 1), ContentModel.TYPE_CONTENT); + Map aspectProperties = new HashMap(1, 1.0f); + aspectProperties.put(ContentModel.PROP_IS_INDEXED, false); + unprotNodeService.addAspect(fileInfo.getNodeRef(), ContentModel.ASPECT_INDEX_CONTROL, aspectProperties); + ContentWriter writer = contentService.getWriter( + fileInfo.getNodeRef(), ContentModel.PROP_CONTENT, true); + writer.guessMimetype(fileInfo.getName()); + writer.putContent(content); + if (logger.isDebugEnabled()) + logger.debug("createDocument: " + fileInfo.toString()); + } + finally + { + behaviourFilter.enableBehaviour(parentFolderRef, ContentModel.ASPECT_AUDITABLE); + } + return null; } }, runAsUser); @@ -532,7 +554,20 @@ public class ADMRemoteStore extends BaseRemoteStore { final NodeRef fileRef = fileInfo.getNodeRef(); this.nodeService.addAspect(fileRef, ContentModel.ASPECT_TEMPORARY, null); - this.nodeService.deleteNode(fileRef); + + // ALF-17729 + NodeRef parentFolderRef = unprotNodeService.getPrimaryParent(fileRef).getParentRef(); + behaviourFilter.disableBehaviour(parentFolderRef, ContentModel.ASPECT_AUDITABLE); + + try + { + this.nodeService.deleteNode(fileRef); + } + finally + { + behaviourFilter.enableBehaviour(parentFolderRef, ContentModel.ASPECT_AUDITABLE); + } + if (logger.isDebugEnabled()) logger.debug("deleteDocument: " + fileInfo.toString()); } @@ -736,11 +771,14 @@ public class ADMRemoteStore extends BaseRemoteStore if (create) { // ensure folders exist down to the specified parent + // ALF-17729 / ALF-17796 - disable auditable on parent folders result = FileFolderUtil.makeFolders( this.fileFolderService, surfConfigRef, isFolder ? pathElements : pathElements.subList(0, pathElements.size() - 1), - ContentModel.TYPE_FOLDER); + ContentModel.TYPE_FOLDER, + behaviourFilter, + new HashSet(Arrays.asList(new QName[]{ContentModel.ASPECT_AUDITABLE}))); } else { diff --git a/source/java/org/alfresco/repo/web/scripts/discussion/DiscussionRestApiTest.java b/source/java/org/alfresco/repo/web/scripts/discussion/DiscussionRestApiTest.java index e2f55c3fff..d259440f35 100644 --- a/source/java/org/alfresco/repo/web/scripts/discussion/DiscussionRestApiTest.java +++ b/source/java/org/alfresco/repo/web/scripts/discussion/DiscussionRestApiTest.java @@ -50,6 +50,7 @@ import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.PropertyMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.json.JSONArray; import org.json.JSONObject; import org.springframework.extensions.webscripts.Status; import org.springframework.extensions.webscripts.TestWebScriptServer.DeleteRequest; @@ -1205,4 +1206,35 @@ public class DiscussionRestApiTest extends BaseWebScriptTest assertEquals(1, result.getJSONArray("items").getJSONObject(0).getInt("replyCount")); } + /** + * https://issues.alfresco.com/jira/browse/ALF-17443 reports that site contributors are unable + * to edit replies that they have made. + */ + public void testContributorCanEditReply() throws Exception + { + authenticationComponent.setCurrentUser(USER_ONE); + JSONObject post = createSitePost("Can contributors edit replies?", "The title says it all", Status.STATUS_OK); + NodeRef postNodeRef = new NodeRef(post.getString("nodeRef")); + + authenticationComponent.setCurrentUser(USER_TWO); + JSONObject reply = createReply(postNodeRef, "", "Let's see.", Status.STATUS_OK); + NodeRef replyNodeRef = new NodeRef(reply.getString("nodeRef")); + updateComment(replyNodeRef, "", "Yes I can", Status.STATUS_OK); + + authenticationComponent.setCurrentUser(USER_ONE); + + post = getPost(postNodeRef, Status.STATUS_OK); + assertEquals("Can contributors edit replies?", post.getString("title")); + assertEquals("The title says it all", post.getString("content")); + assertEquals(1, post.getInt("replyCount")); + + JSONObject replies = getReplies(postNodeRef, Status.STATUS_OK); + JSONArray items = replies.getJSONArray("items"); + assertEquals(1, items.length()); + + reply = items.getJSONObject(0); + assertEquals("Yes I can", reply.getString("content")); + + } + } diff --git a/source/java/org/alfresco/repo/web/scripts/discussion/ForumPostPut.java b/source/java/org/alfresco/repo/web/scripts/discussion/ForumPostPut.java index a79b45d7b8..c3394e7f83 100644 --- a/source/java/org/alfresco/repo/web/scripts/discussion/ForumPostPut.java +++ b/source/java/org/alfresco/repo/web/scripts/discussion/ForumPostPut.java @@ -89,15 +89,17 @@ public class ForumPostPut extends AbstractDiscussionWebScript private void doUpdatePost(PostInfo post, TopicInfo topic, WebScriptRequest req, JSONObject json) { + boolean updateTopic = false; // Fetch the details from the JSON - - // Update the titles on the post and it's topic + + // Update the titles on the post and it's topic if (json.containsKey("title")) { String title = (String)json.get("title"); post.setTitle(title); if (title.length() > 0) { + updateTopic = true; topic.setTitle(title); } } @@ -118,10 +120,14 @@ public class ForumPostPut extends AbstractDiscussionWebScript { topic.getTags().addAll(tags); } + updateTopic = true; } // Save the topic and the post - discussionService.updateTopic(topic); + if (updateTopic == true) + { + discussionService.updateTopic(topic); + } discussionService.updatePost(post); } } diff --git a/source/java/org/alfresco/repo/web/scripts/solr/SOLRSerializer.java b/source/java/org/alfresco/repo/web/scripts/solr/SOLRSerializer.java index 28fe1f100a..ad28845912 100644 --- a/source/java/org/alfresco/repo/web/scripts/solr/SOLRSerializer.java +++ b/source/java/org/alfresco/repo/web/scripts/solr/SOLRSerializer.java @@ -128,7 +128,9 @@ import org.springframework.extensions.webscripts.json.JSONUtils; // Treat it as text return new PropertyValue(true, serializeToJSONString(value)); } - else if (propertyDef.isMultiValued()) + DataTypeDefinition dataType = propertyDef.getDataType(); + QName dataTypeName = dataType.getName(); + if (propertyDef.isMultiValued()) { if(!(value instanceof Collection)) { @@ -140,7 +142,28 @@ import org.springframework.extensions.webscripts.json.JSONUtils; JSONArray body = new JSONArray(); for(Serializable o : c) { - body.put(serializeToJSONString(o)); + if(dataTypeName.equals(DataTypeDefinition.MLTEXT)) + { + MLText source = (MLText)o; + JSONArray array = new JSONArray(); + for(Locale locale : source.getLocales()) + { + JSONObject json = new JSONObject(); + json.put("locale", DefaultTypeConverter.INSTANCE.convert(String.class, locale)); + json.put("value", source.getValue(locale)); + array.put(json); + } + body.put(array); + } + else if(dataTypeName.equals(DataTypeDefinition.CONTENT)) + { + throw new RuntimeException("Multi-valued content properties are not supported"); + } + else + { + body.put(serializeToJSONString(o)); + } + } return new PropertyValue(false, body.toString()); @@ -148,8 +171,6 @@ import org.springframework.extensions.webscripts.json.JSONUtils; else { boolean encodeString = true; - DataTypeDefinition dataType = propertyDef.getDataType(); - QName dataTypeName = dataType.getName(); if(dataTypeName.equals(DataTypeDefinition.MLTEXT)) { encodeString = false; diff --git a/source/java/org/alfresco/repo/web/scripts/wiki/WikiPageListGet.java b/source/java/org/alfresco/repo/web/scripts/wiki/WikiPageListGet.java index 6ffd82ea56..27d186bb90 100644 --- a/source/java/org/alfresco/repo/web/scripts/wiki/WikiPageListGet.java +++ b/source/java/org/alfresco/repo/web/scripts/wiki/WikiPageListGet.java @@ -174,6 +174,20 @@ public class WikiPageListGet extends AbstractWikiWebScript wiki.put("pages", items); // Old style wiki.put("container", container); + if (userFiltering) + { + // We need to get all the wiki pages for "My Pages" filter otherwise + // the links for renamed wiki pages won't be rendered correctly, + // which were created by other users + pages = wikiService.listWikiPages(site.getShortName(), paging); + List pageTitles = new ArrayList(pages.getPage().size()); + for (WikiPageInfo page : pages.getPage()) + { + pageTitles.add(page.getTitle()); + } + wiki.put("pageTitles", pageTitles); + } + Map model = new HashMap(); model.put("data", data); // New style model.put("wiki", wiki); diff --git a/source/java/org/alfresco/repo/web/scripts/workflow/AbstractWorkflowRestApiTest.java b/source/java/org/alfresco/repo/web/scripts/workflow/AbstractWorkflowRestApiTest.java index 452bb9f41a..1a40973c12 100644 --- a/source/java/org/alfresco/repo/web/scripts/workflow/AbstractWorkflowRestApiTest.java +++ b/source/java/org/alfresco/repo/web/scripts/workflow/AbstractWorkflowRestApiTest.java @@ -307,13 +307,14 @@ public abstract class AbstractWorkflowRestApiTest extends BaseWebScriptTest // Retrieve tasks using the workflow instance String baseUrl = MessageFormat.format(URL_WORKFLOW_TASKS, workflowId); - // Check returns the completed start task and the current task. + // Check returns the completed start task. String adhocTaskId = task.getId(); - checkTasksMatch(baseUrl, startTaskId, adhocTaskId); + checkTasksMatch(baseUrl, startTaskId); String completedUrl = baseUrl + "?state=" + WorkflowTaskState.COMPLETED; checkTasksMatch(completedUrl, startTaskId); + personManager.setUser(USER2); String inProgressUrl = baseUrl + "?state=" + WorkflowTaskState.IN_PROGRESS; checkTasksMatch(inProgressUrl, adhocTaskId); diff --git a/source/java/org/alfresco/repo/web/scripts/workflow/TaskInstancesGet.java b/source/java/org/alfresco/repo/web/scripts/workflow/TaskInstancesGet.java index d80279d2c5..0053e870cb 100644 --- a/source/java/org/alfresco/repo/web/scripts/workflow/TaskInstancesGet.java +++ b/source/java/org/alfresco/repo/web/scripts/workflow/TaskInstancesGet.java @@ -32,8 +32,9 @@ import javax.servlet.http.HttpServletResponse; import org.alfresco.repo.workflow.WorkflowModel; import org.alfresco.service.cmr.workflow.WorkflowTask; import org.alfresco.service.cmr.workflow.WorkflowTaskQuery; -import org.alfresco.service.cmr.workflow.WorkflowTaskState; import org.alfresco.service.cmr.workflow.WorkflowTaskQuery.OrderBy; +import org.alfresco.service.cmr.workflow.WorkflowTaskState; +import org.alfresco.util.ModelUtil; import org.springframework.extensions.webscripts.Cache; import org.springframework.extensions.webscripts.Status; import org.springframework.extensions.webscripts.WebScriptException; @@ -68,6 +69,12 @@ public class TaskInstancesGet extends AbstractWorkflowWebscript // authority is not included into filters list as it will be taken into account before filtering String authority = getAuthority(req); + if (authority == null) + { + // ALF-11036 fix, if authority argument is omitted the tasks for the current user should be returned. + authority = authenticationService.getCurrentUserName(); + } + // state is also not included into filters list, for the same reason WorkflowTaskState state = getState(req); @@ -120,8 +127,8 @@ public class TaskInstancesGet extends AbstractWorkflowWebscript // no workflow instance id is present so get all tasks if (authority != null) { - List tasks = workflowService.getAssignedTasks(authority, state); - List pooledTasks = workflowService.getPooledTasks(authority); + List tasks = workflowService.getAssignedTasks(authority, state, true); + List pooledTasks = workflowService.getPooledTasks(authority, true); if (pooledTasksOnly != null) { if (pooledTasksOnly.booleanValue()) @@ -159,18 +166,40 @@ public class TaskInstancesGet extends AbstractWorkflowWebscript } } - // filter results + int maxItems = getIntParameter(req, PARAM_MAX_ITEMS, DEFAULT_MAX_ITEMS); + int skipCount = getIntParameter(req, PARAM_SKIP_COUNT, DEFAULT_SKIP_COUNT); + int totalCount = 0; ArrayList> results = new ArrayList>(); - for (WorkflowTask task : allTasks) + + // Filter results + WorkflowTask task = null; + for(int i=0; i= skipCount && (maxItems < 0 || maxItems > results.size())) + { + // Only build the actual detail if it's in the range of items we need. This will + // drastically improve performance over paging after building the model + results.add(modelBuilder.buildSimple(task, properties)); + } } } - + + Map model = new HashMap(); + model.put("taskInstances", results); + + if (maxItems != DEFAULT_MAX_ITEMS || skipCount != DEFAULT_SKIP_COUNT) + { + // maxItems or skipCount parameter was provided so we need to include paging into response + model.put("paging", ModelUtil.buildPaging(totalCount, maxItems == DEFAULT_MAX_ITEMS ? totalCount : maxItems, skipCount)); + } + // create and return results, paginated if necessary - return createResultModel(req, "taskInstances", results); + return model; } /** diff --git a/source/java/org/alfresco/repo/web/scripts/workflow/WorkflowModelBuilder.java b/source/java/org/alfresco/repo/web/scripts/workflow/WorkflowModelBuilder.java index e313257910..848b40530f 100644 --- a/source/java/org/alfresco/repo/web/scripts/workflow/WorkflowModelBuilder.java +++ b/source/java/org/alfresco/repo/web/scripts/workflow/WorkflowModelBuilder.java @@ -192,10 +192,10 @@ public class WorkflowModelBuilder model.put(TASK_PATH, getUrl(task.getPath())); model.put(TASK_OUTCOME, getOutcome(task)); model.put(TASK_IS_POOLED, isPooled(task.getProperties())); - model.put(TASK_IS_EDITABLE, this.workflowService.isTaskEditable(task, currentUser)); - model.put(TASK_IS_REASSIGNABLE, this.workflowService.isTaskReassignable(task, currentUser)); - model.put(TASK_IS_CLAIMABLE, this.workflowService.isTaskClaimable(task, currentUser)); - model.put(TASK_IS_RELEASABLE, this.workflowService.isTaskReleasable(task, currentUser)); + model.put(TASK_IS_EDITABLE, this.workflowService.isTaskEditable(task, currentUser, false)); + model.put(TASK_IS_REASSIGNABLE, this.workflowService.isTaskReassignable(task, currentUser, false)); + model.put(TASK_IS_CLAIMABLE, this.workflowService.isTaskClaimable(task, currentUser, false)); + model.put(TASK_IS_RELEASABLE, this.workflowService.isTaskReleasable(task, currentUser, false)); Serializable owner = task.getProperties().get(ContentModel.PROP_OWNER); model.put(TASK_OWNER, getPersonModel(owner)); diff --git a/source/java/org/alfresco/repo/webdav/DeleteMethod.java b/source/java/org/alfresco/repo/webdav/DeleteMethod.java index 70084e2fe8..c9571850e0 100644 --- a/source/java/org/alfresco/repo/webdav/DeleteMethod.java +++ b/source/java/org/alfresco/repo/webdav/DeleteMethod.java @@ -107,11 +107,11 @@ public class DeleteMethod extends WebDAVMethod implements ActivityPostProducer // MNT-181: working copies and versioned nodes are hidden rather than deleted if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_WORKING_COPY) || nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE)) { - setHidden(nodeRef, true); + fileFolderService.setHidden(nodeRef, true); getDAVLockService().unlock(nodeRef); } // We just ensure already-hidden nodes are left unlocked - else if (isHidden(nodeRef)) + else if (fileFolderService.isHidden(nodeRef)) { getDAVLockService().unlock(nodeRef); } diff --git a/source/java/org/alfresco/repo/webdav/MoveMethod.java b/source/java/org/alfresco/repo/webdav/MoveMethod.java index 1c5e4fd20c..90ab31b2ee 100644 --- a/source/java/org/alfresco/repo/webdav/MoveMethod.java +++ b/source/java/org/alfresco/repo/webdav/MoveMethod.java @@ -112,7 +112,7 @@ public class MoveMethod extends HierarchicalMethod if (!destInfo.getNodeRef().equals(sourceInfo.getNodeRef())) { // ALF-7079 fix, if destInfo is a hidden shuffle target then pretend it's not there - destExists = !isHidden(destInfo.getNodeRef()); + destExists = !getFileFolderService().isHidden(destInfo.getNodeRef()); if (!hasOverWrite() && destExists) { if (logger.isDebugEnabled()) @@ -201,14 +201,14 @@ public class MoveMethod extends HierarchicalMethod if (destFileInfo != null) { copyContentOnly(sourceNodeRef, destFileInfo, fileFolderService); - setHidden(destFileInfo.getNodeRef(), false); + fileFolderService.setHidden(destFileInfo.getNodeRef(), false); if (isMove) { if (getDAVHelper().isRenameShuffle(destPath) && !getDAVHelper().isRenameShuffle(sourcePath)) { // if temporary or backup file already exists // don't delete source that is node with version history - setHidden(sourceNodeRef, true); + fileFolderService.setHidden(sourceNodeRef, true); // As per the WebDAV spec, we make sure the node is unlocked once moved getDAVHelper().getLockService().unlock(sourceNodeRef); } @@ -229,7 +229,7 @@ public class MoveMethod extends HierarchicalMethod { destFileInfo = fileFolderService.create(destParentNodeRef, name, ContentModel.TYPE_CONTENT); copyContentOnly(sourceNodeRef, destFileInfo, fileFolderService); - setHidden(sourceNodeRef, true); + fileFolderService.setHidden(sourceNodeRef, true); // As per the WebDAV spec, we make sure the node is unlocked once moved getDAVHelper().getLockService().unlock(sourceNodeRef); diff --git a/source/java/org/alfresco/repo/webdav/PropFindMethod.java b/source/java/org/alfresco/repo/webdav/PropFindMethod.java index 605a2a58bd..c3eb70b908 100644 --- a/source/java/org/alfresco/repo/webdav/PropFindMethod.java +++ b/source/java/org/alfresco/repo/webdav/PropFindMethod.java @@ -36,8 +36,10 @@ import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.model.FileNotFoundException; import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.cmr.repository.datatype.TypeConverter; +import org.alfresco.service.namespace.InvalidQNameException; import org.alfresco.service.namespace.QName; import org.dom4j.DocumentHelper; import org.dom4j.io.OutputFormat; @@ -197,7 +199,7 @@ public class PropFindMethod extends WebDAVMethod // A node hidden during a 'shuffle' operation - send a 404 error back to the client, as some Mac clients need // this - if (isHidden(pathNodeInfo.getNodeRef())) + if (getFileFolderService().isHidden(pathNodeInfo.getNodeRef())) { throw new WebDAVServerException(HttpServletResponse.SC_NOT_FOUND); } @@ -429,6 +431,7 @@ public class PropFindMethod extends WebDAVMethod { // Get the properties for the node Map props = nodeInfo.getProperties(); + Map deadProperties = null; // Output the start of the properties element Attributes nullAttr = getDAVHelper().getNullAttributes(); @@ -608,15 +611,23 @@ public class PropFindMethod extends WebDAVMethod else { // Look in the custom properties - - // TODO: Custom properties lookup - // String qualifiedName = propNamespaceUri + WebDAV.NAMESPACE_SEPARATOR + propName; - + +// String qualifiedName = propNamespaceUri + WebDAV.NAMESPACE_SEPARATOR + propName; + String value = (String) nodeInfo.getProperties().get(property.createQName()); if (value == null) { - propertiesNotFound.add(property); - } + if (deadProperties == null) + { + deadProperties = loadDeadProperties(nodeInfo.getNodeRef()); + } + value = deadProperties.get(property.createQName()); + } + + if (value == null) + { + propertiesNotFound.add(property); + } else { if (property.hasNamespaceName()) @@ -945,6 +956,72 @@ public class PropFindMethod extends WebDAVMethod } } + /** + * Loads all dead properties persisted on the node + * + * @param nodeRef + * @return the map of all dead properties + */ + @SuppressWarnings("unchecked") + protected Map loadDeadProperties(NodeRef nodeRef) + { + Map result; + + List deadProperties = (List)getNodeService().getProperty(nodeRef, ContentModel.PROP_DEAD_PROPERTIES); + + if (deadProperties != null) + { + result = new HashMap(deadProperties.size() * 2); + + for (String deadProperty : deadProperties) + { + int last = deadProperty.length() - 1; + int pos = deadProperty.indexOf(QName.NAMESPACE_END); + if (pos == -1 || pos == last) + { + continue; + } + pos = deadProperty.indexOf(':', pos + 1); + if (pos == -1 || pos == last) + { + continue; + } + try + { + result.put(QName.createQName(deadProperty.substring(0, pos)), deadProperty.substring(pos + 1)); + } + catch (InvalidQNameException e) + { + // Skip and continue + } + } + } + else + { + result = new HashMap(7); + } + + return result; + } + + /** + * Persists dead properties for specified resource + * + * @param nodeRef specified resource + * @param deadProperties the properties to persist + */ + protected void persistDeadProperties(NodeRef nodeRef, Map deadProperties) + { + List listToPersist = new ArrayList(deadProperties.size()); + + for (Map.Entry entry: deadProperties.entrySet()) + { + listToPersist.add(entry.getKey().toString() + ':' + entry.getValue()); + } + + getNodeService().setProperty(nodeRef, ContentModel.PROP_DEAD_PROPERTIES, (Serializable)listToPersist); + } + /** * Output the lockentry element of the specified type * @param xml XMLWriter diff --git a/source/java/org/alfresco/repo/webdav/PropPatchMethod.java b/source/java/org/alfresco/repo/webdav/PropPatchMethod.java index bafc0e3437..e3619321c7 100644 --- a/source/java/org/alfresco/repo/webdav/PropPatchMethod.java +++ b/source/java/org/alfresco/repo/webdav/PropPatchMethod.java @@ -1,5 +1,5 @@ -/* - * Copyright (C) 2005-2013 Alfresco Software Limited. +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -14,435 +14,464 @@ * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License - * along with Alfresco. If not, see . - */ -package org.alfresco.repo.webdav; - -import java.util.ArrayList; - -import javax.servlet.http.HttpServletResponse; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.service.cmr.model.FileInfo; -import org.alfresco.service.cmr.model.FileNotFoundException; -import org.dom4j.DocumentHelper; -import org.dom4j.io.XMLWriter; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.xml.sax.Attributes; - -/** - * Implements the WebDAV PROPPATCH method - * - * @author Ivan Rybnikov - */ -public class PropPatchMethod extends PropFindMethod -{ - // Properties to patch - protected ArrayList m_propertyActions = null; - private String strHRef; - private WebDAVProperty failedProperty; - private String basePath; - - /** - * @return Returns false always - */ - @Override - protected boolean isReadOnly() - { - return false; - } - - @Override - protected void executeImpl() throws WebDAVServerException, Exception - { - FileInfo pathNodeInfo = null; - try - { - // Check that the path exists - pathNodeInfo = getNodeForPath(getRootNodeRef(), m_strPath); - } - catch (FileNotFoundException e) - { - // The path is not valid - send a 404 error back to the client - throw new WebDAVServerException(HttpServletResponse.SC_NOT_FOUND); - } - - checkNode(pathNodeInfo); - - // Create the path for the current location in the tree - StringBuilder baseBuild = new StringBuilder(256); - baseBuild.append(getPath()); - if (baseBuild.length() == 0 || baseBuild.charAt(baseBuild.length() - 1) != WebDAVHelper.PathSeperatorChar) - { - baseBuild.append(WebDAVHelper.PathSeperatorChar); - } - basePath = baseBuild.toString(); - - // Build the href string for the current node - boolean isFolder = pathNodeInfo.isFolder(); - strHRef = getURLForPath(m_request, basePath, isFolder); - - // Do the real work: patch the properties - patchProperties(pathNodeInfo, basePath); - } - - - @Override - protected void generateResponseImpl() throws Exception - { - m_response.setStatus(WebDAV.WEBDAV_SC_MULTI_STATUS); - - // Set the response content type - m_response.setContentType(WebDAV.XML_CONTENT_TYPE); - - // Create multistatus response - XMLWriter xml = createXMLWriter(); - - xml.startDocument(); - - String nsdec = generateNamespaceDeclarations(m_namespaces); - xml.startElement( - WebDAV.DAV_NS, - WebDAV.XML_MULTI_STATUS + nsdec, - WebDAV.XML_NS_MULTI_STATUS + nsdec, - getDAVHelper().getNullAttributes()); - - // Output the response block for the current node - xml.startElement( - WebDAV.DAV_NS, - WebDAV.XML_RESPONSE, - WebDAV.XML_NS_RESPONSE, - getDAVHelper().getNullAttributes()); - - xml.startElement(WebDAV.DAV_NS, WebDAV.XML_HREF, WebDAV.XML_NS_HREF, getDAVHelper().getNullAttributes()); - xml.write(strHRef); - xml.endElement(WebDAV.DAV_NS, WebDAV.XML_HREF, WebDAV.XML_NS_HREF); - - if (failedProperty != null) - { - generateError(xml); - } - - for (PropertyAction propertyAction : m_propertyActions) - { - WebDAVProperty property = propertyAction.getProperty(); - int statusCode = propertyAction.getStatusCode(); - String statusCodeDescription = propertyAction.getStatusCodeDescription(); - generatePropertyResponse(xml, property, statusCode, statusCodeDescription); - } - - // Close off the response element - xml.endElement(WebDAV.DAV_NS, WebDAV.XML_RESPONSE, WebDAV.XML_NS_RESPONSE); - - // Close the outer XML element - xml.endElement(WebDAV.DAV_NS, WebDAV.XML_MULTI_STATUS, WebDAV.XML_NS_MULTI_STATUS); - - // Send remaining data - flushXML(xml); - } - - - /** - * Parse the request body - * - * @exception WebDAVServerException - */ - @Override - protected void parseRequestBody() throws WebDAVServerException - { - Document body = getRequestBodyAsDocument(); - if (body != null) - { - Element rootElement = body.getDocumentElement(); - NodeList childList = rootElement.getChildNodes(); - - m_propertyActions = new ArrayList(); - - for (int i = 0; i < childList.getLength(); i++) - { - Node currentNode = childList.item(i); - switch (currentNode.getNodeType()) - { - case Node.TEXT_NODE: - break; - case Node.ELEMENT_NODE: - if (currentNode.getNodeName().endsWith(WebDAV.XML_SET) || currentNode.getNodeName().endsWith(WebDAV.XML_REMOVE)) - { - NodeList propertiesList = currentNode.getChildNodes(); - - for (int j = 0; j < propertiesList.getLength(); j++) - { - Node propertiesNode = propertiesList.item(j); - switch (propertiesNode.getNodeType()) - { - case Node.TEXT_NODE: - break; - case Node.ELEMENT_NODE: - if (propertiesNode.getNodeName().endsWith(WebDAV.XML_PROP)) - { - NodeList propList = propertiesNode.getChildNodes(); - - for (int k = 0; k < propList.getLength(); k++) - { - Node propNode = propList.item(k); - switch (propNode.getNodeType()) - { - case Node.TEXT_NODE: - break; - case Node.ELEMENT_NODE: - int action = currentNode.getNodeName().endsWith(WebDAV.XML_SET) ? PropertyAction.SET : PropertyAction.REMOVE; - WebDAVProperty prop = createProperty(propNode); - if (prop != null) - { - m_propertyActions.add(new PropertyAction(action, prop)); - } - break; - } - } - } - break; - } - } - } - break; - } - } - - } - - } - - /** - * Parse the request headers - * - * @exception WebDAVServerException - */ - @Override - protected void parseRequestHeaders() throws WebDAVServerException - { - // Parse Lock tokens and ETags, if any - parseIfHeader(); - } - - /** - * Creates a WebDAVProperty from the given XML node - */ - protected WebDAVProperty createProperty(Node node) - { - WebDAVProperty property = super.createProperty(node); - - String strValue = null; - Node value = node.getFirstChild(); - if (value != null) - { - strValue = value.getNodeValue(); - } - property.setValue(strValue); - - return property; - } - - - protected void patchProperties(FileInfo nodeInfo, String path) throws WebDAVServerException - { - failedProperty = null; - for (PropertyAction action : m_propertyActions) - { - if (action.getProperty().isProtected()) - { - failedProperty = action.getProperty(); - break; - } - } - - for (PropertyAction propertyAction : m_propertyActions) - { - int statusCode; - String statusCodeDescription; - WebDAVProperty property = propertyAction.getProperty(); - - if (failedProperty == null) - { - if (PropertyAction.SET == propertyAction.getAction()) - { - getNodeService().setProperty(nodeInfo.getNodeRef(), property.createQName(), property.getValue()); - } - else if (PropertyAction.REMOVE == propertyAction.getAction()) - { - getNodeService().removeProperty(nodeInfo.getNodeRef(), property.createQName()); - } - else - { - throw new WebDAVServerException(HttpServletResponse.SC_BAD_REQUEST); - } - statusCode = HttpServletResponse.SC_OK; - statusCodeDescription = WebDAV.SC_OK_DESC; - } - else if (failedProperty == property) - { - statusCode = HttpServletResponse.SC_FORBIDDEN; - statusCodeDescription = WebDAV.SC_FORBIDDEN_DESC; - } - else - { - statusCode = WebDAV.WEBDAV_SC_FAILED_DEPENDENCY; - statusCodeDescription = WebDAV.WEBDAV_SC_FAILED_DEPENDENCY_DESC; - } - - propertyAction.setResult(statusCode, statusCodeDescription); - } - } - - - /** - * Generates the XML response for a PROPFIND request that asks for a list of - * all known properties - * - * @param xml XMLWriter - * @param node NodeRef - * @param isDir boolean - */ - protected void generatePropertyResponse(XMLWriter xml, WebDAVProperty property, int status, String description) - { - try - { - // Output the start of the properties element - Attributes nullAttr = getDAVHelper().getNullAttributes(); - - xml.startElement(WebDAV.DAV_NS, WebDAV.XML_PROPSTAT, WebDAV.XML_NS_PROPSTAT, nullAttr); - - // Output property name - xml.startElement(WebDAV.DAV_NS, WebDAV.XML_PROP, WebDAV.XML_NS_PROP, nullAttr); - if (property.hasNamespaceName()) - { - xml.write(DocumentHelper.createElement(property.getNamespaceName() + WebDAV.NAMESPACE_SEPARATOR + property.getName())); - } - else - { - xml.write(DocumentHelper.createElement(property.getName())); - } - xml.endElement(WebDAV.DAV_NS, WebDAV.XML_PROP, WebDAV.XML_NS_PROP); - - // Output action result status - xml.startElement(WebDAV.DAV_NS, WebDAV.XML_STATUS, WebDAV.XML_NS_STATUS, nullAttr); - xml.write(WebDAV.HTTP1_1 + " " + status + " " + description); - xml.endElement(WebDAV.DAV_NS, WebDAV.XML_STATUS, WebDAV.XML_NS_STATUS); - - xml.endElement(WebDAV.DAV_NS, WebDAV.XML_PROPSTAT, WebDAV.XML_NS_PROPSTAT); - } - catch (Exception ex) - { - // Convert to a runtime exception - throw new AlfrescoRuntimeException("XML processing error", ex); - } - } - - - /** - * Generates the error tag - * - * @param xml XMLWriter - */ - protected void generateError(XMLWriter xml) - { - try - { - // Output the start of the error element - Attributes nullAttr = getDAVHelper().getNullAttributes(); - - xml.startElement(WebDAV.DAV_NS, WebDAV.XML_ERROR, WebDAV.XML_NS_ERROR, nullAttr); - // Output error - xml.write(DocumentHelper.createElement(WebDAV.XML_NS_CANNOT_MODIFY_PROTECTED_PROPERTY)); - - xml.endElement(WebDAV.DAV_NS, WebDAV.XML_ERROR, WebDAV.XML_NS_ERROR); - } - catch (Exception ex) - { - // Convert to a runtime exception - throw new AlfrescoRuntimeException("XML processing error", ex); - } - } - - - /** - * Stores information about PROPPATCH action(set or remove) an according property. - * - * @author Ivan Rybnikov - */ - protected class PropertyAction - { - public static final int SET = 0; - public static final int REMOVE = 1; - - // Property on which action should be performed - private WebDAVProperty property; - - // Action - private int action; - - private int statusCode; - - private String statusCodeDescription; - - /** - * Constructor - * - * @param action - * @param property - */ - public PropertyAction(int action, WebDAVProperty property) - { - this.action = action; - this.property = property; - } - - public void setResult(int statusCode, String statusCodeDescription) - { - this.statusCode = statusCode; - this.statusCodeDescription = statusCodeDescription; - } - - public int getStatusCode() - { - return this.statusCode; - } - - public String getStatusCodeDescription() - { - return this.statusCodeDescription; - } - - public int getAction() - { - return action; - } - - public WebDAVProperty getProperty() - { - return property; - } - - public String toString() - { - StringBuilder str = new StringBuilder(); - - str.append("["); - str.append("action="); - str.append(getAction() == 0 ? "SET" : "REMOVE"); - str.append(",property="); - str.append(getProperty()); - str.append(",statusCode="); - str.append(getStatusCode()); - str.append(",statusCodeDescription="); - str.append(getStatusCodeDescription()); - str.append("]"); - - return str.toString(); - } - } - - -} + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.webdav; + +import java.util.ArrayList; +import java.util.Map; + +import javax.servlet.http.HttpServletResponse; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.model.FileNotFoundException; +import org.alfresco.service.namespace.QName; +import org.dom4j.DocumentHelper; +import org.dom4j.io.XMLWriter; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.Attributes; + +/** + * Implements the WebDAV PROPPATCH method + * + * @author Ivan Rybnikov + */ +public class PropPatchMethod extends PropFindMethod +{ + // Properties to patch + protected ArrayList m_propertyActions = null; + private String strHRef; + private WebDAVProperty failedProperty; + private String basePath; + + /** + * @return Returns false always + */ + @Override + protected boolean isReadOnly() + { + return false; + } + + @Override + protected void executeImpl() throws WebDAVServerException, Exception + { + FileInfo pathNodeInfo = null; + try + { + // Check that the path exists + pathNodeInfo = getNodeForPath(getRootNodeRef(), m_strPath); + } + catch (FileNotFoundException e) + { + // The path is not valid - send a 404 error back to the client + throw new WebDAVServerException(HttpServletResponse.SC_NOT_FOUND); + } + + checkNode(pathNodeInfo); + + // Create the path for the current location in the tree + StringBuilder baseBuild = new StringBuilder(256); + baseBuild.append(getPath()); + if (baseBuild.length() == 0 || baseBuild.charAt(baseBuild.length() - 1) != WebDAVHelper.PathSeperatorChar) + { + baseBuild.append(WebDAVHelper.PathSeperatorChar); + } + basePath = baseBuild.toString(); + + // Build the href string for the current node + boolean isFolder = pathNodeInfo.isFolder(); + strHRef = getURLForPath(m_request, basePath, isFolder); + + // Do the real work: patch the properties + patchProperties(pathNodeInfo, basePath); + } + + + @Override + protected void generateResponseImpl() throws Exception + { + m_response.setStatus(WebDAV.WEBDAV_SC_MULTI_STATUS); + + // Set the response content type + m_response.setContentType(WebDAV.XML_CONTENT_TYPE); + + // Create multistatus response + XMLWriter xml = createXMLWriter(); + + xml.startDocument(); + + String nsdec = generateNamespaceDeclarations(m_namespaces); + xml.startElement( + WebDAV.DAV_NS, + WebDAV.XML_MULTI_STATUS + nsdec, + WebDAV.XML_NS_MULTI_STATUS + nsdec, + getDAVHelper().getNullAttributes()); + + // Output the response block for the current node + xml.startElement( + WebDAV.DAV_NS, + WebDAV.XML_RESPONSE, + WebDAV.XML_NS_RESPONSE, + getDAVHelper().getNullAttributes()); + + xml.startElement(WebDAV.DAV_NS, WebDAV.XML_HREF, WebDAV.XML_NS_HREF, getDAVHelper().getNullAttributes()); + xml.write(strHRef); + xml.endElement(WebDAV.DAV_NS, WebDAV.XML_HREF, WebDAV.XML_NS_HREF); + + if (failedProperty != null) + { + generateError(xml); + } + + for (PropertyAction propertyAction : m_propertyActions) + { + WebDAVProperty property = propertyAction.getProperty(); + int statusCode = propertyAction.getStatusCode(); + String statusCodeDescription = propertyAction.getStatusCodeDescription(); + generatePropertyResponse(xml, property, statusCode, statusCodeDescription); + } + + // Close off the response element + xml.endElement(WebDAV.DAV_NS, WebDAV.XML_RESPONSE, WebDAV.XML_NS_RESPONSE); + + // Close the outer XML element + xml.endElement(WebDAV.DAV_NS, WebDAV.XML_MULTI_STATUS, WebDAV.XML_NS_MULTI_STATUS); + + // Send remaining data + flushXML(xml); + } + + + /** + * Parse the request body + * + * @exception WebDAVServerException + */ + @Override + protected void parseRequestBody() throws WebDAVServerException + { + Document body = getRequestBodyAsDocument(); + if (body != null) + { + Element rootElement = body.getDocumentElement(); + NodeList childList = rootElement.getChildNodes(); + + m_propertyActions = new ArrayList(); + + for (int i = 0; i < childList.getLength(); i++) + { + Node currentNode = childList.item(i); + switch (currentNode.getNodeType()) + { + case Node.TEXT_NODE: + break; + case Node.ELEMENT_NODE: + if (currentNode.getNodeName().endsWith(WebDAV.XML_SET) || currentNode.getNodeName().endsWith(WebDAV.XML_REMOVE)) + { + NodeList propertiesList = currentNode.getChildNodes(); + + for (int j = 0; j < propertiesList.getLength(); j++) + { + Node propertiesNode = propertiesList.item(j); + switch (propertiesNode.getNodeType()) + { + case Node.TEXT_NODE: + break; + case Node.ELEMENT_NODE: + if (propertiesNode.getNodeName().endsWith(WebDAV.XML_PROP)) + { + NodeList propList = propertiesNode.getChildNodes(); + + for (int k = 0; k < propList.getLength(); k++) + { + Node propNode = propList.item(k); + switch (propNode.getNodeType()) + { + case Node.TEXT_NODE: + break; + case Node.ELEMENT_NODE: + int action = currentNode.getNodeName().endsWith(WebDAV.XML_SET) ? PropertyAction.SET : PropertyAction.REMOVE; + m_propertyActions.add(new PropertyAction(action, createProperty(propNode))); + break; + } + } + } + break; + } + } + } + break; + } + } + + } + + } + + /** + * Parse the request headers + * + * @exception WebDAVServerException + */ + @Override + protected void parseRequestHeaders() throws WebDAVServerException + { + // Parse Lock tokens and ETags, if any + parseIfHeader(); + } + + /** + * Creates a WebDAVProperty from the given XML node + */ + protected WebDAVProperty createProperty(Node node) + { + WebDAVProperty property = super.createProperty(node); + + String strValue = null; + Node value = node.getFirstChild(); + if (value != null) + { + strValue = value.getNodeValue(); + } + property.setValue(strValue); + + return property; + } + + + protected void patchProperties(FileInfo nodeInfo, String path) throws WebDAVServerException + { + failedProperty = null; + for (PropertyAction action : m_propertyActions) + { + if (action.getProperty().isProtected()) + { + failedProperty = action.getProperty(); + break; + } + } + + Map deadProperties = null; + for (PropertyAction propertyAction : m_propertyActions) + { + int statusCode; + String statusCodeDescription; + WebDAVProperty property = propertyAction.getProperty(); + + if (failedProperty == null) + { + PropertyDefinition propDef = getDAVHelper().getDictionaryService().getProperty(property.createQName()); + + boolean deadProperty = propDef == null || (!propDef.getContainerClass().isAspect() && !getDAVHelper().getDictionaryService().isSubClass(getNodeService().getType(nodeInfo.getNodeRef()), + propDef.getContainerClass().getName())); + + if (deadProperty && deadProperties == null) + { + deadProperties = loadDeadProperties(nodeInfo.getNodeRef()); + } + + if (PropertyAction.SET == propertyAction.getAction()) + { + if (deadProperty) + { + deadProperties.put(property.createQName(), property.getValue()); + } + else + { + getNodeService().setProperty(nodeInfo.getNodeRef(), property.createQName(), property.getValue()); + } + } + else if (PropertyAction.REMOVE == propertyAction.getAction()) + { + if (deadProperty) + { + deadProperties.remove(property.createQName()); + } + else + { + getNodeService().removeProperty(nodeInfo.getNodeRef(), property.createQName()); + } + } + else + { + throw new WebDAVServerException(HttpServletResponse.SC_BAD_REQUEST); + } + statusCode = HttpServletResponse.SC_OK; + statusCodeDescription = WebDAV.SC_OK_DESC; + } + else if (failedProperty == property) + { + statusCode = HttpServletResponse.SC_FORBIDDEN; + statusCodeDescription = WebDAV.SC_FORBIDDEN_DESC; + } + else + { + statusCode = WebDAV.WEBDAV_SC_FAILED_DEPENDENCY; + statusCodeDescription = WebDAV.WEBDAV_SC_FAILED_DEPENDENCY_DESC; + } + + propertyAction.setResult(statusCode, statusCodeDescription); + } + + if (deadProperties != null) + { + persistDeadProperties(nodeInfo.getNodeRef(), deadProperties); + } + } + + + /** + * Generates the XML response for a PROPFIND request that asks for a list of + * all known properties + * + * @param xml XMLWriter + * @param node NodeRef + * @param isDir boolean + */ + protected void generatePropertyResponse(XMLWriter xml, WebDAVProperty property, int status, String description) + { + try + { + // Output the start of the properties element + Attributes nullAttr = getDAVHelper().getNullAttributes(); + + xml.startElement(WebDAV.DAV_NS, WebDAV.XML_PROPSTAT, WebDAV.XML_NS_PROPSTAT, nullAttr); + + // Output property name + xml.startElement(WebDAV.DAV_NS, WebDAV.XML_PROP, WebDAV.XML_NS_PROP, nullAttr); + if (property.hasNamespaceName()) + { + xml.write(DocumentHelper.createElement(property.getNamespaceName() + WebDAV.NAMESPACE_SEPARATOR + property.getName())); + } + else + { + xml.write(DocumentHelper.createElement(property.getName())); + } + xml.endElement(WebDAV.DAV_NS, WebDAV.XML_PROP, WebDAV.XML_NS_PROP); + + // Output action result status + xml.startElement(WebDAV.DAV_NS, WebDAV.XML_STATUS, WebDAV.XML_NS_STATUS, nullAttr); + xml.write(WebDAV.HTTP1_1 + " " + status + " " + description); + xml.endElement(WebDAV.DAV_NS, WebDAV.XML_STATUS, WebDAV.XML_NS_STATUS); + + xml.endElement(WebDAV.DAV_NS, WebDAV.XML_PROPSTAT, WebDAV.XML_NS_PROPSTAT); + } + catch (Exception ex) + { + // Convert to a runtime exception + throw new AlfrescoRuntimeException("XML processing error", ex); + } + } + + + /** + * Generates the error tag + * + * @param xml XMLWriter + */ + protected void generateError(XMLWriter xml) + { + try + { + // Output the start of the error element + Attributes nullAttr = getDAVHelper().getNullAttributes(); + + xml.startElement(WebDAV.DAV_NS, WebDAV.XML_ERROR, WebDAV.XML_NS_ERROR, nullAttr); + // Output error + xml.write(DocumentHelper.createElement(WebDAV.XML_NS_CANNOT_MODIFY_PROTECTED_PROPERTY)); + + xml.endElement(WebDAV.DAV_NS, WebDAV.XML_ERROR, WebDAV.XML_NS_ERROR); + } + catch (Exception ex) + { + // Convert to a runtime exception + throw new AlfrescoRuntimeException("XML processing error", ex); + } + } + + + /** + * Stores information about PROPPATCH action(set or remove) an according property. + * + * @author Ivan Rybnikov + */ + protected class PropertyAction + { + public static final int SET = 0; + public static final int REMOVE = 1; + + // Property on which action should be performed + private WebDAVProperty property; + + // Action + private int action; + + private int statusCode; + + private String statusCodeDescription; + + /** + * Constructor + * + * @param action + * @param property + */ + public PropertyAction(int action, WebDAVProperty property) + { + this.action = action; + this.property = property; + } + + public void setResult(int statusCode, String statusCodeDescription) + { + this.statusCode = statusCode; + this.statusCodeDescription = statusCodeDescription; + } + + public int getStatusCode() + { + return this.statusCode; + } + + public String getStatusCodeDescription() + { + return this.statusCodeDescription; + } + + public int getAction() + { + return action; + } + + public WebDAVProperty getProperty() + { + return property; + } + + public String toString() + { + StringBuilder str = new StringBuilder(); + + str.append("["); + str.append("action="); + str.append(getAction() == 0 ? "SET" : "REMOVE"); + str.append(",property="); + str.append(getProperty()); + str.append(",statusCode="); + str.append(getStatusCode()); + str.append(",statusCodeDescription="); + str.append(getStatusCodeDescription()); + str.append("]"); + + return str.toString(); + } + } + + +} diff --git a/source/java/org/alfresco/repo/webdav/PutMethod.java b/source/java/org/alfresco/repo/webdav/PutMethod.java index 5a8716c2b3..75893c2397 100644 --- a/source/java/org/alfresco/repo/webdav/PutMethod.java +++ b/source/java/org/alfresco/repo/webdav/PutMethod.java @@ -168,9 +168,9 @@ public class PutMethod extends WebDAVMethod implements ActivityPostProducer // 'Unhide' nodes hidden by us and behave as though we created them NodeRef contentNodeRef = contentNodeInfo.getNodeRef(); - if (isHidden(contentNodeRef) && !getDAVHelper().isRenameShuffle(getPath())) + if (fileFolderService.isHidden(contentNodeRef) && !getDAVHelper().isRenameShuffle(getPath())) { - setHidden(contentNodeRef, false); + fileFolderService.setHidden(contentNodeRef, false); created = true; } } @@ -239,7 +239,7 @@ public class PutMethod extends WebDAVMethod implements ActivityPostProducer disabledVersioning = true; } // ALF-16756: To avoid firing inbound rules too early (while a node is still locked) apply the no content aspect - if (nodeLockInfo != null && nodeLockInfo.isExclusive() && !ContentData.hasContent(contentData)) + if (nodeLockInfo != null && nodeLockInfo.isExclusive() && !(ContentData.hasContent(contentData) && contentData.getSize() > 0)) { getNodeService().addAspect(contentNodeInfo.getNodeRef(), ContentModel.ASPECT_NO_CONTENT, null); } diff --git a/source/java/org/alfresco/repo/webdav/WebDAVMethod.java b/source/java/org/alfresco/repo/webdav/WebDAVMethod.java index 846d24e268..baa26f6ca9 100644 --- a/source/java/org/alfresco/repo/webdav/WebDAVMethod.java +++ b/source/java/org/alfresco/repo/webdav/WebDAVMethod.java @@ -996,37 +996,6 @@ public abstract class WebDAVMethod return ns.toString(); } - protected void setHidden(NodeRef nodeRef, boolean isHidden) - { - int mask = 0; - boolean allVisible = true; - Visibility webDavVisibility = isHidden ? Visibility.NotVisible : Visibility.Visible; - HiddenAspect hiddenAspect = m_davHelper.getHiddenAspect(); - for (Client client : hiddenAspect.getClients()) - { - Visibility clientVisibility = client == FileFilterMode.getClient() ? webDavVisibility : hiddenAspect - .getVisibility(client, nodeRef); - if (clientVisibility != Visibility.Visible) - { - allVisible = false; - } - mask |= hiddenAspect.getClientVisibilityMask(client, clientVisibility); - } - if (allVisible) - { - getNodeService().removeAspect(nodeRef, ContentModel.ASPECT_HIDDEN); - } - else - { - hiddenAspect.hideNode(nodeRef, mask); - } - } - - protected boolean isHidden(NodeRef nodeRef) - { - return m_davHelper.getHiddenAspect().getVisibility(FileFilterMode.getClient(), nodeRef) != Visibility.Visible; - } - /** * Checks if write operation can be performed on node. *