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

Java tutorial

Introduction

Here is the source code for org.jahia.services.content.JCRVersionService.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 org.apache.commons.lang.StringUtils;
import org.jahia.utils.comparator.NumericStringComparator;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.slf4j.Logger;
import org.jahia.api.Constants;
import org.jahia.exceptions.JahiaException;
import org.jahia.exceptions.JahiaInitializationException;
import org.jahia.services.JahiaService;
import org.jahia.services.content.decorator.JCRFrozenNodeAsRegular;

import javax.jcr.*;
import javax.jcr.nodetype.NodeType;
import javax.jcr.version.Version;
import javax.jcr.version.VersionHistory;
import javax.jcr.version.VersionIterator;
import javax.jcr.version.VersionManager;

import java.util.*;

/**
 * Version listing and retrieval service. This service offers tools to list the versions in a user-friendly way,
 * collapsing internal versions into date-related changes, retrieving comments, etc...
 *
 * @author loom
 *         Date: Mar 10, 2010
 *         Time: 10:06:02 AM
 */
public class JCRVersionService extends JahiaService {
    public static final DateTimeFormatter DATE_FORMAT = DateTimeFormat.forPattern("yyyy_MM_dd_HH_mm_ss");

    private static JCRVersionService instance;

    private static transient Logger logger = org.slf4j.LoggerFactory.getLogger(JCRVersionService.class);

    /**
     * Get the singleton instance of the JCRPublicationService
     *
     * @return the singleton instance of the JCRPublicationService
     */
    public synchronized static JCRVersionService getInstance() {
        if (instance == null) {
            instance = new JCRVersionService();
        }
        return instance;
    }

    private Set<String> mixinsToRemoveOnDestination = new HashSet<String>(
            Arrays.asList(new String[] { "jmix:referencesInField" }));

    private Set<String> nodetypesToSkipWhenSynchronizing = new HashSet<String>(
            Arrays.asList(new String[] { "jnt:referenceInField" }));

    @Override
    public void start() throws JahiaInitializationException {
    }

    @Override
    public void stop() throws JahiaException {
    }

    /**
     * Retrieves the list of versions, ignoring internal version created in the publication process.
     *
     * @param session the session to use to retrieve the versions
     * @param node    the node for which to retrieve the versions
     * @return a List of VersionInfo objects containing the resolved versions, as well as extra information such as the
     *         checkinDate if available.
     * @throws RepositoryException happens if there was a problem retrieving the list of versions.
     */
    public List<VersionInfo> getVersionInfos(Session session, JCRNodeWrapper node) throws RepositoryException {
        VersionHistory versionHistory = session.getWorkspace().getVersionManager()
                .getVersionHistory(node.getPath());

        VersionIterator versions = versionHistory.getAllVersions();
        return getVersionsInfos(versionHistory, versions);
    }

    public List<VersionInfo> getLinearVersionInfos(Session session, JCRNodeWrapper node)
            throws RepositoryException {
        VersionHistory versionHistory = session.getWorkspace().getVersionManager()
                .getVersionHistory(node.getPath());

        VersionIterator versions = versionHistory.getAllLinearVersions();
        return getVersionsInfos(versionHistory, versions);
    }

    private List<VersionInfo> getVersionsInfos(VersionHistory versionHistory, VersionIterator versions)
            throws RepositoryException {
        if (versions.hasNext()) {
            versions.nextVersion();
            // the first is the root version, which has no properties, so we will ignore it.
        }
        List<VersionInfo> versionList = new ArrayList<VersionInfo>();
        while (versions.hasNext()) {
            Version v = versions.nextVersion();
            String[] versionLabels = versionHistory.getVersionLabels(v);
            if (versionLabels != null && versionLabels.length > 0) {
                for (String string : versionLabels) {
                    VersionInfo versionInfo = new VersionInfo(v, string, 0);
                    versionList.add(versionInfo);
                }
            }
        }
        Collections.sort(versionList, new NumericStringComparator<VersionInfo>(
                ((JCRSessionWrapper) versionHistory.getSession()).getLocale()));
        return versionList;
    }

    /**
     * Finds the closest version in a version history to a specific date.
     *
     * @param vh          the version history in which to lookup versions
     * @param versionDate the date to compare with. Note that it will find the closest version at OR BEFORE the date
     * @return the closest version at or before the date specified.
     * @throws RepositoryException
     */
    public static Version findClosestVersion(VersionHistory vh, Date versionDate) throws RepositoryException {
        VersionIterator vi = null;
        try {
            vi = vh.getAllLinearVersions();
        } catch (ItemNotFoundException e) {
            String[] labels = vh.getVersionLabels();
            for (String label : labels) {
                if (label.startsWith(vh.getSession().getWorkspace().getName() + "_removed")) {
                    try {
                        Date removedAt = DATE_FORMAT.parseDateTime(StringUtils.substringAfter(label,
                                vh.getSession().getWorkspace().getName() + "_removed_at_")).toDate();
                        if (removedAt.before(versionDate)) {
                            return null;
                        }
                    } catch (IllegalArgumentException e1) {
                        logger.error("Cannot parse deletion date for label " + label, e1);
                        return null;
                    }
                    Version base = vh.getVersionByLabel(label);
                    LinkedList<Version> versions = new LinkedList<Version>();
                    while (base != null) {
                        versions.addFirst(base);
                        Version[] preds = base.getPredecessors();
                        if (preds.length == 0) {
                            base = null;
                        } else {
                            base = preds[0];
                        }
                    }
                    vi = new VersionIteratorImpl(versions.iterator(), versions.size());
                    break;
                }
            }
            if (vi == null) {
                return null;
            }
        }

        if (vi.getSize() <= 1) {
            return null;
        }

        Version lastVersion = null;
        Version closestVersion = null;
        if (vi.hasNext()) {
            vi.nextVersion();
            // the first is the root version, which has no properties, so we will ignore it.
        }
        String nodeTitle = null;
        StringBuilder propertyString = null;
        while (vi.hasNext()) {
            Version v = vi.nextVersion();
            if (logger.isDebugEnabled()) {
                try {
                    Node frozenNode = v.getFrozenNode();
                    propertyString = new StringBuilder();
                    PropertyIterator propertyIterator = frozenNode.getProperties();
                    while (propertyIterator.hasNext()) {
                        Property property = propertyIterator.nextProperty();
                        propertyString.append("  ");
                        propertyString.append(property.getName());
                        propertyString.append("=");
                        if (property.isMultiple()) {
                            for (Value value : property.getValues()) {
                                propertyString.append(value.getString());
                                propertyString.append(",");
                            }
                        } else {
                            propertyString.append(property.getValue().getString());
                        }
                        propertyString.append("\n");
                    }
                } catch (IllegalStateException e) {
                    propertyString.append(e.getMessage()).append("\n");
                }
            }
            Date checkinDate = null;
            boolean checkinDateAvailable = false;
            if (v.getCreated().getTime().compareTo(versionDate) > 0) {
                // this can happen if we have a checkinDate, but try to resolve using the creation date.
                closestVersion = lastVersion;
                break;
            }
            if (logger.isDebugEnabled()) {
                logger.debug("Version " + v.getName() + " checkinDateAvailable=" + checkinDateAvailable
                        + " checkinDate=" + checkinDate + " created=" + v.getCreated().getTime() + " properties:"
                        + propertyString.toString());
            }
            lastVersion = v;
        }
        if (closestVersion == null && lastVersion != null) {
            // if we haven't found anything, maybe it's the last version that we should be using ?
            if (lastVersion.getCreated().getTime().compareTo(versionDate) <= 0) {
                closestVersion = lastVersion;
            }
        }
        if (closestVersion != null && logger.isDebugEnabled()) {
            logger.debug("Resolved date " + versionDate + " for node title " + nodeTitle + " to closest version "
                    + closestVersion.getName() + " createdTime=" + closestVersion.getCreated().getTime());
        }
        return closestVersion;
    }

    public void restoreVersionLabel(final JCRNodeWrapper node, final Date versionDate, final String label,
            final boolean allSubTree) throws RepositoryException {
        JCRTemplate.getInstance().doExecuteWithSystemSessionAsUser(null, node.getSession().getWorkspace().getName(),
                null, new JCRCallback<Object>() {
                    public Object doInJCR(final JCRSessionWrapper session) throws RepositoryException {
                        String workspace = label != null ? StringUtils.substringBefore(label, "_") : "live";
                        JCRTemplate.getInstance().doExecuteWithSystemSessionAsUser(null, workspace, null,
                                new JCRCallback<Object>() {
                                    public Object doInJCR(final JCRSessionWrapper frozensession)
                                            throws RepositoryException {
                                        JCRNodeWrapper destinationNode = session
                                                .getNodeByUUID(node.getIdentifier());
                                        VersionManager versionManager = session.getWorkspace().getVersionManager();
                                        String path = destinationNode.getPath();
                                        if (!versionManager.isCheckedOut(path)) {
                                            versionManager.checkout(path);
                                        }

                                        // Todo: first get frozen node for this label
                                        frozensession.setVersionLabel(label);
                                        frozensession.setVersionDate(versionDate);
                                        JCRNodeWrapper frozenVersionAsRegular = frozensession
                                                .getNodeByUUID(destinationNode.getIdentifier());

                                        if (frozenVersionAsRegular == null) {
                                            throw new RepositoryException("label version " + label
                                                    + " could not be found on node " + destinationNode.getPath());
                                        }
                                        synchronizeNode(frozenVersionAsRegular, destinationNode, session,
                                                allSubTree);
                                        session.save();
                                        return null;
                                    }
                                });
                        return null;
                    }
                });
    }

    private void synchronizeNode(final JCRNodeWrapper frozenNode, final JCRNodeWrapper destinationNode,
            JCRSessionWrapper session, boolean allSubTree) throws RepositoryException {
        session.checkout(destinationNode);

        NodeType[] mixin = destinationNode.getMixinNodeTypes();
        for (NodeType aMixin : mixin) {
            String mixinName = aMixin.getName();
            if (mixinsToRemoveOnDestination.contains(mixinName)) {
                logger.info("Removing mixin " + mixinName + " on node " + destinationNode.getPath());
                destinationNode.removeMixin(mixinName);
            }
        }

        mixin = frozenNode.getMixinNodeTypes();
        for (NodeType aMixin : mixin) {
            if (!Constants.forbiddenMixinToCopy.contains(aMixin.getName())) {
                logger.info("Adding mixin " + aMixin.getName() + " on node " + destinationNode.getPath());
                destinationNode.addMixin(aMixin.getName());
            }
        }

        if (frozenNode.hasProperty("jcr:language")) {
            destinationNode.setProperty("jcr:language", frozenNode.getProperty("jcr:language").getString());
        }

        PropertyIterator props = frozenNode.getProperties();

        List<String> names = new ArrayList<String>();
        while (props.hasNext()) {
            Property property = props.nextProperty();
            String propertyName = property.getName();
            names.add(propertyName);
            logger.info("Checking property for updating " + propertyName + " from source node "
                    + destinationNode.getPath());
            try {
                if (!property.getDefinition().isProtected()
                        && !Constants.forbiddenPropertiesToCopy.contains(propertyName)) {
                    logger.info("Setting property " + propertyName + " on node " + destinationNode.getPath());
                    if (property.getDefinition().isMultiple() && property.isMultiple()) {
                        destinationNode.setProperty(propertyName, property.getValues());
                    } else {
                        destinationNode.setProperty(propertyName, property.getValue());
                    }
                }
            } catch (Exception e) {
                logger.warn("Unable to copy property '" + propertyName + "'. Skipping.", e);
            }
        }

        PropertyIterator pi = destinationNode.getProperties();
        while (pi.hasNext()) {
            JCRPropertyWrapper oldChild = (JCRPropertyWrapper) pi.next();
            logger.info("Checking property for removal " + oldChild.getName() + " from destination node "
                    + destinationNode.getPath());
            if (!oldChild.getDefinition().isProtected()) {
                if (!names.contains(oldChild.getName())) {
                    logger.info(
                            "Removing property " + oldChild.getName() + " on node " + destinationNode.getPath());
                    oldChild.remove();
                }
            }
        }

        mixin = destinationNode.getMixinNodeTypes();
        for (NodeType aMixin : mixin) {
            if (!frozenNode.isNodeType(aMixin.getName())
                    && !Constants.forbiddenMixinToCopy.contains(aMixin.getName())) {
                logger.info("Removing mixin " + aMixin.getName() + " on node " + destinationNode.getPath());
                destinationNode.removeMixin(aMixin.getName());
            }
        }

        Map<String, JCRNodeWrapper> destinationNodes = new HashMap<String, JCRNodeWrapper>();
        NodeIterator ni = destinationNode.getNodes();
        while (ni.hasNext()) {
            JCRNodeWrapper n = (JCRNodeWrapper) ni.nextNode();
            destinationNodes.put(n.getIdentifier(), n);
        }

        names.clear();
        ni = frozenNode.getNodes();
        while (ni.hasNext()) {
            JCRNodeWrapper child = (JCRNodeWrapper) ni.next();
            // do not handle rights on the node when restore a node
            if (!child.getName().equals("j:acl")) {
                names.add(child.getName());
                if (destinationNodes.containsKey(child.getIdentifier())) {
                    JCRNodeWrapper node = destinationNodes.remove(child.getIdentifier());
                    synchronizeNode(child, node, session, allSubTree);
                } else if (child.getRealNode().getParent().isNodeType(Constants.NT_FROZENNODE)) {
                    String primaryNodeType = child.getPrimaryNodeType().getName();
                    if (!nodetypesToSkipWhenSynchronizing.contains(primaryNodeType)) {
                        JCRNodeWrapper node = destinationNode.addNode(child.getName(), primaryNodeType);
                        synchronizeNode(child, node, session, allSubTree);
                    }
                } else {
                    VersionHistory history;
                    try {
                        history = (VersionHistory) child.getRealNode().getProperty(Constants.JCR_VERSIONHISTORY)
                                .getNode();
                    } catch (RepositoryException e) {
                        history = (VersionHistory) child.getRealNode().getParent().getParent();
                    }
                    Version version = findVersionByLabel(history,
                            ((JCRFrozenNodeAsRegular) child).getVersionLabel());
                    if (version == null) {
                        version = findClosestVersion(history, ((JCRFrozenNodeAsRegular) child).getVersionDate());
                    }
                    if (version != null) {
                        session.save();
                        logger.info(
                                "Restoring node " + child.getPath() + " on parent " + destinationNode.getPath());
                        session.getWorkspace().getVersionManager().restore(child.getPath(), version, false);
                        JCRNodeWrapper node = session.getNode(child.getPath(), false);
                        synchronizeNode(child, node, session, allSubTree);
                    }
                    //child.copy(destinationNode, child.getName(), false);
                }
            }
        }

        for (JCRNodeWrapper oldChild : destinationNodes.values()) {
            if (!names.contains(oldChild.getName())) {
                if ((!oldChild.isNodeType("jmix:publication") || allSubTree)
                        && !oldChild.isNodeType("jnt:translation")) {
                    logger.info("Removing node " + oldChild.getName() + " on node " + destinationNode.getPath());
                    oldChild.remove();
                }
            }
        }

        if (destinationNode.getPrimaryNodeType().hasOrderableChildNodes()) {
            Collections.reverse(names);
            String previous = null;
            for (String name : names) {
                destinationNode.orderBefore(name, previous);
                previous = name;
            }
        }
        if (destinationNode.isNodeType(Constants.MIX_LAST_MODIFIED)) {
            destinationNode.setProperty(Constants.JCR_LASTMODIFIED, GregorianCalendar.getInstance());
        }
    }

    public static Version findVersionByLabel(VersionHistory vh, String label) throws RepositoryException {
        if (label != null && !"".equals(label.trim())) {
            if (vh.hasVersionLabel(label.trim())) {
                VersionIterator allVersions = vh.getAllVersions();
                while (allVersions.hasNext()) {
                    Version version = allVersions.nextVersion();
                    if (Arrays.asList(vh.getVersionLabels(version)).contains(label.trim())) {
                        return version;
                    }
                }
            }
        }
        return null;
    }

    public void addVersionLabel(final JCRNodeWrapper node, final String label) throws RepositoryException {
        JCRTemplate.getInstance().doExecuteWithSystemSessionAsUser(null, node.getSession().getWorkspace().getName(),
                null, new JCRCallback<Object>() {
                    public Object doInJCR(JCRSessionWrapper session) throws RepositoryException {
                        JCRNodeWrapper nodeWrapper = session.getNodeByUUID(node.getIdentifier());
                        VersionManager versionManager = session.getWorkspace().getVersionManager();
                        VersionHistory versionHistory = versionManager.getVersionHistory(node.getPath());
                        String labelWithWs = node.getSession().getWorkspace().getName() + "_" + label;
                        if (!versionHistory.hasVersionLabel(labelWithWs)) {
                            Version version = versionManager.getBaseVersion(node.getPath());
                            if (logger.isDebugEnabled()) {
                                logger.debug("Add version label " + labelWithWs + " on " + node.getPath()
                                        + " for version " + version.getName());
                            }
                            if (nodeWrapper.isVersioned()) {
                                versionHistory.addVersionLabel(version.getName(), labelWithWs, true);
                            }
                        }
                        return null;
                    }
                });
    }

    public void addVersionLabel(final List<String> allUuids, final String label, final String workspace)
            throws RepositoryException {
        JCRTemplate.getInstance().doExecuteWithSystemSessionAsUser(null, workspace, null,
                new JCRCallback<Object>() {
                    public Object doInJCR(JCRSessionWrapper session) throws RepositoryException {
                        VersionManager versionManager = session.getWorkspace().getVersionManager();
                        for (String allUuid : allUuids) {
                            try {
                                JCRNodeWrapper nodeWrapper = session.getNodeByUUID(allUuid);
                                VersionHistory versionHistory = versionManager
                                        .getVersionHistory(nodeWrapper.getPath());
                                String labelWithWs = workspace + "_" + label;
                                if (!versionHistory.hasVersionLabel(labelWithWs)) {
                                    Version version = versionManager.getBaseVersion(nodeWrapper.getPath());
                                    if (logger.isDebugEnabled()) {
                                        logger.debug("Add version label " + labelWithWs + " on "
                                                + nodeWrapper.getPath() + " for version " + version.getName());
                                    }
                                    if (nodeWrapper.isVersioned()) {
                                        versionHistory.addVersionLabel(version.getName(), labelWithWs, true);
                                    }
                                }
                            } catch (RepositoryException e) {
                                logger.debug(e.getMessage(), e);
                            }
                        }
                        return null;
                    }
                });
    }

    public void setMixinsToRemoveOnDestination(Set<String> mixinsToRemoveOnDestination) {
        this.mixinsToRemoveOnDestination = mixinsToRemoveOnDestination;
    }
}