diff --git a/config/alfresco/script-services-context.xml b/config/alfresco/script-services-context.xml index 74a95199d7..bf7dad9938 100644 --- a/config/alfresco/script-services-context.xml +++ b/config/alfresco/script-services-context.xml @@ -219,5 +219,64 @@ - + + + + + + + + + + + + + + + + + + + + appUtils + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CancelCheckOut + ChangePermissions + CreateChildren + Delete + Write + + + + diff --git a/source/java/org/alfresco/repo/jscript/ApplicationScriptUtils.java b/source/java/org/alfresco/repo/jscript/ApplicationScriptUtils.java new file mode 100644 index 0000000000..b82ea47784 --- /dev/null +++ b/source/java/org/alfresco/repo/jscript/ApplicationScriptUtils.java @@ -0,0 +1,291 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * 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.jscript; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.jscript.app.JSONPropertyDecorator; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.AccessStatus; +import org.alfresco.service.namespace.NamespaceException; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.ISO8601DateFormat; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.json.JSONException; +import org.json.JSONObject; +import org.springframework.extensions.surf.util.URLEncoder; + +import java.io.Serializable; +import java.text.MessageFormat; +import java.util.*; + +/** + * Utility functions specifically for external application use. + * + * @author Mike Hatfield + */ + +public final class ApplicationScriptUtils extends BaseScopableProcessorExtension +{ + private static Log logger = LogFactory.getLog(ApplicationScriptUtils.class); + + /** Repository Service Registry */ + private ServiceRegistry services; + private NodeService nodeService = null; + private Map decoratedProperties; + private String[] userPermissions; + + private final static String CONTENT_DOWNLOAD_API_URL = "/api/node/content/{0}/{1}/{2}/{3}"; + + /** + * Set the service registry + * + * @param serviceRegistry the service registry + */ + public void setServiceRegistry(ServiceRegistry serviceRegistry) + { + this.services = serviceRegistry; + this.nodeService = services.getNodeService(); + } + + /** + * Set the properties that require decorator beans + * + * @param decoratedProperties + */ + public void setDecoratedProperties(Map decoratedProperties) + { + this.decoratedProperties = decoratedProperties; + } + + /** + * Define the list of user permissions to return in the JSON body + * + * @param userPermissions + */ + public void setUserPermissions(String[] userPermissions) + { + this.userPermissions = userPermissions; + } + + /** + * Returns the JSON representation of a node. Long-form QNames are used in the + * result. + * + * @param node the node to convert to JSON representation. + * @return The JSON representation of this node + */ + public String toJSON(ScriptNode node) + { + return this.toJSON(node, false); + } + + /** + * Returns the JSON representation of this node. + * + * @param node the node to convert to JSON representation. + * @param useShortQNames if true short-form qnames will be returned, else long-form. + * @return The JSON representation of this node + */ + public String toJSON(ScriptNode node, boolean useShortQNames) + { + return this.toJSONObj(node, useShortQNames).toString(); + } + + /** + * Returns a JSON object representing the node. + * + * @param node the node to convert to JSON representation. + * @param useShortQNames if true short-form qnames will be returned, else long-form. + * @return The JSON representation of this node + */ + protected Object toJSONObj(ScriptNode node, boolean useShortQNames) + { + NodeRef nodeRef = node.getNodeRef(); + JSONObject json = new JSONObject(); + + if (this.nodeService.exists(nodeRef)) + { + if (this.services.getPublicServiceAccessService().hasAccess(ServiceRegistry.NODE_SERVICE.getLocalName(), "getProperties", nodeRef) == AccessStatus.ALLOWED) + { + try + { + String typeString = useShortQNames ? this.getShortQName(node.getQNameType()) : node.getType(); + boolean isLink = node.getIsLinkToContainer() || node.getIsLinkToDocument(); + + json.put("nodeRef", nodeRef.toString()); + json.put("type", typeString); + json.put("isContainer", node.getIsContainer() || node.getIsLinkToContainer()); + json.put("isLink", isLink); + json.put("isLocked", node.getIsLocked()); + + if (node.getIsDocument()) + { + json.put("contentURL", this.getDownloadAPIUrl(node)); + json.put("mimetype", node.getMimetype()); + json.put("size", node.getSize()); + } + + // permissions + Map permissionsJSON = new LinkedHashMap(3); + if (node.hasPermission("ReadPermissions")) + { + permissionsJSON.put("roles", node.retrieveAllSetPermissions(false, true)); + } + permissionsJSON.put("inherited", node.inheritsPermissions()); + Map userPermissionJSON = new LinkedHashMap(this.userPermissions.length); + for (String userPermission : this.userPermissions) + { + userPermissionJSON.put(userPermission, node.hasPermission(userPermission)); + } + permissionsJSON.put("user", (Serializable) userPermissionJSON); + json.put("permissions", permissionsJSON); + + // add properties + Map nodeProperties = this.nodeService.getProperties(nodeRef); + json.put("properties", this.parseToJSON(nodeRef, nodeProperties, useShortQNames)); + + // add aspects as an array + Set nodeAspects = this.nodeService.getAspects(nodeRef); + if (useShortQNames) + { + Set nodeAspectsShortQNames = new LinkedHashSet(nodeAspects.size()); + for (QName nextLongQName : nodeAspects) + { + String nextShortQName = this.getShortQName(nextLongQName); + nodeAspectsShortQNames.add(nextShortQName); + } + json.put("aspects", nodeAspectsShortQNames); + } + else + { + json.put("aspects", nodeAspects); + } + + // link to document or folder? + if (isLink) + { + NodeRef targetNodeRef = (NodeRef) nodeProperties.get(ContentModel.PROP_LINK_DESTINATION); + if (targetNodeRef != null) + { + json.put("linkedNode", this.toJSONObj(new ScriptNode(targetNodeRef, this.services, node.scope), useShortQNames)); + } + } + } + catch (JSONException error) + { + error.printStackTrace(); + } + } + } + + return json; + } + + /** + * Given a long-form QName, this method uses the namespace service to create a + * short-form QName string. + * + * @param longQName + * @return the short form of the QName string, e.g. "cm:content" + */ + protected String getShortQName(QName longQName) + { + return longQName.toPrefixString(this.services.getNamespaceService()); + } + + /** + * Converts a map of node properties to a format suitable for JSON output + * + * @param nodeRef + * @param properties + * @param useShortQNames + * @return a decorated map of properties suitable for JSON output + */ + protected Map parseToJSON(NodeRef nodeRef, Map properties, boolean useShortQNames) + { + Map json = new LinkedHashMap(properties.size()); + + for (QName nextLongQName : properties.keySet()) + { + try + { + String shortQName = this.getShortQName(nextLongQName); + String key = useShortQNames ? shortQName : nextLongQName.toString(); + Serializable value = properties.get(nextLongQName); + + // Has a decorator has been registered for this property? + if (this.decoratedProperties.containsKey(shortQName)) + { + json.put(key, ((JSONPropertyDecorator) this.decoratedProperties.get(shortQName)).decorate(nodeRef, shortQName, value)); + } + else + { + // Built-in data type processing + if (value instanceof Date) + { + Map dateObj = new LinkedHashMap(1); + dateObj.put("value", value); + dateObj.put("iso8601", ISO8601DateFormat.format((Date)value)); + json.put(key, (Serializable)dateObj); + } + else + { + json.put(key, value); + } + } + } + catch (NamespaceException ne) + { + // ignore properties that do not have a registered namespace + if (logger.isDebugEnabled()) + logger.debug("Ignoring property '" + nextLongQName + "' as its namespace is not registered"); + } + } + + return json; + } + + /** + * @param node the node to construct the download URL for + * @return For a content document, this method returns the URL to the /api/node/content + * API for the default content property + *

+ * For a container node, this method returns an empty string + *

+ */ + public String getDownloadAPIUrl(ScriptNode node) + { + if (node.getIsDocument()) + { + return MessageFormat.format(CONTENT_DOWNLOAD_API_URL, new Object[]{ + node.nodeRef.getStoreRef().getProtocol(), + node.nodeRef.getStoreRef().getIdentifier(), + node.nodeRef.getId(), + URLEncoder.encode(node.getName())}); + } + else + { + return ""; + } + } + +} diff --git a/source/java/org/alfresco/repo/jscript/ScriptNode.java b/source/java/org/alfresco/repo/jscript/ScriptNode.java index 75c301340c..676103d479 100644 --- a/source/java/org/alfresco/repo/jscript/ScriptNode.java +++ b/source/java/org/alfresco/repo/jscript/ScriptNode.java @@ -130,7 +130,7 @@ public class ScriptNode implements Serializable, Scopeable, NamespacePrefixResol private final static String CONTENT_PROP_URL = "/d/d/{0}/{1}/{2}/{3}?property={4}"; private final static String CONTENT_DOWNLOAD_PROP_URL = "/d/a/{0}/{1}/{2}/{3}?property={4}"; private final static String FOLDER_BROWSE_URL = "/n/browse/{0}/{1}/{2}"; - + /** Root scope for this object */ protected Scriptable scope; @@ -1263,7 +1263,7 @@ public class ScriptNode implements Serializable, Scopeable, NamespacePrefixResol { return getDownloadUrl(); } - + /** * @return The WebDav cm:name based path to the content for the default content property * (@see ContentModel.PROP_CONTENT) @@ -1411,7 +1411,7 @@ public class ScriptNode implements Serializable, Scopeable, NamespacePrefixResol * * @return Object[] of packed permission strings. */ - private Object[] retrieveAllSetPermissions(boolean direct, boolean full) + protected Object[] retrieveAllSetPermissions(boolean direct, boolean full) { Set acls = this.services.getPermissionService().getAllSetPermissions(getNodeRef()); List permissions = new ArrayList(acls.size()); @@ -3076,7 +3076,7 @@ public class ScriptNode implements Serializable, Scopeable, NamespacePrefixResol * @param longQName * @return the short form of the QName string, e.g. "cm:content" */ - private String getShortQName(QName longQName) + protected String getShortQName(QName longQName) { return longQName.toPrefixString(services.getNamespaceService()); } @@ -3088,7 +3088,7 @@ public class ScriptNode implements Serializable, Scopeable, NamespacePrefixResol * * @return QName */ - private QName createQName(String s) + protected QName createQName(String s) { QName qname; if (s.indexOf(NAMESPACE_BEGIN) != -1) diff --git a/source/java/org/alfresco/repo/jscript/app/CategoryPropertyDecorator.java b/source/java/org/alfresco/repo/jscript/app/CategoryPropertyDecorator.java new file mode 100644 index 0000000000..d0ff2b4325 --- /dev/null +++ b/source/java/org/alfresco/repo/jscript/app/CategoryPropertyDecorator.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * 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.jscript.app; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.PermissionService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.io.Serializable; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Category property decorator class. + * + * @author Mike Hatfield + */ +public class CategoryPropertyDecorator implements JSONPropertyDecorator +{ + private static Log logger = LogFactory.getLog(CategoryPropertyDecorator.class); + + private ServiceRegistry services; + private NodeService nodeService = null; + private PermissionService permissionService = null; + + public void setServiceRegistry(ServiceRegistry serviceRegistry) + { + this.services = serviceRegistry; + this.nodeService = serviceRegistry.getNodeService(); + this.permissionService = serviceRegistry.getPermissionService(); + } + + public Serializable decorate(NodeRef nodeRef, String propertyName, Serializable value) + { + Collection collection = (Collection)value; + Object[] array = new Object[collection.size()]; + int index = 0; + + for (NodeRef obj : collection) + { + try + { + Map jsonObj = new LinkedHashMap(4); + jsonObj.put("name", this.nodeService.getProperty(obj, ContentModel.PROP_NAME)); + jsonObj.put("path", this.getPath(obj)); + jsonObj.put("nodeRef", obj.toString()); + array[index++] = jsonObj; + } + catch (InvalidNodeRefException e) + { + logger.warn("Category with nodeRef " + obj.toString() + " does not exist."); + } + } + + return array; + } + + /** + * Category path used for node membership queries + * + * @return Display path to this node + */ + public String getPath(NodeRef nodeRef) + { + String displayPath = this.nodeService.getPath(nodeRef).toDisplayPath(this.nodeService, this.permissionService); + return displayPath.replaceFirst("/categories/General", ""); + } +} diff --git a/source/java/org/alfresco/repo/jscript/app/JSONPropertyDecorator.java b/source/java/org/alfresco/repo/jscript/app/JSONPropertyDecorator.java new file mode 100644 index 0000000000..5b7aa74bc9 --- /dev/null +++ b/source/java/org/alfresco/repo/jscript/app/JSONPropertyDecorator.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * 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.jscript.app; + +import org.alfresco.service.cmr.repository.NodeRef; +import java.io.Serializable; + +/** + * Interface for property decorators used by ApplicationScriptUtils.toJSON() + * + * @author Mike Hatfield + */ +public interface JSONPropertyDecorator +{ + Serializable decorate(NodeRef nodeRef, String propertyName, Serializable value); +} diff --git a/source/java/org/alfresco/repo/jscript/app/TagPropertyDecorator.java b/source/java/org/alfresco/repo/jscript/app/TagPropertyDecorator.java new file mode 100644 index 0000000000..a2464aaa46 --- /dev/null +++ b/source/java/org/alfresco/repo/jscript/app/TagPropertyDecorator.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * 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.jscript.app; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.io.Serializable; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Tag property decorator class. + * + * @author Mike Hatfield + */ +public class TagPropertyDecorator implements JSONPropertyDecorator +{ + private static Log logger = LogFactory.getLog(TagPropertyDecorator.class); + + private ServiceRegistry services; + private NodeService nodeService = null; + + public void setServiceRegistry(ServiceRegistry serviceRegistry) + { + this.services = serviceRegistry; + this.nodeService = serviceRegistry.getNodeService(); + } + + public Serializable decorate(NodeRef nodeRef, String propertyName, Serializable value) + { + Collection collection = (Collection)value; + Object[] array = new Object[collection.size()]; + int index = 0; + + for (NodeRef obj : collection) + { + try + { + Map jsonObj = new LinkedHashMap(2); + jsonObj.put("name", this.nodeService.getProperty(obj, ContentModel.PROP_NAME)); + jsonObj.put("nodeRef", obj.toString()); + array[index++] = jsonObj; + } + catch (InvalidNodeRefException e) + { + logger.warn("Tag with nodeRef " + obj.toString() + " does not exist."); + } + } + + return array; + } +} diff --git a/source/java/org/alfresco/repo/jscript/app/UsernamePropertyDecorator.java b/source/java/org/alfresco/repo/jscript/app/UsernamePropertyDecorator.java new file mode 100644 index 0000000000..d4042f3194 --- /dev/null +++ b/source/java/org/alfresco/repo/jscript/app/UsernamePropertyDecorator.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * 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.jscript.app; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.namespace.QName; + +import java.io.Serializable; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Username property decorator class. + * + * @author Mike Hatfield + */ +public class UsernamePropertyDecorator implements JSONPropertyDecorator +{ + private ServiceRegistry services; + private NodeService nodeService = null; + private PersonService personService = null; + + public void setServiceRegistry(ServiceRegistry serviceRegistry) + { + this.services = serviceRegistry; + this.nodeService = serviceRegistry.getNodeService(); + this.personService = serviceRegistry.getPersonService(); + } + + public Serializable decorate(NodeRef nodeRef, String propertyName, Serializable value) + { + String username = value.toString(); + String firstName = null; + String lastName = null; + Map map = new LinkedHashMap(1); + map.put("userName", username); + + if (this.personService.personExists(username)) + { + NodeRef personRef = this.personService.getPerson(username); + Map properties = this.nodeService.getProperties(personRef); + firstName = properties.get(ContentModel.PROP_FIRSTNAME).toString(); + lastName = properties.get(ContentModel.PROP_LASTNAME).toString(); + } + else if (username.equals("System") || username.startsWith("System@")) + { + firstName = "System"; + lastName = "User"; + } + else + { + return null; + } + + map.put("firstName", firstName); + map.put("lastName", lastName); + map.put("displayName", (firstName + " " + lastName).replaceAll("^\\s+|\\s+$", "")); + return (Serializable)map; + } +}