info.magnolia.cms.core.version.VersionManager.java Source code

Java tutorial

Introduction

Here is the source code for info.magnolia.cms.core.version.VersionManager.java

Source

/**
 *
 * Magnolia and its source-code is licensed under the LGPL.
 * You may copy, adapt, and redistribute this file for commercial or non-commercial use.
 * When copying, adapting, or redistributing this document in keeping with the guidelines above,
 * you are required to provide proper attribution to obinary.
 * If you reproduce or distribute the document without making any substantive modifications to its content,
 * please use the following attribution line:
 *
 * Copyright 1993-2006 obinary Ltd. (http://www.obinary.com) All rights reserved.
 *
 */
package info.magnolia.cms.core.version;

import info.magnolia.cms.core.*;
import info.magnolia.cms.util.Rule;
import info.magnolia.cms.util.RuleBasedContentFilter;
import info.magnolia.cms.util.ExclusiveWrite;
import info.magnolia.cms.beans.config.VersionConfig;
import info.magnolia.context.MgnlContext;

import javax.jcr.version.Version;
import javax.jcr.version.VersionHistory;
import javax.jcr.version.VersionIterator;
import javax.jcr.version.VersionException;
import javax.jcr.*;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.commons.io.IOUtils;

import java.io.*;
import java.util.List;

/**
 * Singleton class which should be used for any operation related to versioning
 * VersionManager synchronizes all operations like add version, restore version and remove version
 * but it does not synchronize between operations
 *
 * @author Sameer Charles
 * $Id: VersionManager.java 3301 2006-05-30 15:03:40Z scharles $
 */
public final class VersionManager {

    /**
     * version data base
     * */
    public static final String VERSION_WORKSPACE = "mgnlVersion";

    /**
     * version workspace system path
     * */
    public static final String TMP_REFERENCED_NODES = "mgnl:tmpReferencedNodes";

    /**
     * version system node, holds this node version specific data
     * */
    private static final String SYSTEM_NODE = "mgnl:versionMetaData";

    /**
     *  property name for collection rule
     *
     * */
    protected static final String PROPERTY_RULE = "Rule";

    /**
     * jcr root version
     * */
    public static final String ROOT_VERSION = "jcr:rootVersion";

    /**
     * Logger.
     */
    private static Logger log = LoggerFactory.getLogger(VersionManager.class);

    /**
     * singleton
     * */
    private static final VersionManager thisInstance = new VersionManager();

    /**
     * do not instanciate
     * */
    private VersionManager() {
        try {
            this.createInitialStructure();
        } catch (RepositoryException re) {
            log.error("Failed to initialize VersionManager");
            log.error(re.getMessage(), re);
        }
    }

    /**
     * create structure needed for version store workspace
     * @throws RepositoryException if unable to create magnolia system structure
     * */
    private void createInitialStructure() throws RepositoryException {
        HierarchyManager hm = MgnlContext.getSystemContext().getHierarchyManager(VERSION_WORKSPACE);
        try {
            Content tmp = hm.getContent("/" + VersionManager.TMP_REFERENCED_NODES);
            // remove nodes if they are no longer referenced within this workspace
            NodeIterator children = tmp.getJCRNode().getNodes();
            while (children.hasNext()) {
                Node child = children.nextNode();
                if (child.getReferences().getSize() < 1) {
                    child.remove();
                }
            }
        } catch (PathNotFoundException e) {
            hm.createContent("", VersionManager.TMP_REFERENCED_NODES, ItemType.SYSTEM.getSystemName());
        }
        hm.save();
    }

    /**
     * get instance
     * */
    public static VersionManager getInstance() {
        return thisInstance;
    }

    /**
     * add version of the specified node and all child nodes while ignoring the same node type
     *
     * @param node to be versioned
     * @return newly created version node
     * @throws UnsupportedOperationException if repository implementation does not support Versions API
     * @throws javax.jcr.RepositoryException if any repository error occurs
     */
    public synchronized Version addVersion(Content node)
            throws UnsupportedRepositoryOperationException, RepositoryException {
        //Rule rule = new Rule(new String[] {node.getNodeType().getName(), ItemType.SYSTEM.getSystemName()});
        Rule rule = new Rule(node.getNodeTypeName() + "," + ItemType.SYSTEM.getSystemName(), ",");
        rule.reverse();
        return this.addVersion(node, rule);
    }

    /**
     * add version of the specified node and all child nodes while ignoring the same node type
     *
     * @param node to be versioned
     * @return newly created version node
     * @throws UnsupportedOperationException if repository implementation does not support Versions API
     * @throws javax.jcr.RepositoryException if any repository error occurs
     */
    public synchronized Version addVersion(Content node, Rule rule)
            throws UnsupportedRepositoryOperationException, RepositoryException {
        List permissions = this.getAccessManagerPermissions();
        this.impersonateAccessManager(null);
        try {
            return this.createVersion(node, rule);
        } catch (RepositoryException re) {
            // since add version is synchronized on a singleton object, its safe to revert all changes made in
            // the session attached to workspace - mgnlVersion
            log.error(
                    "failed to copy versionable node to version store, reverting all changes made in this session");
            getHierarchyManager().refresh(false);
            throw re;
        } finally {
            this.revertAccessManager(permissions);
        }
    }

    /**
     * create version of the specified node and all child nodes based on the given <code>Rule</code>
     *
     * @param node to be versioned
     * @param rule
     * @return newly created version node
     * @throws UnsupportedOperationException if repository implementation does not support Versions API
     * @throws javax.jcr.RepositoryException if any repository error occurs
     */
    private Version createVersion(Content node, Rule rule)
            throws UnsupportedRepositoryOperationException, RepositoryException {
        if (VersionConfig.getMaxVersionIndex() < 1) {
            log.debug("Ignore create version, MaxVersionIndex < 1");
            log.debug("Returning root version of the source node");
            return node.getJCRNode().getVersionHistory().getRootVersion();
        }
        CopyUtil.getInstance().copyToversion(node, new RuleBasedContentFilter(rule));
        Content versionedNode = this.getVersionedNode(node);
        Content systemInfo = this.getSystemNode(versionedNode);
        // add serialized rule which was used to create this version
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        try {
            ObjectOutput objectOut = new ObjectOutputStream(out);
            objectOut.writeObject(rule);
            objectOut.flush();
            objectOut.close();
            NodeData nodeData;
            // PROPERTY_RULE is not a part of MetaData to allow versioning of node types which does NOT support MetaData
            if (!systemInfo.hasNodeData(PROPERTY_RULE))
                nodeData = systemInfo.createNodeData(PROPERTY_RULE);
            else
                nodeData = systemInfo.getNodeData(PROPERTY_RULE);
            nodeData.setValue(out.toString());
        } catch (IOException e) {
            throw new RepositoryException("Unable to add serialized Rule to the versioned content");
        }
        // temp fix, MgnlContext should always have user either logged-in or anonymous
        String userName = "";
        if (MgnlContext.getUser() != null)
            userName = MgnlContext.getUser().getName();
        // add all system properties for this version
        if (!systemInfo.hasNodeData(ContentVersion.VERSION_USER))
            systemInfo.createNodeData(ContentVersion.VERSION_USER).setValue(userName);
        else
            systemInfo.getNodeData(ContentVersion.VERSION_USER).setValue(userName);
        if (!systemInfo.hasNodeData(ContentVersion.NAME))
            systemInfo.createNodeData(ContentVersion.NAME).setValue(node.getName());
        else
            systemInfo.getNodeData(ContentVersion.NAME).setValue(node.getName());

        versionedNode.save();
        // add version
        Version newVersion = versionedNode.getJCRNode().checkin();
        versionedNode.getJCRNode().checkout();
        try {
            this.setMaxVersionHistory(versionedNode);
        } catch (RepositoryException re) {
            log.error("Failed to limit version history to the maximum configured", re);
            log.error("New version has already been created");
        }

        return newVersion;
    }

    /***
     * get node from version store
     * @param node
     * */
    protected synchronized Content getVersionedNode(Content node) throws RepositoryException {
        return getVersionedNode(node.getUUID());
    }

    /***
     * get node from version store
     * @param uuid
     * */
    protected synchronized Content getVersionedNode(String uuid) throws RepositoryException {
        List permissions = this.getAccessManagerPermissions();
        this.impersonateAccessManager(null);
        try {
            return getHierarchyManager().getContentByUUID(uuid);
        } catch (ItemNotFoundException e) {
            return null;
        } catch (RepositoryException re) {
            throw re;
        } finally {
            this.revertAccessManager(permissions);
        }
    }

    /**
     * set version history to max version possible
     * @param node
     * @throws RepositoryException if failed to get VersionHistory or fail to remove
     * */
    private void setMaxVersionHistory(Content node) throws RepositoryException {
        VersionHistory history = node.getJCRNode().getVersionHistory();
        VersionIterator versions = history.getAllVersions();
        long indexToRemove = (versions.getSize() - 1) - VersionConfig.getMaxVersionIndex();
        if (indexToRemove > 0) {
            // skip root version
            versions.nextVersion();
            // remove the version after rootVersion
            for (; indexToRemove > 0; indexToRemove--) {
                history.removeVersion(versions.nextVersion().getName());
            }
        }
    }

    /**
     * get history of this node as recorded in the version store
     *
     * @param node
     * @return version history of the given node
     * @throws UnsupportedOperationException if repository implementation does not support Versions API
     * @throws javax.jcr.RepositoryException if any repository error occurs
     */
    public synchronized VersionHistory getVersionHistory(Content node)
            throws UnsupportedRepositoryOperationException, RepositoryException {
        Content versionedNode = this.getVersionedNode(node);
        if (versionedNode == null) {
            // node does not exist in version store so no version history
            log.info("No VersionHistory found for this node");
            return null;
        }
        return versionedNode.getJCRNode().getVersionHistory();
    }

    /**
     * get named version
     * @param node
     * @param name
     * @return version node
     * @throws UnsupportedOperationException if repository implementation does not support Versions API
     * @throws javax.jcr.RepositoryException if any repository error occurs
     * */
    public synchronized Version getVersion(Content node, String name)
            throws UnsupportedRepositoryOperationException, RepositoryException {
        VersionHistory history = this.getVersionHistory(node);
        if (history != null) {
            return history.getVersion(name);
        }
        log.error("Node " + node.getHandle() + " was never versioned");
        return null;
    }

    /**
     * Returns the current base version of given node
     * @throws UnsupportedRepositoryOperationException
     * @throws RepositoryException
     * */
    public Version getBaseVersion(Content node) throws UnsupportedOperationException, RepositoryException {
        Content versionedNode = this.getVersionedNode(node);
        if (versionedNode != null) {
            return versionedNode.getJCRNode().getBaseVersion();
        } else {
            throw new RepositoryException("Node " + node.getHandle() + " was never versioned");
        }
    }

    /**
     * get all versions
     *
     * @param node
     * @return Version iterator retreived from version history
     * @throws UnsupportedOperationException if repository implementation does not support Versions API
     * @throws javax.jcr.RepositoryException if any repository error occurs
     */
    public synchronized VersionIterator getAllVersions(Content node)
            throws UnsupportedRepositoryOperationException, RepositoryException {
        Content versionedNode = this.getVersionedNode(node);
        if (versionedNode == null) {
            // node does not exist in version store so no versions
            return null;
        }
        return versionedNode.getJCRNode().getVersionHistory().getAllVersions();
    }

    /**
     * restore specified version
     *
     * @param node to be restored
     * @param version to be used
     * @param removeExisting
     * @throws javax.jcr.version.VersionException if the specified <code>versionName</code> does not exist in this
     * node's version history
     * @throws javax.jcr.RepositoryException if an error occurs
     * @throws javax.jcr.version.VersionException
     */
    public synchronized void restore(Content node, Version version, boolean removeExisting)
            throws VersionException, UnsupportedRepositoryOperationException, RepositoryException {
        // get the cloned node from version store
        Content versionedNode = this.getVersionedNode(node);
        versionedNode.getJCRNode().restore(version, removeExisting);
        versionedNode.getJCRNode().checkout();
        List permissions = this.getAccessManagerPermissions();
        this.impersonateAccessManager(null);
        try {
            // if restored, update original node with the restored node and its subtree
            Rule rule = this.getUsedFilter(versionedNode);
            try {
                synchronized (ExclusiveWrite.getInstance()) {
                    CopyUtil.getInstance().copyFromVersion(versionedNode, node, new RuleBasedContentFilter(rule));
                    node.save();
                }
            } catch (RepositoryException re) {
                log.error("failed to restore versioned node, reverting all changes make to this node");
                node.refresh(false);
                throw re;
            }
        } catch (IOException e) {
            throw new RepositoryException(e);
        } catch (ClassNotFoundException e) {
            throw new RepositoryException(e);
        } catch (RepositoryException e) {
            throw e;
        } finally {
            this.revertAccessManager(permissions);
        }
    }

    /**
     * Removes all versions of the node associated with given UUID
     * @param uuid
     * @throws RepositoryException if fails to remove versioned node from the version store
     * */
    public synchronized void removeVersionHistory(String uuid) throws RepositoryException {
        List permissions = this.getAccessManagerPermissions();
        this.impersonateAccessManager(null);
        try {
            Content node = this.getVersionedNode(uuid);
            if (node != null) {
                VersionHistory history = node.getVersionHistory();
                VersionIterator versions = node.getAllVersions();
                if (versions != null) {
                    // skip root version
                    versions.nextVersion();
                    while (versions.hasNext()) {
                        history.removeVersion(versions.nextVersion().getName());
                    }
                }
                if (node.getJCRNode().getReferences().getSize() < 1) {
                    // remove node from the version store only if its not referenced 
                    node.delete();
                }
            }
        } catch (RepositoryException re) {
            throw re;
        } finally {
            this.revertAccessManager(permissions);
        }
        getHierarchyManager().save();
    }

    /**
     * get Rule used for this version
     * @param versionedNode
     * @throws IOException
     * @throws ClassNotFoundException
     * @throws RepositoryException
     * */
    protected Rule getUsedFilter(Content versionedNode)
            throws IOException, ClassNotFoundException, RepositoryException {
        // if restored, update original node with the restored node and its subtree
        ByteArrayInputStream inStream = null;
        try {
            String ruleString = this.getSystemNode(versionedNode).getNodeData(PROPERTY_RULE).getString();
            inStream = new ByteArrayInputStream(ruleString.getBytes());
            ObjectInput objectInput = new ObjectInputStream(inStream);
            return (Rule) objectInput.readObject();
        } catch (IOException e) {
            throw e;
        } catch (ClassNotFoundException e) {
            throw e;
        } finally {
            IOUtils.closeQuietly(inStream);
        }
    }

    /**
     * get magnolia system node created under the given node
     * @param node
     * @throws RepositoryException if failed to create system node
     * */
    protected synchronized Content getSystemNode(Content node) throws RepositoryException {
        try {
            return node.getContent(SYSTEM_NODE);
        } catch (PathNotFoundException e) {
            return node.createContent(SYSTEM_NODE, ItemType.SYSTEM);
        }
    }

    /**
     * impersonate to be access manager with system rights
     * @param permissions
     * */
    private void impersonateAccessManager(List permissions) {
        this.getHierarchyManager().getAccessManager().setPermissionList(permissions);
    }

    /**
     * revert access manager permissions
     * @param permissions
     * */
    private void revertAccessManager(List permissions) {
        this.getHierarchyManager().getAccessManager().setPermissionList(permissions);
    }

    /**
     * get access manager permission list
     * */
    private List getAccessManagerPermissions() {
        return this.getHierarchyManager().getAccessManager().getPermissionList();
    }

    /**
     * get version store hierarchy manager
     * */
    private HierarchyManager getHierarchyManager() {
        return MgnlContext.getHierarchyManager(VersionManager.VERSION_WORKSPACE);
    }

}