/* * Copyright (C) 2005-2013 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 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; import java.util.Map; import java.util.Set; import org.alfresco.model.ContentModel; import org.alfresco.service.ServiceRegistry; 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.ContentData; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.MimetypeService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.security.AccessPermission; import org.alfresco.service.cmr.security.AccessStatus; import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.cmr.security.PublicServiceAccessService; import org.alfresco.service.namespace.NamespaceException; import org.alfresco.service.namespace.NamespaceService; 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.simple.JSONArray; import org.json.simple.JSONAware; import org.json.simple.JSONObject; import org.springframework.extensions.surf.util.URLEncoder; /** * JSON Conversion Component * * @author Roy Wetherall * @author Kevin Roast */ public class JSONConversionComponent { /** Content download API URL template */ private final static String CONTENT_DOWNLOAD_API_URL = "/slingshot/node/content/{0}/{1}/{2}/{3}"; /** Logger */ private static Log logger = LogFactory.getLog(JSONConversionComponent.class); /** Registered decorators */ 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; protected NamespaceService namespaceService; protected FileFolderService fileFolderService; protected LockService lockService; protected ContentService contentService; protected PermissionService permissionService; protected MimetypeService mimetypeService; /** * @param nodeService node service */ public void setNodeService(NodeService nodeService) { this.nodeService = nodeService; } /** * @param publicServiceAccessService public service access service */ public void setPublicServiceAccessService(PublicServiceAccessService publicServiceAccessService) { this.publicServiceAccessService = publicServiceAccessService; } /** * @param namespaceService namespace service */ public void setNamespaceService(NamespaceService namespaceService) { this.namespaceService = namespaceService; } /** * @param fileFolderService file folder service */ public void setFileFolderService(FileFolderService fileFolderService) { this.fileFolderService = fileFolderService; } /** * @param lockService lock service */ public void setLockService(LockService lockService) { this.lockService = lockService; } /** * @param permissionService permission service */ public void setPermissionService(PermissionService permissionService) { this.permissionService = permissionService; } /** * @param userPermissions user permissions */ public void setUserPermissions(String[] userPermissions) { this.userPermissions = userPermissions; } /** * @param contentService content service */ public void setContentService(ContentService contentService) { this.contentService = contentService; } /** * @param mimetypeService mimetype service */ public void setMimetypeService(MimetypeService mimetypeService) { this.mimetypeService = mimetypeService; } /** * Register a property decorator; * * @param propertyDecorator */ public void registerPropertyDecorator(PropertyDecorator propertyDecorator) { for (QName propertyName : propertyDecorator.getPropertyNames()) { propertyDecorators.put(propertyName, propertyDecorator); } } /** * Convert a node reference to a JSON string. Selects the correct converter based on selection * implementation. */ @SuppressWarnings("unchecked") public String toJSON(final NodeRef nodeRef, final boolean useShortQNames) { 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 = 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, nodeInfo.getProperties(), useShortQNames)); // add aspects json.put("aspects", apsectsToJSON(nodeRef, useShortQNames)); } } return json.toJSONString(); } /** * * @param nodeInfo * @param rootJSONObject * @param useShortQNames * @throws JSONException */ @SuppressWarnings("unchecked") protected void setRootValues(final FileInfo nodeInfo, final JSONObject rootJSONObject, final boolean useShortQNames) { 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()) { NodeRef targetNodeRef = nodeInfo.getLinkNodeRef(); if (targetNodeRef != null) { rootJSONObject.put("linkedNode", toJSON(targetNodeRef, useShortQNames)); } } // TODO should this be moved to the property output since we may have more than one content property // or a non-standard content property if (nodeInfo.isFolder() == false) { final ContentData cdata = nodeInfo.getContentData(); if (cdata != null) { String contentURL = MessageFormat.format( CONTENT_DOWNLOAD_API_URL, new Object[]{ nodeRef.getStoreRef().getProtocol(), nodeRef.getStoreRef().getIdentifier(), nodeRef.getId(), URLEncoder.encode(nodeInfo.getName())}); rootJSONObject.put("contentURL", contentURL); rootJSONObject.put("mimetype", cdata.getMimetype()); Map mimetypeDescriptions; mimetypeDescriptions = mimetypeService.getDisplaysByMimetype(); if (mimetypeDescriptions.containsKey(cdata.getMimetype())) { rootJSONObject.put("mimetypeDisplayName", mimetypeDescriptions.get(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(final NodeRef nodeRef) { final JSONObject permissionsJSON = new JSONObject(); if (AccessStatus.ALLOWED.equals(permissionService.hasPermission(nodeRef, PermissionService.READ_PERMISSIONS)) == true) { permissionsJSON.put("inherited", permissionService.getInheritParentPermissions(nodeRef)); permissionsJSON.put("roles", allSetPermissionsToJSON(nodeRef)); permissionsJSON.put("user", userPermissionsToJSON(nodeRef)); } return permissionsJSON; } /** * Handles the work of converting user permissions to JSON. * * @param nodeRef * @return */ @SuppressWarnings("unchecked") protected JSONObject userPermissionsToJSON(final NodeRef nodeRef) { final JSONObject userPermissionJSON = new JSONObject(); for (String userPermission : this.userPermissions) { boolean hasPermission = AccessStatus.ALLOWED.equals(permissionService.hasPermission(nodeRef, userPermission)); userPermissionJSON.put(userPermission, hasPermission); } return userPermissionJSON; } /** * Handles the work of converting values to JSON. * * @param nodeRef * @param propertyName * @param key * @param value * @return the JSON value */ @SuppressWarnings({ "unchecked", "rawtypes" }) 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)) { JSONAware jsonAware = propertyDecorators.get(propertyName).decorate(propertyName, nodeRef, value); if (jsonAware != null) { return jsonAware; } } else { // Built-in data type processing if (value instanceof Date) { JSONObject dateObj = new JSONObject(); dateObj.put("value", JSONObject.escape(value.toString())); dateObj.put("iso8601", JSONObject.escape(ISO8601DateFormat.format((Date)value))); return dateObj; } else if (value instanceof List) { // Convert the List to a JSON list by recursively calling propertyToJSON List jsonList = new ArrayList(((List) value).size()); for (Serializable listItem : (List) value) { jsonList.add(propertyToJSON(nodeRef, propertyName, key, listItem)); } return jsonList; } else if (value instanceof Double) { return (Double.isInfinite((Double)value) || Double.isNaN((Double)value) ? null : value.toString()); } else if (value instanceof Float) { return (Float.isInfinite((Float)value) || Float.isNaN((Float)value) ? null : value.toString()); } else { return value.toString(); } } } return null; } /** * * @param nodeRef * @param map * @param useShortQNames * @return * @throws JSONException */ @SuppressWarnings("unchecked") protected JSONObject propertiesToJSON(NodeRef nodeRef, Map properties, boolean useShortQNames) { JSONObject propertiesJSON = new JSONObject(); for (QName propertyName : properties.keySet()) { try { String key = nameToString(propertyName, useShortQNames); Serializable value = properties.get(propertyName); propertiesJSON.put(key, propertyToJSON(nodeRef, propertyName, key, value)); } catch (NamespaceException ne) { // ignore properties that do not have a registered namespace if (logger.isDebugEnabled()) logger.debug("Ignoring property '" + propertyName + "' as its namespace is not registered"); } } return propertiesJSON; } /** * Handles the work of converting aspects to JSON. * * @param nodeRef * @param useShortQNames * @return * @throws JSONException */ @SuppressWarnings("unchecked") protected JSONArray apsectsToJSON(NodeRef nodeRef, boolean useShortQNames) { JSONArray aspectsJSON = new JSONArray(); Set aspects = this.nodeService.getAspects(nodeRef); for (QName aspect : aspects) { aspectsJSON.add(nameToString(aspect, useShortQNames)); } return aspectsJSON; } /** * Handles the work of converting all set permissions to JSON. * * @param nodeRef * @return */ @SuppressWarnings("unchecked") protected JSONArray allSetPermissionsToJSON(NodeRef nodeRef) { Set acls = permissionService.getAllSetPermissions(nodeRef); JSONArray permissions = new JSONArray(); for (AccessPermission permission : acls) { StringBuilder buf = new StringBuilder(64); buf.append(permission.getAccessStatus()) .append(';') .append(permission.getAuthority()) .append(';') .append(permission.getPermission()) .append(';').append(permission.isSetDirectly() ? "DIRECT" : "INHERITED"); permissions.add(buf.toString()); } return permissions; } /** * Convert a qname to a string - either full or short prefixed named. * * @param qname * @param isShortName * @return qname string. */ private String nameToString(final QName qname, final boolean isShortName) { String result; if (isShortName) { 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 { result = qname.toString(); } return result; } /** * Return true if the node is locked. * * @param nodeRef * @return */ private boolean isLocked(final NodeRef nodeRef) { boolean locked = false; if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_LOCKABLE) == true) { LockStatus lockStatus = lockService.getLockStatus(nodeRef); if (lockStatus == LockStatus.LOCKED || lockStatus == LockStatus.LOCK_OWNER) { locked = true; } } return locked; } }