Package org.jahia.services.content

Source Code of org.jahia.services.content.JCRContentUtils

/**
* This file is part of Jahia, next-generation open source CMS:
* Jahia's next-generation, open source CMS stems from a widely acknowledged vision
* of enterprise application convergence - web, search, document, social and portal -
* unified by the simplicity of web content management.
*
* For more information, please visit http://www.jahia.com.
*
* Copyright (C) 2002-2011 Jahia Solutions Group SA. All rights reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* As a special exception to the terms and conditions of version 2.0 of
* the GPL (or any later version), you may redistribute this Program in connection
* with Free/Libre and Open Source Software ("FLOSS") applications as described
* in Jahia's FLOSS exception. You should have received a copy of the text
* describing the FLOSS exception, and it is also available here:
* http://www.jahia.com/license
*
* Commercial and Supported Versions of the program (dual licensing):
* alternatively, commercial and supported versions of the program may be used
* in accordance with the terms and conditions contained in a separate
* written agreement between you and Jahia Solutions Group SA.
*
* If you are unsure which license is appropriate for your use,
* please contact the sales department at sales@jahia.com.
*/

package org.jahia.services.content;

import static org.jahia.api.Constants.EDIT_WORKSPACE;
import static org.jahia.api.Constants.LIVE_WORKSPACE;

import com.google.common.collect.ImmutableSet;
import org.apache.commons.collections.map.UnmodifiableMap;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.CharUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.jackrabbit.util.ISO9075;
import org.apache.jackrabbit.util.Text;
import org.jahia.api.Constants;
import org.jahia.bin.Jahia;
import org.jahia.data.templates.JahiaTemplatesPackage;
import org.jahia.registries.ServicesRegistry;
import org.jahia.services.SpringContextSingleton;
import org.jahia.services.content.decorator.JCRPortletNode;
import org.jahia.services.render.RenderContext;
import org.jahia.services.render.RenderService;
import org.jahia.services.render.Template;
import org.jahia.services.sites.JahiaSitesBaseService;
import org.jahia.utils.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.jahia.services.content.nodetypes.ExtendedNodeType;
import org.jahia.services.content.nodetypes.ExtendedPropertyDefinition;
import org.jahia.services.content.nodetypes.NodeTypeRegistry;

import com.ibm.icu.text.Normalizer;

import javax.jcr.*;
import javax.jcr.nodetype.ItemDefinition;
import javax.jcr.nodetype.NoSuchNodeTypeException;
import javax.jcr.nodetype.NodeType;
import javax.jcr.nodetype.PropertyDefinition;
import javax.jcr.query.InvalidQueryException;
import javax.jcr.query.Query;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
* Utility class for accessing and manipulation JCR properties.
*
* @author Sergiy Shyrkov
*/
public final class JCRContentUtils {
   
    private  static final Map<String, Boolean> iconsPresence = new ConcurrentHashMap<String, Boolean>(512,0.8f,32);
    private static JCRContentUtils instance;

    private static final Logger logger = LoggerFactory.getLogger(JCRContentUtils.class);
   
    public static boolean check(String icon) {
        Boolean present = iconsPresence.get(icon);
        if (present == null) {
            try {
                present = Jahia.getStaticServletConfig().getServletContext().getResource("/modules/" + icon + ".png") != null;
                iconsPresence.put(icon, present);
            } catch (MalformedURLException e) {
                logger.warn(e.getMessage(), e);
                present = Boolean.FALSE;
            }
        }
        return present;
    }
   
    /**
     * Creates the JCR property value, depending on the type of the corresponding value.
     *
     * @param objectValue
     *            the object value to be converted
     * @param factory
     *            the {@link ValueFactory} instance
     * @return the JCR property value
     */
    public static Value createValue(Object objectValue, ValueFactory factory) {
        if (objectValue instanceof String) {
            return factory.createValue((String) objectValue);
        } else if (objectValue instanceof Boolean) {
            return factory.createValue((Boolean) objectValue);
        } else if (objectValue instanceof Long) {
            return factory.createValue((Long) objectValue);
        } else if (objectValue instanceof Integer) {
            return factory.createValue(((Integer) objectValue).longValue());
        } else if (objectValue instanceof Calendar) {
            return factory.createValue((Calendar) objectValue);
        } else if (objectValue instanceof Date) {
            Calendar c = new GregorianCalendar();
            c.setTime((Date) objectValue);
            return factory.createValue(c);
        } else if (objectValue instanceof byte[] || objectValue instanceof File) {
            InputStream is = null;
            try {
                is = objectValue instanceof File ? new BufferedInputStream(new FileInputStream((File) objectValue))
                        : new ByteArrayInputStream((byte[]) objectValue);
                return factory.createValue(factory.createBinary(is));
            } catch (Exception e) {
                throw new IllegalArgumentException(e);
            } finally {
                IOUtils.closeQuietly(is);
            }
        } else {
            logger.warn("Do not know, how to handle value of type {}", objectValue.getClass()
                    .getName());
        }
        return null;
    }

   

    /**
     * Reverse operation of encodeJCRNamePrefix.
     * Note : this is not yet complete as it depends on the restrictions imposed on XML namespaces as
     * defined here http://www.w3.org/TR/REC-xml-names/
     * @param encodedPrefix
     * @return
     */
    public static String decodeJCRNamePrefix(final String encodedPrefix) {
        String originalPrefix = encodedPrefix.replace("\\3A", ":");
        return originalPrefix;
    }

    /**
     * Can be used to delete the Jackrabbit indexes folders for version and workspaces to force re-indexing on the next repository startup.
     * Note please that this method can only be invoked when the repository is not started (offline).
     *
     * @param repositoryHome
     *            the repository home folder
     */
    public static void deleteJackrabbitIndexes(File repositoryHome) {
        if (repositoryHome == null || !repositoryHome.isDirectory()) {
            return;
        }
        logger.info("Removing JCR repository indexes in repository {}", repositoryHome);

        // Remove index directories to force re-indexing on next startup
        org.apache.commons.io.FileUtils.deleteQuietly(new File(repositoryHome, "index"));
        File workspaces = new File(repositoryHome, "workspaces");
        org.apache.commons.io.FileUtils.deleteQuietly(new File(new File(workspaces,
                Constants.EDIT_WORKSPACE), "index"));
        org.apache.commons.io.FileUtils.deleteQuietly(new File(new File(workspaces,
                Constants.LIVE_WORKSPACE), "index"));

        logger.info("...done removing index folders.");
    }

    /**
     * Downloads the JCR content to a temporary file.
     * @param node the JCR node with the file content
     * @return the target file descriptor
     * @throws IOException in case of an error
     */
    public static File downloadFileContent(JCRNodeWrapper node) throws IOException {
        return downloadFileContent(node, File.createTempFile("data", null));
    }

    /**
     * Downloads the JCR content to a specified file.
     * @param node the JCR node with the file content
     * @param targetFile target file to write data into
     * @return the target file descriptor
     * @throws IOException in case of an error
     */
    public static File downloadFileContent(JCRNodeWrapper node, File targetFile) throws IOException {
        InputStream is = node.getFileContent().downloadFile();
        if (is == null) {
            throw new IllegalArgumentException("Provided node has no file content");
        }
        OutputStream os = new BufferedOutputStream(new FileOutputStream(targetFile));
        try {
            IOUtils.copy(is, os);
        } finally {
            IOUtils.closeQuietly(is);
            IOUtils.closeQuietly(os);
        }
        return targetFile;
    }

    /**
     * Encode a JCR qualified form name prefix, according to the grammar defined in section 3.2.5.2
     * of the JCR 2.0 specification http://www.day.com/specs/jcr/2.0/3_Repository_Model.html#Names
     * Note : this is not yet complete as it depends on the restrictions imposed on XML namespaces as
     * defined here http://www.w3.org/TR/REC-xml-names/
     * @param originalPrefix
     * @return
     */
    public static String encodeJCRNamePrefix(final String originalPrefix) {
        String encodedPrefix = originalPrefix.replace(":", "\\3A");
        return encodedPrefix;
    }
   
    public static String escapeLocalNodeName(String name) {
        name = name.trim();
        StringBuilder buffer = new StringBuilder(name.length() * 2);
        for (int i = 0; i < name.length(); i++) {
            char ch = name.charAt(i);
            if (ch == '/' || ch == ':' || ch == '[' || ch == ']'
                || ch == '*' || ch == '|') {
                buffer.append('%');
                buffer.append(Character.toUpperCase(Character.forDigit(ch / 16, 16)));
                buffer.append(Character.toUpperCase(Character.forDigit(ch % 16, 16)));
            } else if (ch == '\t' || ch == '\r' || ch == '\n') {
                buffer.append(' ');
            } else {
                buffer.append(ch);
            }
        }
        return buffer.toString();
    }
   
    public static String escapeNodePath(String path) {
        StringBuilder buffer = new StringBuilder(path.length() * 2);
        for (int i = 0; i < path.length(); i++) {
            char ch = path.charAt(i);
            if (ch == ':' || ch == '[' || ch == ']'
                || ch == '*' || ch == '|') {
                buffer.append('%');
                buffer.append(Character.toUpperCase(Character.forDigit(ch / 16, 16)));
                buffer.append(Character.toUpperCase(Character.forDigit(ch % 16, 16)));
            } else {
                buffer.append(ch);
            }
        }
        return buffer.toString();
    }

    /**
     * Returns the next available name for a node, appending if needed numbers.
     *
     * @param dest the destination node, where the new one will be created
     * @param name the name of the new node
     * @return the next available name for a node, appending if needed numbers
     */
    public static String findAvailableNodeName(Node dest, String name) {
        int i = 1;

        String basename = name;
        int dot = basename.lastIndexOf('.');
        String ext = "";
        if (dot > 0) {
            ext = basename.substring(dot);
            basename = basename.substring(0, dot);
        }
        int und = basename.lastIndexOf('-');
        if (und > -1 && basename.substring(und + 1).matches("[0-9]+")) {
            basename = basename.substring(0, und);
        }

        do {
            try {
                dest.getNode(name);
                String newSuffix = "-" + (i++) + ext;
                name = basename + newSuffix;
                //name has a sizelimit of 32 chars
                if(name.length() > 32) {
                    name = basename.substring(0,(basename.length()<=32?basename.length():32)-newSuffix.length())+newSuffix;
                }
            } catch (RepositoryException e) {
                break;
            }

        } while (true);

        return name;
    }

    public static JCRNodeWrapper findDisplayableNode(JCRNodeWrapper node, RenderContext context) {
        Template template = null;
        JCRNodeWrapper currentNode = node;
        try {
            while (template == null && currentNode != null) {
                template = RenderService.getInstance().resolveTemplate(new org.jahia.services.render.Resource(
                        currentNode, "html", null, org.jahia.services.render.Resource.CONFIGURATION_PAGE), context);
                if (template == null) {
                    currentNode = currentNode.getParent();
                }
            }
        } catch (Exception e) {
            currentNode = node;
        }
        if (currentNode == null) {
            currentNode = node;
        }
        return currentNode;
    }

    /**
     * Generates the JCR node name from the provided text by normalizing it,
     * converting to lower case, replacing spaces with dashes and truncating to
     * ${@code maxLength} characters.
     *
     * @param text the original text to be used as a source
     * @param maxLength the maximum length of the resulting name (it will be
     *            truncated if needed)
     * @return the JCR node name from the provided text by normalizing it,
     *         converting to lower case, replacing spaces with dashes and
     *         truncating to ${@code maxLength} characters
     */
    public static String generateNodeName(String text, int maxLength) {
        String nodeName = text;
        final char[] chars = Normalizer.normalize(nodeName, Normalizer.NFKD).toCharArray();
        final char[] newChars = new char[chars.length];
        int j = 0;
        for (char aChar : chars) {
            if (CharUtils.isAsciiAlphanumeric(aChar) || aChar == 32 || aChar == '-') {
                newChars[j++] = aChar;
            }
        }
        nodeName = new String(newChars, 0, j).trim().replaceAll(" ", "-").toLowerCase();
        if (nodeName.length() > maxLength) {
            nodeName = nodeName.substring(0, maxLength);
            if (nodeName.endsWith("-") && nodeName.length() > 2) {
                nodeName = nodeName.substring(0, nodeName.length() - 1);
            }
        }

        return StringUtils.isNotEmpty(nodeName) ? nodeName : "untitled";
    }

    /**
     * Returns a list of child nodes of the provided node, matching the specified node type (multiple node types can be specified, separated
     * by a comma).
     *
     * @param node
     *            the parent node to retrieve children from
     * @param type
     *            the node type to be matched by a retrieved child. Multiple node types can be specified, separated by a comma.
     * @return a list of child nodes of the provided node, matching the specified node type (multiple node types can be specified, separated
     *         by a comma).
     */
    public static List<JCRNodeWrapper> getChildrenOfType(JCRNodeWrapper node, String type) {
        List<JCRNodeWrapper> children = null;
        if (node == null) {
            return null;
        }
        if (type.contains(",")) {
            String[] typesToCheck = type.split(",");
            List<JCRNodeWrapper> matchingChildren = new LinkedList<JCRNodeWrapper>();
            try {
                for (NodeIterator iterator = node.getNodes(); iterator.hasNext();) {
                    Node child = iterator.nextNode();
                    for (String matchType : typesToCheck) {
                        if (child.isNodeType(matchType)) {
                            matchingChildren.add((JCRNodeWrapper) child);
                            break;
                        }
                    }
                }
            } catch (RepositoryException e) {
                logger.warn(e.getMessage(), e);
            }
            children = matchingChildren;
        } else {
            children = getNodes(node, type);
        }
        return children;
    }

    /**
     * Returns a content object key for a node if available. Otherwise returns
     * node name.
     *
     * @param node
     *            the node to get name
     * @return a content object key for a node if available. Otherwise returns
     *         node name
     * @throws RepositoryException
     */
    public static String getContentNodeName(Node node)
            throws RepositoryException {
        return node.getName();
    }

    /**
     * Returns a node path, composed using only content object keys, i.e.
     *
     * <code>/mySite/ContentPage_1/ContentPage_19/ContentContainerList_21/ContentContainer_13</code>
     *
     * @param node
     *            the content node to compute path for
     * @return a node path, composed using only content object keys
     * @throws RepositoryException
     */
    public static String getContentObjectPath(Node node)
            throws RepositoryException {
       
        StringBuilder path = new StringBuilder(64);
        path.append("/").append(getContentNodeName(node));
        Node parent = null;
        try {
            parent = node.getParent();
        } catch (ItemNotFoundException e) {
            parent = null;
        }
        while (parent != null) {
            String name = getContentNodeName(parent);
            path.insert(0, name);
            if (!"/".equals(name) && name.length() > 0) {
                path.insert(0, "/");
            }
            try {
                parent = parent.getParent();
            } catch (ItemNotFoundException e) {
                parent = null;
            }
        }
       
        return path.toString();
    }

    public static NodeIterator getDescendantNodes(JCRNodeWrapper node, String type) {
        try {
            return node.getSession().getWorkspace().getQueryManager().createQuery("select * from ["+type+"] as sel where isdescendantnode(sel,['"+node.getPath()+"'])",
                                                                                  Query.JCR_SQL2).execute().getNodes();
        } catch (InvalidQueryException e) {
            logger.error("Error while retrieving nodes", e);
        } catch (RepositoryException e) {
            logger.error("Error while retrieving nodes", e);
        }
        return NodeIteratorImpl.EMPTY;
    }
   
    /**
     * Get the node or property display name depending on the locale
     *
     * @param item the item to get the label for
     * @param locale current locale
     * @param nodeTypeForSearchingLabel
     * @return the node or property display name depending on the locale
     */
    public static String getDisplayLabel(Object item, Locale locale, ExtendedNodeType nodeTypeForSearchingLabel) {
        if (item != null) {

            try {
                // case of property
                if (item instanceof Property) {
                    Property property = (Property) item;
                    PropertyDefinition propertyDefintion = property.getDefinition();
                    if (propertyDefintion != null && propertyDefintion instanceof ExtendedPropertyDefinition) {
                        ExtendedPropertyDefinition itemDef = (ExtendedPropertyDefinition) propertyDefintion;
                        return itemDef.getLabel(locale,nodeTypeForSearchingLabel);
                    } else {
                        logger.error("PropertyDefinition doesn't implement 'org.jahia.services.content.nodetypes.ExtendedPropertyDefinition'");
                    }
                }
                // case of PropertyDefinition
                else if (item instanceof PropertyDefinition) {
                    if (item instanceof ExtendedPropertyDefinition) {
                        ExtendedPropertyDefinition itemDef = (ExtendedPropertyDefinition) item;
                        return itemDef.getLabel(locale,nodeTypeForSearchingLabel);
                    } else {
                        logger.error("PropertyDefinition doesn't implement 'org.jahia.services.content.nodetypes.ExtendedPropertyDefinition'");
                    }
                }
                // case of node type
                else if (item instanceof NodeType) {
                    NodeType nodeType = (NodeType) item;
                    if (nodeType instanceof ExtendedNodeType) {
                        ExtendedNodeType extendNodeType = (ExtendedNodeType) nodeType;
                        return extendNodeType.getLabel(locale);
                    } else {
                        logger.error("nodeType doesn't implement 'org.jahia.services.content.nodetypes.ExtendedNodeType'");
                    }
                } else {
                    logger.error("Object must be a 'javax.jcr.Property' or 'javax.jcr.nodetype.NodeType'");
                }
            } catch (RepositoryException e) {
                logger.error(e.getMessage(), e);
            }
        }
        return null;
    }   

    public static String getExpandedName(String name, NamespaceRegistry namespaceRegistry) throws RepositoryException {
        if (!name.startsWith("{")) {
            if (name.contains(":")) {
                name = "{" + namespaceRegistry.getURI(StringUtils.substringBefore(name, ":")) + "}" +
                        StringUtils.substringAfter(name, ":");
            } else {
                name = "{}" + name;
            }
        }
       
        return name;
    }
   
    public static String getIcon(ExtendedNodeType type) throws RepositoryException {
        String icon = getIconsFolder(type) + type.getName().replace(':', '_');
        if (check(icon)) {
            return icon;
        }
        for (ExtendedNodeType nodeType : type.getSupertypes()) {
            icon = getIconsFolder(nodeType) + nodeType.getName().replace(':', '_');
            if (check(icon)) {
                return icon;
            }
        }
        return null;
    }   

    public static String getIcon(JCRNodeWrapper f) throws RepositoryException {
        String folder = JCRContentUtils.getIconsFolder(f.getPrimaryNodeType());
        if (f.isFile()) {
            return folder + "jnt_file_" + FileUtils.getFileIcon(f.getName());
        } else if (f.isPortlet()) {
            try {
                JCRPortletNode portletNode = new JCRPortletNode(f);
                if (portletNode.getContextName().equalsIgnoreCase("/rss")) {
                    return folder + "jnt_portlet_rss";
                } else {
                    return folder + "jnt_portlet";
                }
            } catch (RepositoryException e) {
                return folder + "jnt_portlet";
            }
        } else {
            final ExtendedNodeType type = f.getPrimaryNodeType();
            String icon = JCRContentUtils.getIcon(type);
            return icon;
        }
    }
   
    public static String getIconsFolder(final ExtendedNodeType primaryNodeType) throws RepositoryException {
        String folder = primaryNodeType.getSystemId();
        if (folder.startsWith("system-")) {
            folder = "assets";
        } else {
            final JahiaTemplatesPackage aPackage =
                    ServicesRegistry.getInstance().getJahiaTemplateManagerService().getTemplatePackage(folder);
            if (aPackage != null) {
                folder = aPackage.getRootFolder().equals("default")?"assets":aPackage.getRootFolder();
            } else {
                folder = "assets"; // todo handle portlets
            }
        }
        folder += "/icons/";
        return folder;
    }

    public static JCRContentUtils getInstance() {
        if (instance == null) {
            throw new UnsupportedOperationException("JCRContentUtils is not initialized yet");
        }
        return instance;
    }

    /**
     * Returns the last part of a path, so the last name. This method supports expanded form names in the path, so the
     * last slash detection will be properly handled.
     * @param path the path to get the name from
     * @return the name of the leaf in the path.
     */
    public static String getNameFromPath(String path) {
        String[] pathNames = splitJCRPath(path);
        return pathNames[pathNames.length-1];
    }

    public static List<JCRNodeWrapper> getNodes(JCRNodeWrapper node, String type) {
        try {
            List<JCRNodeWrapper> res = new ArrayList<JCRNodeWrapper>();
            NodeIterator ni = node.getNodes();
            while (ni.hasNext()) {
                JCRNodeWrapper child = (JCRNodeWrapper) ni.next();
                if (StringUtils.isEmpty(type) || child.isNodeType(type)) {
                    res.add(child);
                }
            }
            return res;
//            return node.getSession().getWorkspace().getQueryManager().createQuery("select * from ["+type+"] as sel where ischildnode(sel,['"+node.getPath()+"'])",
//                                                                                  Query.JCR_SQL2).execute().getNodes();
//        } catch (InvalidQueryException e) {
//            logger.error("Error while retrieving nodes", e);
        } catch (RepositoryException e) {
            logger.error("Error while retrieving nodes", e);
        }
        return new LinkedList<JCRNodeWrapper>();
    }

    /**
     * Little utility method to retrieve or create a path, building it if necessary. For example let's say that we want
     * to get or create the path from a parentNode : messages/inbox . We can simply pass the parent node, the session
     * and the path to check, and it will either retrieve it if it exists, or create it if it doesn't.
     *
     * Please note that this method also checks out the parent nodes.
     * @param session
     * @param parentNode the parent node in from which we want to retrieve the relative path.
     * @param path the path to retrieve or create. Note that this path MUST be relative
     * @param pathNodeType the type to use for the intermediary nodes (and the last path too !) if they need to be
     * created. Usually you'll want to use Constants.JAHIANT_CONTENTLIST here.
     * @return the leaf that is equivalent to the lowest path value.
     * @throws RepositoryException occurs if there is any problem accessing content or creating the nodes.
     */
    public static JCRNodeWrapper getOrAddPath(JCRSessionWrapper session, JCRNodeWrapper parentNode, String path, String pathNodeType) throws RepositoryException {
        String[] subPaths = splitJCRPath(path);
        JCRNodeWrapper node = parentNode;
        for (String subPath : subPaths) {
            if (StringUtils.isNotBlank(subPath) && !"*".equals(subPath)) {
                try {
                    node = node.getNode(subPath);
                    session.checkout(node);
                } catch (PathNotFoundException e) {
                    if (node != null) {
                        session.checkout(node);
                        node = node.addNode(subPath, pathNodeType);
                    }
                }
            }
        }
        return node;
    }

    /**
     * A small utility method to retrieve the parent path of a path that contains expanded form names.
     * @param path the path for which we want to retrieve the parent path.
     * @return the parent path including all names in expanded or qualified form.
     */
    public static String getParentJCRPath(String path) {
        String[] pathNames = splitJCRPath(path);
        StringBuffer parentPath = new StringBuffer();
        // if we are dealing with an absolute path, we add the initial separator
        if (path.startsWith("/")) {
            parentPath.append("/");
        }
        for (int i=0; i < pathNames.length -1; i++) {
            parentPath.append(pathNames[i]);
            if (i < pathNames.length -2) {
                parentPath.append("/");
            }
        }
        return parentPath.toString();
    }

    public static JCRNodeWrapper getParentOfType(JCRNodeWrapper node,
            String type) {
        JCRNodeWrapper matchingParent = null;
        try {
            JCRNodeWrapper parent = node.getParent();
            while (parent != null) {
                if (parent.isNodeType(type)) {
                    matchingParent = parent;
                    break;
                }
                parent = parent.getParent();
            }
        } catch (ItemNotFoundException e) {
            // we reached the hierarchy top
        } catch (RepositoryException e) {
            logger.error("Error while retrieving nodes parent node. Cause: "
                    + e.getMessage(), e);
        }
        return matchingParent;
    }

    public static JCRNodeWrapper getPathFolder(JCRNodeWrapper root, String name, String options, String nodeType) throws RepositoryException {
        JCRNodeWrapper result = root;
        if (options.contains("initials")) {
            String s = "" + Character.toUpperCase(name.charAt(0));
            if (!result.hasNode(s)) {
                result = result.addNode(s, nodeType);
            } else {
                result = result.getNode(s);
            }
        }
        return result;
    }

    private static ExtendedPropertyDefinition getPropertyDefExtension(
            PropertyDefinition propDef) {
        try {
            return NodeTypeRegistry.getInstance().getNodeType(
                    propDef.getDeclaringNodeType().getName()).getPropertyDefinition(
                    propDef.getName());
        } catch (NoSuchNodeTypeException e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    public static PropertyDefinition getPropertyDefinition(NodeType type,
            String property) throws RepositoryException {
        PropertyDefinition foundDefintion = null;
        PropertyDefinition[] pds = type.getDeclaredPropertyDefinitions();
        for (int i = 0; i < pds.length; i++) {
            PropertyDefinition pd = pds[i];
            if (pd.getName().equals(property)) {
                foundDefintion = pd;
                break;
            }
        }

        return foundDefintion;
    }
   
    public static PropertyDefinition getPropertyDefinition(String nodeType,
            String property) throws RepositoryException {
        return getPropertyDefinition(NodeTypeRegistry.getInstance().getNodeType(
                        nodeType), property);
    }

    public static int getPropertyDefSelector(ItemDefinition itemDef) {
        ExtendedPropertyDefinition propDefExtension = null;
        if (itemDef instanceof PropertyDefinition) {
            propDefExtension = getPropertyDefExtension((PropertyDefinition) itemDef);
        }

        return propDefExtension != null ? propDefExtension.getSelector() : 0;
    }

    /**
     * If the node path contains site information (i.e.
     * <code>/sites/&lt;siteKey&gt;/...</code>) this method returns the site key
     * part; otherwise <code>null</code> is returned.
     *
     * @param jcrNodePath the JCR node path
     * @return if the node path contains site information (i.e.
     *         <code>/sites/&lt;siteKey&gt;/...</code>) this method returns the
     *         site key part; otherwise <code>null</code> is returned
     */
    public static String getSiteKey(String jcrNodePath) {
        return jcrNodePath != null ? (jcrNodePath.startsWith("/sites/") ? StringUtils.substringBetween(jcrNodePath,
                "/sites/", "/") : null) : null;
    }

    public static String getSystemSitePath() {
        return "/sites/" + JahiaSitesBaseService.SYSTEM_SITE_KEY;       
    }

    /**
     * Returns an object value that corresponds to the provided JCR property value depending on its type.
     *
     * @param propertyValue
     *            the JCR property value to be converted
     * @return the object value
     * @throws RepositoryException
     *             in case of a conversion error
     * @throws ValueFormatException
     *             in case of a conversion error
     */
    public static Object getValue(Value propertyValue) throws ValueFormatException,
            RepositoryException {
        Object value = propertyValue.getString();
        switch (propertyValue.getType()) {
            case PropertyType.BOOLEAN:
                value = Boolean.valueOf(propertyValue.getBoolean());
                break;
            case PropertyType.DATE:
                value = propertyValue.getDate();
                break;
            case PropertyType.DECIMAL:
            case PropertyType.LONG:
                value = Long.valueOf(propertyValue.getDecimal().longValue());
                break;
            case PropertyType.DOUBLE:
                value = Double.valueOf(propertyValue.getDouble());
                break;
        }
        return value;
    }

    /**
     * Used by portlet backends to determine if a user is part of a specific permissionName on a node specified by it's
     * UUID
     *
     *
     * @param workspaceName the name of the workspace in which we load the node from.
     * @param permissionName
     * @param nodeUUID
     * @return
     */
    public static boolean hasPermission(String workspaceName, final String permissionName, final String nodeUUID) {
        try {
            JCRSessionWrapper session = JCRTemplate.getInstance().getSessionFactory().getCurrentUserSession(workspaceName);
            JCRNodeWrapper node = session.getNodeByIdentifier(nodeUUID);
            return node.hasPermission(permissionName);
        } catch (Exception e) {
            logger.error("Error while checking permission "+ permissionName +" for node UUID " + nodeUUID, e);
        }
        return false;
    }

    /**
     * Performs import of JCR data using provided skeleton locations. This method is used when a new virtual site or a new user is created.
     *
     *
     * @param skeletonLocations
     *            the (pattern-based) location to search for resources. Multiple locations can be provided separated by comma (or any
     *            delimiter, defined in {@link org.springframework.context.ConfigurableApplicationContext#CONFIG_LOCATION_DELIMITERS} )
     * @param targetPath
     *            target JCR path to perform import into
     * @param session
     *            the current JCR session
     * @param replacements
     * @throws IOException
     *             in case of skeleton lookup error
     * @throws InvalidSerializedDataException
     *             import related exception
     * @throws RepositoryException
     *             general JCR exception
     */
    public static void importSkeletons(String skeletonLocations, String targetPath,
                                       JCRSessionWrapper session) throws IOException, InvalidSerializedDataException,
            RepositoryException {
        importSkeletons(skeletonLocations, targetPath, session, ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW, null);
    }

    /**
     * Performs import of JCR data using provided skeleton locations. This method is used when a new virtual site or a new user is created.
     *
     *
     * @param skeletonLocations
     *            the (pattern-based) location to search for resources. Multiple locations can be provided separated by comma (or any
     *            delimiter, defined in {@link org.springframework.context.ConfigurableApplicationContext#CONFIG_LOCATION_DELIMITERS} )
     * @param targetPath
     *            target JCR path to perform import into
     * @param session
     *            the current JCR session
     * @param importUUIDBehavior the {@link javax.jcr.ImportUUIDBehavior} to use during import
     * @param replacements
     * @throws IOException
     *             in case of skeleton lookup error
     * @throws InvalidSerializedDataException
     *             import related exception
     * @throws RepositoryException
     *             general JCR exception
     */
    public static void importSkeletons(String skeletonLocations, String targetPath,
                                       JCRSessionWrapper session, int importUUIDBehavior, Map<String, String> replacements) throws IOException, InvalidSerializedDataException,
            RepositoryException {
        for (Resource resource : SpringContextSingleton.getInstance().getResources(
                skeletonLocations)) {
            logger.info("Importing data using skeleton [{}]", resource);
            InputStream is = null;
            try {
                is = resource.getInputStream();
                session.importXML(targetPath, is, importUUIDBehavior, true, replacements);
            } finally {
                IOUtils.closeQuietly(is);
            }
        }
    }

    /**
     * Performs import of JCR data using provided skeleton locations. This method is used when a new virtual site or a new user is created.
     *
     *
     * @param skeletonLocations
     *            the (pattern-based) location to search for resources. Multiple locations can be provided separated by comma (or any
     *            delimiter, defined in {@link org.springframework.context.ConfigurableApplicationContext#CONFIG_LOCATION_DELIMITERS} )
     * @param targetPath
     *            target JCR path to perform import into
     * @param session
     *            the current JCR session
     * @param replacements
     * @throws IOException
     *             in case of skeleton lookup error
     * @throws InvalidSerializedDataException
     *             import related exception
     * @throws RepositoryException
     *             general JCR exception
     */
    public static void importSkeletons(String skeletonLocations, String targetPath,
                                       JCRSessionWrapper session, Map<String, String> replacements) throws IOException, InvalidSerializedDataException,
            RepositoryException {
        importSkeletons(skeletonLocations, targetPath, session, ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW, replacements);
    }

    /**
     * Returns <code>true</code> if the node is locked and cannot be edited by current user.
     *
     * @param node
     *            the node to be tested
     * @return <code>true</code> if the node is locked and cannot be edited by current user
     * @throws RepositoryException
     *             in case of a JCR error
     */
    public static boolean isLockedAndCannotBeEdited(JCRNodeWrapper node) throws RepositoryException {
        if (node == null) {
            return false;
        }

        String username = node.getSession().getUser().getUsername();
        String lockOwner = node.getLockOwner();
        boolean isLocked = node.isLocked() && (lockOwner == null || !lockOwner.equals(username));
        if (!isLocked && node.hasProperty(Constants.JAHIA_LOCKTYPES)) {
            Value[] values = node.getProperty(Constants.JAHIA_LOCKTYPES).getValues();
            for (Value value : values) {
                if (!value.getString().startsWith(username)) {
                    isLocked = true;
                    break;
                }
            }
        }

        return isLocked;
    }

    /**
     * Returns <code>true</code> if the provided node matches the specified node type (multiple node types can be specified, separated by a
     * comma).
     *
     * @param node
     *            the node to be tested
     * @param type
     *            the node type to be matched. Multiple node types can be specified, separated by a comma.
     * @return <code>true</code> if the provided node matches the specified node type (multiple node types can be specified, separated by a
     *         comma).
     * @throws RepositoryException
     *             in case of a JCR error
     */
    public static boolean isNodeType(JCRNodeWrapper node, String type) throws RepositoryException {
        if (node == null || StringUtils.isEmpty(type)) {
            return false;
        }
        boolean matches = false;
        if (type.contains(",")) {
            String[] types = type.split(",");
            for (String matchType : types) {
                if (node.isNodeType(matchType)) {
                    matches = true;
                    break;
                }
            }
        } else {
            matches = node.isNodeType(type);
        }

        return matches;
    }

    /**
     * Returns <code>true</code> if the provided node matches one of the specified node types.
     *
     * @param node
     *            the node to be tested
     * @param types
     *            an array of node types to be matched.
     * @return <code>true</code> if the provided node matches one of the specified node types
     * @throws RepositoryException
     *             in case of a JCR error
     */
    public static boolean isNodeType(JCRNodeWrapper node, Iterable<String> types)
            throws RepositoryException {
        if (node == null || types == null) {
            return false;
        }
        boolean matches = false;
        for (String matchType : types) {
            if (node.isNodeType(matchType)) {
                matches = true;
                break;
            }
        }

        return matches;
    }

    /**
     * Returns <code>true</code> if the provided UUID string does not seem like a valid Jackrabbit node UUID. In such a case it comes from a
     * different provide, like VFS.
     *
     * @param uuid
     *            the UUID string to check
     * @return code>true</code> if the provided UUID string does not seem like a valid Jackrabbit node UUID. In such a case it comes from a
     *         different provide, like VFS
     */
    public static boolean isNotJcrUuid(String uuid) {
        return StringUtils.isEmpty(uuid) || uuid.contains("/");
    }

    public static boolean isValidFilename(String name) {
        return (!name.startsWith(" ") && !name.endsWith(" ") && name.matches("([^\\*:/\\\\<>|?\"])*"));
    }

    /**
     * Validates if the specified name is a valid JCR workspace (either <code>default</code> or <code>live</code>).
     *
     * @param workspace
     *            the workspace name to check
     * @return <code>true</code> if the specified name is a valid JCR workspace (either <code>default</code> or <code>live</code>);
     *         otherwise returns <code>false</code>
     */
    public static final boolean isValidWorkspace(String workspace) {
        return isValidWorkspace(workspace, false);
    }

    /**
     * Validates if the specified name is a valid JCR workspace (either <code>default</code> or <code>live</code>).
     *
     * @param workspace
     *            the workspace name to check
     * @param allowBlank set to true if the workspace name is allowed to be null or empty
     * @return <code>true</code> if the specified name is a valid JCR workspace (either <code>default</code> or <code>live</code>);
     *         otherwise returns <code>false</code>
     */
    public static final boolean isValidWorkspace(String workspace, boolean allowBlank) {
        return StringUtils.isEmpty(workspace) ? allowBlank : EDIT_WORKSPACE.equals(workspace)
                || LIVE_WORKSPACE.equals(workspace);
    }

    /**
     * Small utility method to help with proper namespace registration in all JCR providers.
     * @param session
     * @param prefix
     * @param uri
     * @throws RepositoryException
     */
    public static void registerNamespace(Session session, String prefix, String uri) throws RepositoryException {
        NamespaceRegistry namespaceRegistry = session.getWorkspace().getNamespaceRegistry();
        Set<String> prefixes = ImmutableSet.copyOf(namespaceRegistry.getPrefixes());
        if (!prefixes.contains(prefix)) {
            namespaceRegistry.registerNamespace(prefix, uri);
            session.setNamespacePrefix(prefix, uri);
        }

    }


    /**
     * Returns the number of elements in the provided iterator.
     *
     * @param iterator the item iterator to check the size
     * @return the number of elements in the provided iterator
     */
    public static long size(RangeIterator iterator) {
        long size = iterator.getSize();
        if (size <= 0) {
            size = 0;
            while (iterator.hasNext()) {
                size++;
                iterator.next();
            }
        }

        return size;
    }

    /**
     * Utility method to split a JCR path into names. Note that this method supports expanded name notation (using
     * URIs), such as {http://www.jcp.org/jcr/1.0}read, as it is tricky to split simply using the "/" character when
     * URIs are present.
     * @param path a relative or absolute path, with node names in qualified or expanded form
     * @return an array of String instances that contain the node names in order of the path.
     */
    public static String[] splitJCRPath(String path) {
        List<String> result = new ArrayList<String>();
        int pathPos = 0;

        if (path.startsWith("/")) {
            pathPos++;
        }

        int nextSlashPos = -1;
        do {
            StringBuffer currentName = new StringBuffer();
            if (path.indexOf('{', pathPos) == pathPos) {
                int endingBracketPos = path.indexOf('}', pathPos+1);
                currentName.append(path.substring(pathPos, endingBracketPos+1));
                pathPos = endingBracketPos + 1;
            }
            nextSlashPos = path.indexOf('/', pathPos);
            if (nextSlashPos > -1) {
                currentName.append(path.substring(pathPos, nextSlashPos));
                pathPos = nextSlashPos + 1;
            } else {
                currentName.append(path.substring(pathPos));
            }
            result.add(currentName.toString());
        } while (nextSlashPos > -1);

        return result.toArray(new String[result.size()]);
    }

    /**
     * Convert a path string to encoded path Strings in XPATH queries
     *
     * @param str
     *            Any string.
     * @return A valid string path suitable for use in XPATH queries
     */
    public static String stringToJCRPathExp(String str) {
        return ISO9075.encode(Text.escapeIllegalJcrChars(str));
    }

    /**
     * Convert a string to a JCR search expression literal, suitable for use in
     * jcr:contains() (inside XPath) or contains (SQL2). The characters - and " have
     * special meaning, and may be escaped with a backslash to obtain their
     * literal value. See JSR-283 spec v2.0, Sec. 4.6.6.19.
     *
     * @param str
     *            Any string.
     * @return A valid string literal suitable for use in
     *         JCR contains clauses, including enclosing quotes.
     */
    public static String stringToJCRSearchExp(String str) {
        // Escape ' and \ everywhere, preceding them with \ except when \
        // appears
        // in one of the combinations \" or \-
        return stringToQueryLiteral(Text.escapeIllegalXpathSearchChars(str));
    }


    /**
     * Convert a string to a literal, suitable for inclusion
     * in a query. See JSR-283 spec v2.0, Sec. 4.6.6.19.
     *
     * @param str
     *            Any string.
     * @return A valid JCR query string literal, including enclosing quotes.
     */
    public static String stringToQueryLiteral(String str) {
        // Single quotes needed for jcr:contains()
        return "'" + str.replaceAll("'", "''") + "'";
    }

    /**
     * Decode an encoded JCR local name encoded with the encodeJCRLocalName method
     * Note : this implementation is not yet complete as it does not handle the XML
     * restrictions yet, only the JCR ones.
     * @param encodedLocalName
     * @return
     */
    public static String unescapeLocalNodeName(final String encodedLocalName) {
        return Text.unescapeIllegalJcrChars(encodedLocalName);
    }

    private Map<String, String> fileExtensionIcons;
   
    private Map<String, List<String>> mimeTypes;

    private NameGenerationHelper nameGenerationHelper;

    /**
     * Initializes an instance of this class.
     *
     * @param mimeTypes
     *            a map with mime type mappings
     * @param fileExtensionIcons mapping between file extensions and corresponding icons
     */
    @SuppressWarnings("unchecked")
    public JCRContentUtils(Map<String, List<String>> mimeTypes, Map<String, String> fileExtensionIcons) {
        super();
        instance = this;
        this.mimeTypes = UnmodifiableMap.decorate(mimeTypes);
        this.fileExtensionIcons = UnmodifiableMap.decorate(fileExtensionIcons);
    }

    /**
     * Generates the node name by using a configurable NameGenerationHelper implementation.
     *
     * @return a node name generated by configurable helper service
     */   
    public String generateNodeName(JCRNodeWrapper parent, String nodeType) {
        return getNameGenerationHelper().generatNodeName(parent, nodeType);
    }

    /**
     * Generates the node name by using a configurable NameGenerationHelper implementation.
     *
     * @return a node name generated by configurable helper service
     */   
    public String generateNodeName(JCRNodeWrapper parent, String defaultLanguage, ExtendedNodeType nodeType) {
        return getNameGenerationHelper().generatNodeName(parent, defaultLanguage, nodeType);
    }
   
    /**
     * Returns a mapping between file extensions and corresponding icons.
     *
     * @return a mapping between file extensions and corresponding icons
     */
    public Map<String, String> getFileExtensionIcons() {
        return fileExtensionIcons;
    }

    /**
     * Return a map of mime types (file formats) to be available in the
     * advanced search form.
     *
     * @return a map of mime types (file formats) to be available in the
     *         advanced search form
     */
    public Map<String, List<String>> getMimeTypes() {
        return mimeTypes;
    }

    public NameGenerationHelper getNameGenerationHelper() {
        return nameGenerationHelper;
    }

    public void setNameGenerationHelper(NameGenerationHelper nameGenerationHelper) {
        this.nameGenerationHelper = nameGenerationHelper;
    }
}
TOP

Related Classes of org.jahia.services.content.JCRContentUtils

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.