org.jahia.services.content.JCRAutoSplitUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.jahia.services.content.JCRAutoSplitUtils.java

Source

/*
 * ==========================================================================================
 * =                   JAHIA'S DUAL LICENSING - IMPORTANT INFORMATION                       =
 * ==========================================================================================
 *
 *                                 http://www.jahia.com
 *
 *     Copyright (C) 2002-2017 Jahia Solutions Group SA. All rights reserved.
 *
 *     THIS FILE IS AVAILABLE UNDER TWO DIFFERENT LICENSES:
 *     1/GPL OR 2/JSEL
 *
 *     1/ GPL
 *     ==================================================================================
 *
 *     IF YOU DECIDE TO CHOOSE THE GPL LICENSE, YOU MUST COMPLY WITH THE FOLLOWING TERMS:
 *
 *     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 3 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, see <http://www.gnu.org/licenses/>.
 *
 *
 *     2/ JSEL - Commercial and Supported Versions of the program
 *     ===================================================================================
 *
 *     IF YOU DECIDE TO CHOOSE THE JSEL LICENSE, YOU MUST COMPLY WITH THE FOLLOWING TERMS:
 *
 *     Alternatively, commercial and supported versions of the program - also known as
 *     Enterprise Distributions - must 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.services.content.JCRContentUtils.findAvailableNodeName;

import java.text.SimpleDateFormat;
import java.util.*;

import javax.jcr.NodeIterator;
import javax.jcr.PathNotFoundException;
import javax.jcr.RepositoryException;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.jahia.api.Constants;
import org.jahia.utils.Patterns;

/**
 * Utility class for auto-splitting child nodes of a node, based on the provided
 * settings.
 * 
 * @author Thomas Draier 
 * @author Sergiy Shyrkov
 */
public final class JCRAutoSplitUtils {

    private static final Logger logger = org.slf4j.LoggerFactory.getLogger(JCRAutoSplitUtils.class);

    /**
     * Moves the specified node according to the auto-split policy set on the
     * parent node.<br>
     * Splitting is supported by a particular string property value (e.g.
     * creator, node name etc.), first characters or a part (substring) of the
     * that value. Additionally the date fields are supported.<br>
     * For example the following settings
     * <code>property,jcr:creator;date,jcr:created,yyyy;date,jcr:created,MM</code>
     * will split the child nodes first into folders, based on the creator name
     * (jcr:createdBy), than by creation year (jcr:created) and than by creation
     * month (jcr:created).<br>
     * I.e. the node report.pdf, created by user 'sergiy' on 1st or July 2010,
     * will land under:
     * 
     * <pre>
     *    <parent-node>
     *       |_sergiy
     *               |_2010
     *                     |_07
     *                         |_report.pdf
     * </pre>
     * 
     * The intermediate folders will be created.
     * 
     * @param node the node to be moved
     * @return the moved node
     * @throws RepositoryException in case of an error when relocating node
     */
    public static JCRNodeWrapper applyAutoSplitRules(JCRNodeWrapper node) throws RepositoryException {
        return applyAutoSplitRules(node, node.getParent());
    }

    public static JCRNodeWrapper applyAutoSplitRules(JCRNodeWrapper node, JCRNodeWrapper parent)
            throws RepositoryException {
        try {
            String splitConfig = parent.getProperty(Constants.SPLIT_CONFIG).getString();
            String splitType = parent.getProperty(Constants.SPLIT_NODETYPE).getString();
            if (node.isNodeType(splitType)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Aborting auto-splitting on node (UUID=" + node.getIdentifier() + ", path="
                            + node.getPath() + ") since it is applied on the split type.");
                }
                return null;
            }
            if (logger.isDebugEnabled()) {
                logger.debug("Auto-splitting node (UUID=" + node.getIdentifier() + ", path=" + node.getPath()
                        + ") with split config " + splitConfig + " and split node type " + splitType);
            }
            // quick sanity check, to make sure the node hasn't been moved already in the same session !
            try {
                node.getSession().getNode(node.getPath());
            } catch (PathNotFoundException pnfe) {
                logger.warn("Node has already been moved in this session, will not move again !");
                return null;
            }
            String[] config = Patterns.SEMICOLON.split(splitConfig);
            for (String s : config) {
                String[] folderConfig = Patterns.COMMA.split(s);

                String type = folderConfig[0];
                String propertyName = folderConfig[1];

                String key = null;

                try {
                    if (type.equals("constant")) {
                        key = propertyName;
                    } else if (type.equals("property")) {
                        key = getKey(node, propertyName);
                    } else if (type.equals("firstChars")) {
                        key = getKey(node, propertyName);
                        final int index = Integer.parseInt(folderConfig[2]);
                        if (key != null && key.length() > index) {
                            key = key.substring(0, index);
                        }
                    } else if (type.equals("substring")) {
                        key = getKey(node, propertyName);
                        String[] indexes = Patterns.DASH.split(folderConfig[2]);
                        final int startIndex = Integer.parseInt(indexes[0]);
                        final int endIndex = Integer.parseInt(indexes[1]);
                        if (key != null && key.length() > endIndex) {
                            key = key.substring(startIndex, endIndex);
                        }
                    } else if (type.equals("date")) {
                        if (node.hasProperty(propertyName)) {
                            Calendar calendar = node.getProperty(propertyName).getDate();
                            SimpleDateFormat sdf = new SimpleDateFormat(folderConfig[2]);
                            key = sdf.format(calendar.getTime());
                        }
                    }
                } catch (Exception e) {
                    logger.error("Cannot split folder", e);
                    key = null;
                }

                if (key != null) {
                    if (!parent.hasNode(key)) {
                        node.getSession().checkout(parent);
                        parent = parent.addNode(key, splitType);
                    } else {
                        parent = parent.getNode(key);
                    }
                }
            }
            if (!parent.getPath().equals(node.getParent().getPath())) {
                String newPath = parent.getPath() + "/"
                        + findAvailableNodeName(parent, node.getName(), node.isFile());
                node.getSession().move(node.getPath(), newPath);
                return node.getSession().getNode(newPath);
            }
            return null;
        } catch (RepositoryException e) {
            logger.error("Error while trying to move node " + node.getPath(), e);
            return null;
        }
    }

    /**
     * Iterates over all sub nodes of the specified node and applies auto-split
     * rules on them.
     * 
     * @param node the node, which children should be moved to split folders
     * @return a map of nodes that were moved, useful to update any code that was using the sub nodes. The key is the
     * previous node bean, and the value is the new node bean.
     * @throws RepositoryException in case of an error
     */
    public static Map<JCRNodeWrapper, JCRNodeWrapper> applyAutoSplitRulesOnSubnodes(JCRNodeWrapper node)
            throws RepositoryException {
        NodeIterator ni = node.getNodes();
        Map<JCRNodeWrapper, JCRNodeWrapper> modifiedNodes = new HashMap<JCRNodeWrapper, JCRNodeWrapper>();
        while (ni.hasNext()) {
            JCRNodeWrapper existingNode = (JCRNodeWrapper) ni.nextNode();
            JCRNodeWrapper newNode = applyAutoSplitRules(existingNode);
            if (newNode != null) {
                modifiedNodes.put(existingNode, newNode);
            }
        }
        return modifiedNodes;
    }

    /**
     * Adds a mixin type to the specified node to enable auto-splitting of the
     * child nodes, based on the provided configuration.
     * 
     * @param node the node to enable splitting of children on
     * @param splitConfig auto-splitting settings;
     * @param splitFolderNodeType
     * @throws RepositoryException
     */
    public static void enableAutoSplitting(JCRNodeWrapper node, String splitConfig, String splitFolderNodeType)
            throws RepositoryException {
        node.getSession().checkout(node);
        node.addMixin(Constants.JAHIAMIX_AUTOSPLITFOLDERS);
        node.setProperty(Constants.SPLIT_CONFIG, splitConfig);
        node.setProperty(Constants.SPLIT_NODETYPE, splitFolderNodeType);
        node.getSession().save();
    }

    /**
     * This method uses the valueBean to auto-split the node at creation time, instead of moving it like the other
     * auto-splitting methods do it. This way the node is directly created in the proper location.
     * <br>
     * This method will return the created node in the proper location, or null if the node couldn't be created.
     * <br>
     * Splitting is specified using the splitConfig string, which is used to determine how to name the auto-created
     * intermediary nodes, and uses the format :
     * <code>type,propertyName,type_parameters;type,propertyName,type_parameters;...</code>
     *
     * where type is one of :
     * - constant : the property name will be used as a node name
     * - property : the value of the specified property will be used as a node name
     * - firstChars : takes as a type_parameter the number of characters to use from the value of the property name
     * to split
     * - substring : will use a substring of the property's value. The range is specified using "-" as a separator.
     * - date : will use the type_parameter as a configuration for the SimpleDateFormat parser.
     * the propertyName is the name of the property on which this criteria will operate on. There is a reserved
     * "j:nodename" property name that can be used to reference the nodeName passed to the method.
     *
     * For example the following settings
     * <code>property,creator;date,creationDate,yyyy;date,creationDate,MM</code>
     * and the following bean :
     * <pre>
     * public class MyBean {
     *   public String getCreator();
     *   public Date getCreationDate();
     * }
     * </pre>
     * will split the child nodes first into folders, based on the creator property value
     * (creator), than by creation year (creationDate) and than by creation
     * month (creationDate).<br>
     * I.e. the node report.pdf, created by user 'sergiy' on 1st or July 2010,
     * will land under:
     *
     * <pre>
     *    <parent-node>
     *       |_sergiy
     *               |_2010
     *                     |_07
     *                         |_report.pdf
     * </pre>
     *
     * The intermediate folders will be created.
     *
     * @param parentNode the parent node in which to auto-split the node that will be created.
     * @param nodeName the node name to use to create the new node. Note that if the node name is already present, this
     * method will call the findAvailableNodeName automatically to append a number to the node.
     * @param nodeType the node type to use when creating the node at the auto-split location.
     * @param splitConfig the auto-splitting configuration
     * @param splitNodeType the node type to use to create the intermediary nodes when auto-splitting.
     * @param valueBean an Object on which the auto-split rules will be applied. This object must be a proper JavaBean
     * object, as BeanUtils method will be used to evaluate the properties specified in the auto-split configuration.
     * @return the newly added node, at the proper auto-split location.
     * @throws RepositoryException if there is an internal error
     */
    public static JCRNodeWrapper addNodeWithAutoSplitting(JCRNodeWrapper parentNode, String nodeName,
            String nodeType, String splitConfig, String splitNodeType, Object valueBean)
            throws RepositoryException {

        if (logger.isDebugEnabled()) {
            logger.debug(
                    "Adding node with auto-splitting (name=" + nodeName + ", parent path=" + parentNode.getPath()
                            + ") with split config " + splitConfig + " and split node type " + splitNodeType);
        }
        String[] config = Patterns.SEMICOLON.split(splitConfig);
        for (String s : config) {
            String[] folderConfig = Patterns.COMMA.split(s);

            String type = folderConfig[0];
            String propertyName = folderConfig[1];

            String key = null;

            try {
                if (type.equals("constant")) {
                    key = propertyName;
                } else if (type.equals("property")) {
                    key = getKey(valueBean, nodeName, propertyName);
                } else if (type.equals("firstChars")) {
                    key = getKey(valueBean, nodeName, propertyName);
                    final int index = Integer.parseInt(folderConfig[2]);
                    if (key != null && key.length() > index) {
                        key = key.substring(0, index);
                    }
                } else if (type.equals("substring")) {
                    key = getKey(valueBean, nodeName, propertyName);
                    String[] indexes = Patterns.DASH.split(folderConfig[2]);
                    final int startIndex = Integer.parseInt(indexes[0]);
                    final int endIndex = Integer.parseInt(indexes[1]);
                    if (key != null && key.length() > endIndex) {
                        key = key.substring(startIndex, endIndex);
                    }
                } else if (type.equals("date")) {
                    if (PropertyUtils.getSimpleProperty(valueBean, propertyName) != null) {
                        Date date = (Date) PropertyUtils.getSimpleProperty(valueBean, propertyName);
                        SimpleDateFormat sdf = new SimpleDateFormat(folderConfig[2]);
                        key = sdf.format(date);
                    }
                }
            } catch (Exception e) {
                logger.error("Cannot split folder", e);
                key = null;
            }

            if (key != null) {
                if (!parentNode.hasNode(key)) {
                    parentNode.getSession().checkout(parentNode);
                    parentNode = parentNode.addNode(key, splitNodeType);
                } else {
                    parentNode = parentNode.getNode(key);
                }
            }
        }
        JCRNodeWrapper newNode = parentNode.addNode(findAvailableNodeName(parentNode, nodeName), nodeType);
        if (logger.isDebugEnabled()) {
            logger.debug("Node added at " + newNode.getPath());
        }
        return newNode;
    }

    private static String getKey(JCRNodeWrapper node, String propertyName) throws RepositoryException {
        if (propertyName.equals("j:nodename")) {
            return getNodeNameKey(node.getName());
        } else if (node.hasProperty(propertyName)) {
            return node.getProperty(propertyName).getString();
        }
        return null;
    }

    private static String getKey(Object valueBean, String nodename, String propertyName) throws Exception {
        if (propertyName.equals("j:nodename")) {
            return getNodeNameKey(nodename);
        } else if (PropertyUtils.getSimpleProperty(valueBean, propertyName) != null) {
            return PropertyUtils.getSimpleProperty(valueBean, propertyName).toString();
        }
        return null;
    }

    private static String getNodeNameKey(String nodeName) {
        if (nodeName.indexOf(':') > 0) {
            return StringUtils.substringAfter(nodeName, ":");
        } else {
            return nodeName;
        }
    }

}