diff --git a/config/alfresco/public-services-security-context.xml b/config/alfresco/public-services-security-context.xml index 381e051021..7b547be177 100644 --- a/config/alfresco/public-services-security-context.xml +++ b/config/alfresco/public-services-security-context.xml @@ -431,6 +431,7 @@ org.alfresco.service.cmr.model.FileFolderService.create=ACL_NODE.0.sys:base.CreateChildren org.alfresco.service.cmr.model.FileFolderService.delete=ACL_NODE.0.sys:base.DeleteNode org.alfresco.service.cmr.model.FileFolderService.getNamePath=ACL_NODE.1.sys:base.ReadProperties + org.alfresco.service.cmr.model.FileFolderService.getNameOnlyPath=ACL_NODE.1.sys:base.ReadProperties org.alfresco.service.cmr.model.FileFolderService.resolveNamePath=ACL_ALLOW,AFTER_ACL_NODE.sys:base.ReadProperties org.alfresco.service.cmr.model.FileFolderService.getFileInfo=ACL_NODE.0.sys:base.ReadProperties org.alfresco.service.cmr.model.FileFolderService.getReader=ACL_NODE.0.sys:base.ReadContent diff --git a/config/alfresco/script-services-context.xml b/config/alfresco/script-services-context.xml index 3667b6df9d..48d24af0c4 100644 --- a/config/alfresco/script-services-context.xml +++ b/config/alfresco/script-services-context.xml @@ -256,8 +256,9 @@ - + + @@ -266,7 +267,7 @@ cm:workingCopyOwner cm:lockOwner cm:owner - qshare:sharedBy + qshare:sharedBy diff --git a/source/java/org/alfresco/repo/jscript/ScriptNode.java b/source/java/org/alfresco/repo/jscript/ScriptNode.java index 13a7171874..e15e4ac513 100644 --- a/source/java/org/alfresco/repo/jscript/ScriptNode.java +++ b/source/java/org/alfresco/repo/jscript/ScriptNode.java @@ -103,11 +103,11 @@ import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; import org.alfresco.util.FileFilterMode; +import org.alfresco.util.FileFilterMode.Client; import org.alfresco.util.GUID; import org.alfresco.util.ISO8601DateFormat; import org.alfresco.util.ISO9075; import org.alfresco.util.Pair; -import org.alfresco.util.FileFilterMode.Client; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.json.JSONException; @@ -956,9 +956,9 @@ public class ScriptNode implements Scopeable, NamespacePrefixResolverProvider this.properties = new ContentAwareScriptableQNameMap(this, this.services); Map props = null; - if (nodeInfo != null) + if (this.nodeInfo != null) { - props = nodeInfo.getProperties(); + props = this.nodeInfo.getProperties(); } else { @@ -1334,7 +1334,7 @@ public class ScriptNode implements Scopeable, NamespacePrefixResolverProvider { if (getIsContainer() || getIsDocument()) { - List paths = this.services.getFileFolderService().getNamePath(null, getNodeRef()); + List paths = this.services.getFileFolderService().getNameOnlyPath(null, getNodeRef()); // build up the webdav url StringBuilder path = new StringBuilder(128); @@ -1344,7 +1344,7 @@ public class ScriptNode implements Scopeable, NamespacePrefixResolverProvider for (int i=1; i. */ package org.alfresco.repo.jscript.app; import java.io.Serializable; import java.text.MessageFormat; import java.util.ArrayList; +import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -18,7 +34,7 @@ import org.alfresco.service.cmr.lock.LockService; import org.alfresco.service.cmr.lock.LockStatus; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.model.FileInfo; -import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; @@ -42,6 +58,7 @@ import org.springframework.extensions.surf.util.URLEncoder; * JSON Conversion Component * * @author Roy Wetherall + * @author Kevin Roast */ public class JSONConversionComponent { @@ -52,11 +69,21 @@ public class JSONConversionComponent private static Log logger = LogFactory.getLog(JSONConversionComponent.class); /** Registered decorators */ - protected Map propertyDecorators = new HashMap(3); - + protected Map propertyDecorators = new HashMap(8); + /** User permissions */ protected String[] userPermissions; + /** Thread local cache of namespace prefixes for long QName to short prefix name conversions */ + protected static ThreadLocal> namespacePrefixCache = new ThreadLocal>() + { + @Override + protected Map initialValue() + { + return new HashMap(8); + } + }; + /** Services */ protected NodeService nodeService; protected PublicServiceAccessService publicServiceAccessService; @@ -66,6 +93,7 @@ public class JSONConversionComponent protected ContentService contentService; protected PermissionService permissionService; + /** * @param nodeService node service */ @@ -148,31 +176,34 @@ public class JSONConversionComponent * implementation. */ @SuppressWarnings("unchecked") - public String toJSON(NodeRef nodeRef, boolean useShortQNames) + public String toJSON(final NodeRef nodeRef, final boolean useShortQNames) { - JSONObject json = new JSONObject(); - - if (this.nodeService.exists(nodeRef) == true) + final JSONObject json = new JSONObject(); + + if (this.nodeService.exists(nodeRef)) { if (publicServiceAccessService.hasAccess(ServiceRegistry.NODE_SERVICE.getLocalName(), "getProperties", nodeRef) == AccessStatus.ALLOWED) { + // init namespace prefix cache + namespacePrefixCache.get().clear(); + // Get node info - FileInfo nodeInfo = fileFolderService.getFileInfo(nodeRef); - + FileInfo nodeInfo = this.fileFolderService.getFileInfo(nodeRef); + // Set root values setRootValues(nodeInfo, json, useShortQNames); - + // add permissions json.put("permissions", permissionsToJSON(nodeRef)); - + // add properties - json.put("properties", propertiesToJSON(nodeRef, useShortQNames)); - + json.put("properties", propertiesToJSON(nodeRef, nodeInfo.getProperties(), useShortQNames)); + // add aspects json.put("aspects", apsectsToJSON(nodeRef, useShortQNames)); } } - + return json.toJSONString(); } @@ -184,17 +215,17 @@ public class JSONConversionComponent * @throws JSONException */ @SuppressWarnings("unchecked") - protected void setRootValues(FileInfo nodeInfo, JSONObject rootJSONObject, boolean useShortQNames) + protected void setRootValues(final FileInfo nodeInfo, final JSONObject rootJSONObject, final boolean useShortQNames) { - NodeRef nodeRef = nodeInfo.getNodeRef(); + final NodeRef nodeRef = nodeInfo.getNodeRef(); rootJSONObject.put("nodeRef", nodeInfo.getNodeRef().toString()); rootJSONObject.put("type", nameToString(nodeInfo.getType(), useShortQNames)); rootJSONObject.put("isContainer", nodeInfo.isFolder()); //node.getIsContainer() || node.getIsLinkToContainer()); rootJSONObject.put("isLocked", isLocked(nodeInfo.getNodeRef())); - + rootJSONObject.put("isLink", nodeInfo.isLink()); - if (nodeInfo.isLink() == true) + if (nodeInfo.isLink()) { NodeRef targetNodeRef = nodeInfo.getLinkNodeRef(); if (targetNodeRef != null) @@ -208,9 +239,8 @@ public class JSONConversionComponent if (nodeInfo.isFolder() == false) { - ContentReader reader = contentService.getReader(nodeRef, ContentModel.PROP_CONTENT); - - if (reader != null) + final ContentData cdata = nodeInfo.getContentData(); + if (cdata != null) { String contentURL = MessageFormat.format( CONTENT_DOWNLOAD_API_URL, new Object[]{ @@ -220,23 +250,24 @@ public class JSONConversionComponent URLEncoder.encode(nodeInfo.getName())}); rootJSONObject.put("contentURL", contentURL); - rootJSONObject.put("mimetype", reader.getMimetype()); - rootJSONObject.put("encoding", reader.getEncoding()); - rootJSONObject.put("size", reader.getSize()); + rootJSONObject.put("mimetype", cdata.getMimetype()); + rootJSONObject.put("encoding", cdata.getEncoding()); + rootJSONObject.put("size", cdata.getSize()); } } } /** - * + * Handles the work of converting node permissions to JSON. + * * @param nodeRef * @return * @throws JSONException */ @SuppressWarnings("unchecked") - protected JSONObject permissionsToJSON(NodeRef nodeRef) + protected JSONObject permissionsToJSON(final NodeRef nodeRef) { - JSONObject permissionsJSON = new JSONObject(); + final JSONObject permissionsJSON = new JSONObject(); if (AccessStatus.ALLOWED.equals(permissionService.hasPermission(nodeRef, PermissionService.READ_PERMISSIONS)) == true) { permissionsJSON.put("inherited", permissionService.getInheritParentPermissions(nodeRef)); @@ -247,14 +278,15 @@ public class JSONConversionComponent } /** + * Handles the work of converting user permissions to JSON. * * @param nodeRef * @return */ @SuppressWarnings("unchecked") - protected JSONObject userPermissionsToJSON(NodeRef nodeRef) + protected JSONObject userPermissionsToJSON(final NodeRef nodeRef) { - JSONObject userPermissionJSON = new JSONObject(); + final JSONObject userPermissionJSON = new JSONObject(); for (String userPermission : this.userPermissions) { boolean hasPermission = AccessStatus.ALLOWED.equals(permissionService.hasPermission(nodeRef, userPermission)); @@ -273,12 +305,12 @@ public class JSONConversionComponent * @return the JSON value */ @SuppressWarnings({ "unchecked", "rawtypes" }) - protected Object propertyToJSON(NodeRef nodeRef, QName propertyName, String key, Serializable value) + protected Object propertyToJSON(final NodeRef nodeRef, final QName propertyName, final String key, final Serializable value) { if (value != null) { // Has a decorator has been registered for this property? - if (propertyDecorators.containsKey(propertyName) == true) + if (propertyDecorators.containsKey(propertyName)) { JSONAware jsonAware = propertyDecorators.get(propertyName).decorate(propertyName, nodeRef, value); if (jsonAware != null) @@ -326,16 +358,16 @@ public class JSONConversionComponent /** * * @param nodeRef + * @param map * @param useShortQNames * @return * @throws JSONException */ @SuppressWarnings("unchecked") - protected JSONObject propertiesToJSON(NodeRef nodeRef, boolean useShortQNames) + protected JSONObject propertiesToJSON(NodeRef nodeRef, Map properties, boolean useShortQNames) { JSONObject propertiesJSON = new JSONObject(); - Map properties = nodeService.getProperties(nodeRef); for (QName propertyName : properties.keySet()) { try @@ -357,6 +389,7 @@ public class JSONConversionComponent } /** + * Handles the work of converting aspects to JSON. * * @param nodeRef * @param useShortQNames @@ -367,7 +400,7 @@ public class JSONConversionComponent protected JSONArray apsectsToJSON(NodeRef nodeRef, boolean useShortQNames) { JSONArray aspectsJSON = new JSONArray(); - + Set aspects = this.nodeService.getAspects(nodeRef); for (QName aspect : aspects) { @@ -378,6 +411,7 @@ public class JSONConversionComponent } /** + * Handles the work of converting all set permissions to JSON. * * @param nodeRef * @return @@ -402,17 +436,27 @@ public class JSONConversionComponent } /** + * Convert a qname to a string - either full or short prefixed named. * * @param qname * @param isShortName - * @return + * @return qname string. */ - private String nameToString(QName qname, boolean isShortName) + private String nameToString(final QName qname, final boolean isShortName) { - String result = null; - if (isShortName == true) + String result; + if (isShortName) { - result = qname.toPrefixString(namespaceService); + final Map cache = namespacePrefixCache.get(); + String prefix = cache.get(qname.getNamespaceURI()); + if (prefix == null) + { + // first request for this namespace prefix, get and cache result + Collection prefixes = this.namespaceService.getPrefixes(qname.getNamespaceURI()); + prefix = prefixes.size() != 0 ? prefixes.iterator().next() : ""; + cache.put(qname.getNamespaceURI(), prefix); + } + result = prefix + QName.NAMESPACE_PREFIX + qname.getLocalName(); } else { @@ -422,11 +466,12 @@ public class JSONConversionComponent } /** + * Return true if the node is locked. * * @param nodeRef * @return */ - private boolean isLocked(NodeRef nodeRef) + private boolean isLocked(final NodeRef nodeRef) { boolean locked = false; diff --git a/source/java/org/alfresco/repo/jscript/app/UsernamePropertyDecorator.java b/source/java/org/alfresco/repo/jscript/app/UsernamePropertyDecorator.java index ef9d6d0f46..c58a3b0a4c 100644 --- a/source/java/org/alfresco/repo/jscript/app/UsernamePropertyDecorator.java +++ b/source/java/org/alfresco/repo/jscript/app/UsernamePropertyDecorator.java @@ -19,7 +19,6 @@ package org.alfresco.repo.jscript.app; import java.io.Serializable; -import java.util.Map; import org.alfresco.model.ContentModel; import org.alfresco.service.cmr.repository.NodeRef; @@ -57,13 +56,12 @@ public class UsernamePropertyDecorator extends BasePropertyDecorator String lastName = null; JSONObject map = new JSONObject(); map.put("userName", username); - + if (this.personService.personExists(username)) { NodeRef personRef = this.personService.getPerson(username); - Map properties = this.nodeService.getProperties(personRef); - firstName = (String)properties.get(ContentModel.PROP_FIRSTNAME); - lastName = (String)properties.get(ContentModel.PROP_LASTNAME); + firstName = (String)this.nodeService.getProperty(personRef, ContentModel.PROP_FIRSTNAME); + lastName = (String)this.nodeService.getProperty(personRef, ContentModel.PROP_LASTNAME); } else if (username.equals("System") || username.startsWith("System@")) { @@ -75,7 +73,7 @@ public class UsernamePropertyDecorator extends BasePropertyDecorator map.put("isDeleted", true); return map; } - + map.put("firstName", firstName); map.put("lastName", lastName); map.put("displayName", ((firstName != null ? firstName + " " : "") + (lastName != null ? lastName : "")).replaceAll("^\\s+|\\s+$", "")); diff --git a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java index d5d77b1037..4e891db772 100644 --- a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java +++ b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java @@ -1303,6 +1303,22 @@ public class FileFolderServiceImpl implements FileFolderService return FileFolderUtil.makeFolders(service, parentNodeRef, pathElements, folderTypeQName); } + /** + * Get the file or folder information from the root down to and including the node provided. + *
    + *
  • The root node can be of any type and is not included in the path list.
  • + *
  • Only the primary path is considered. If the target node is not a descendant of the + * root along purely primary associations, then an exception is generated.
  • + *
  • If an invalid type is encountered along the path, then an exception is generated.
  • + *
+ * + * @param rootNodeRef the start of the returned path, or null if the store root + * node must be assumed. + * @param nodeRef a reference to the file or folder + * @return Returns a list of file/folder infos from the root (excluded) down to and + * including the destination file or folder + * @throws FileNotFoundException if the node could not be found + */ public List getNamePath(NodeRef rootNodeRef, NodeRef nodeRef) throws FileNotFoundException { // check the root @@ -1334,13 +1350,13 @@ public class FileFolderServiceImpl implements FileFolderService continue; } // we found the root and expect to be building the path up - //Run as system as the user could not have access to all folders in the path, see ALF-13816 + // Run as system as the user could not have access to all folders in the path, see ALF-13816 FileInfo pathInfo = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() { - public FileInfo doWork() throws Exception - { - return toFileInfo(childNodeRef, true); - } + public FileInfo doWork() throws Exception + { + return toFileInfo(childNodeRef, true); + } }, AuthenticationUtil.getSystemUserName()); // we can't append a path element to the results if there is already a (non-folder) file at the tail @@ -1372,6 +1388,85 @@ public class FileFolderServiceImpl implements FileFolderService throw new FileNotFoundException(nodeRef); } } + + /** + * Get the file or folder names from the root down to and including the node provided. + *
    + *
  • The root node can be of any type and is not included in the path list.
  • + *
  • Only the primary path is considered. If the target node is not a descendant of the + * root along purely primary associations, then an exception is generated.
  • + *
  • If an invalid type is encountered along the path, then an exception is generated.
  • + *
+ * + * @param rootNodeRef the start of the returned path, or null if the store root + * node must be assumed. + * @param nodeRef a reference to the file or folder + * @return Returns a list of file/folder names from the root (excluded) down to and + * including the destination file or folder + * @throws FileNotFoundException if the node could not be found + */ + public List getNameOnlyPath(NodeRef rootNodeRef, final NodeRef nodeRef) throws FileNotFoundException + { + // check the root + if (rootNodeRef == null) + { + rootNodeRef = nodeService.getRootNode(nodeRef.getStoreRef()); + } + try + { + final NodeRef rNodeRef = rootNodeRef; + final ArrayList results = new ArrayList(10); + // Run as system as the user could not have access to all folders in the path, see ALF-13816 + AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + { + public Void doWork() throws Exception + { + // get the primary path + Path path = nodeService.getPath(nodeRef); + // iterate and turn the results into file info objects + boolean foundRoot = false; + for (Path.Element element : path) + { + // ignore everything down to the root + Path.ChildAssocElement assocElement = (Path.ChildAssocElement) element; + final NodeRef childNodeRef = assocElement.getRef().getChildRef(); + if (childNodeRef.equals(rNodeRef)) + { + // just found the root - but we don't put in an entry for it + foundRoot = true; + continue; + } + else if (!foundRoot) + { + // keep looking for the root + continue; + } + results.add(nodeService.getProperty(childNodeRef, ContentModel.PROP_NAME).toString()); + } + // check that we found the root + if (!foundRoot) + { + throw new FileNotFoundException(nodeRef); + } + // done + if (logger.isDebugEnabled()) + { + logger.debug("Built name path for node: \n" + + " root: " + rNodeRef + "\n" + + " node: " + nodeRef + "\n" + + " path: " + results); + } + return null; + } + }, AuthenticationUtil.getSystemUserName()); + + return results; + } + catch (InvalidNodeRefException e) + { + throw new FileNotFoundException(nodeRef); + } + } public FileInfo resolveNamePath(NodeRef rootNodeRef, List pathElements) throws FileNotFoundException { diff --git a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImplTest.java b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImplTest.java index a7e79585a1..94337bc48a 100644 --- a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImplTest.java +++ b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImplTest.java @@ -839,6 +839,36 @@ public class FileFolderServiceImplTest extends TestCase } } + public void testGetNameOnlyPath() throws Exception + { + FileInfo fileInfo = getByName(NAME_L1_FILE_A, false); + assertNotNull(fileInfo); + NodeRef nodeRef = fileInfo.getNodeRef(); + + List infoPaths = fileFolderService.getNameOnlyPath(workingRootNodeRef, nodeRef); + assertEquals("Not enough elements", 2, infoPaths.size()); + assertEquals("First level incorrent", NAME_L0_FOLDER_A, infoPaths.get(0)); + assertEquals("Second level incorrent", NAME_L1_FILE_A, infoPaths.get(1)); + + // pass in a null root and make sure that it still works + infoPaths = fileFolderService.getNameOnlyPath(null, nodeRef); + assertEquals("Not enough elements", 3, infoPaths.size()); + assertEquals("First level incorrent", workingRootNodeRef.getId(), infoPaths.get(0)); + assertEquals("Second level incorrent", NAME_L0_FOLDER_A, infoPaths.get(1)); + assertEquals("Third level incorrent", NAME_L1_FILE_A, infoPaths.get(2)); + + // check that a non-aligned path is detected + NodeRef startRef = getByName(NAME_L0_FOLDER_B, true).getNodeRef(); + try + { + fileFolderService.getNameOnlyPath(startRef, nodeRef); + fail("Failed to detect non-aligned path from root to target node"); + } + catch (FileNotFoundException e) + { + // expected + } + } public void testGetNamePathDoesNotReturnPathContainingNonLeafFileNode() throws Exception { diff --git a/source/java/org/alfresco/repo/webdav/WebDavServiceImpl.java b/source/java/org/alfresco/repo/webdav/WebDavServiceImpl.java index 29b680a910..eb8ff584df 100644 --- a/source/java/org/alfresco/repo/webdav/WebDavServiceImpl.java +++ b/source/java/org/alfresco/repo/webdav/WebDavServiceImpl.java @@ -90,7 +90,7 @@ public class WebDavServiceImpl implements WebDavService if (getIsContainer(typeName) || getIsDocument(typeName)) { - List paths = fileFolderService.getNamePath(getRootNode().getNodeForCurrentTenant(), nodeRef); + List paths = fileFolderService.getNameOnlyPath(getRootNode().getNodeForCurrentTenant(), nodeRef); // build up the webdav url StringBuilder path = new StringBuilder(128); @@ -99,7 +99,7 @@ public class WebDavServiceImpl implements WebDavService for (int i=0; i + *
  • The root node can be of any type and is not included in the path list.
  • + *
  • Only the primary path is considered. If the target node is not a descendant of the + * root along purely primary associations, then an exception is generated.
  • + *
  • If an invalid type is encountered along the path, then an exception is generated.
  • + * + * + * @param rootNodeRef the start of the returned path, or null if the store root + * node must be assumed. + * @param nodeRef a reference to the file or folder + * @return Returns a list of file/folder infos from the root (excluded) down to and + * including the destination file or folder + * @throws FileNotFoundException if the node could not be found + */ + @Auditable(parameters = {"rootNodeRef", "nodeRef"}) + public List getNamePath(NodeRef rootNodeRef, NodeRef nodeRef) throws FileNotFoundException; + /** * Get the file or folder names from the root down to and including the node provided. *
      @@ -319,14 +338,14 @@ public interface FileFolderService *
    * * @param rootNodeRef the start of the returned path, or null if the store root - * node must be assumed. + * node must be assumed. * @param nodeRef a reference to the file or folder - * @return Returns a list of file/folder infos from the root (excluded) down to and - * including the destination file or folder + * @return Returns a list of file/folder names from the root (excluded) down to and + * including the destination file or folder * @throws FileNotFoundException if the node could not be found */ @Auditable(parameters = {"rootNodeRef", "nodeRef"}) - public List getNamePath(NodeRef rootNodeRef, NodeRef nodeRef) throws FileNotFoundException; + public List getNameOnlyPath(NodeRef rootNodeRef, NodeRef nodeRef) throws FileNotFoundException; /** * Resolve a file or folder name path from a given root node down to the final node.