org.pentaho.platform.repository2.unified.jcr.DefaultLockHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.pentaho.platform.repository2.unified.jcr.DefaultLockHelper.java

Source

/*
 * This program is free software; you can redistribute it and/or modify it under the
 * terms of the GNU General Public License, version 2 as published by the Free Software
 * Foundation.
 *
 * You should have received a copy of the GNU General Public License along with this
 * program; if not, you can obtain a copy at http://www.gnu.org/licenses/gpl-2.0.html
 * or from the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * 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.
 *
 *
 * Copyright 2006 - 2013 Pentaho Corporation.  All rights reserved.
 */

package org.pentaho.platform.repository2.unified.jcr;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.platform.api.mt.ITenantedPrincipleNameResolver;
import org.pentaho.platform.engine.core.system.PentahoSessionHolder;
import org.pentaho.platform.repository.RepositoryFilenameUtils;
import org.pentaho.platform.repository2.unified.ServerRepositoryPaths;
import org.springframework.util.Assert;

import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.lock.Lock;
import javax.jcr.lock.LockManager;
import javax.jcr.security.AccessControlManager;
import javax.jcr.security.Privilege;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

/**
 * Default implementation of {@link ILockHelper}. If user {@code suzy} in tenant {@code acme} locks a file with
 * UUID {@code abc} then this implementation will store the lock token {@code xyz} as
 * {@code /pentaho/acme/home/suzy/.lockTokens/abc/xyz}. It is assumed that {@code /pentaho/acme/home/suzy} is never
 * versioned! Putting lock token storage beneath the user's home folder provides access control.
 * 
 * <p>
 * This implementation stores a lock owner, lock date, and lock message in the ownerInfo payload. See JCR 2.0
 * section 17.3. If implemented as custom properties, then a versioned node would require a checkout and checkin to
 * lock a file. There is one caveat: implementations of JCR are free to ignore the ownerInfo payload. In that case,
 * the implementation sets the value. If that happens, we simply return that value as the lock owner and date and
 * message are null.
 * </p>
 * 
 * @author mlowery
 */
public class DefaultLockHelper implements ILockHelper {

    // ~ Static fields/initializers
    // ======================================================================================

    private static final String FOLDER_NAME_LOCK_TOKENS = ".lockTokens"; //$NON-NLS-1$

    private static final char LOCK_OWNER_INFO_SEPARATOR = ':';

    private static final String LOCK_OWNER_INFO_SEPARATOR_REGEX = "\\" + LOCK_OWNER_INFO_SEPARATOR; //$NON-NLS-1$

    private static final List<Character> RESERVED_CHARS = Arrays
            .asList(new Character[] { LOCK_OWNER_INFO_SEPARATOR });

    private static final Log logger = LogFactory.getLog(DefaultLockHelper.class);

    private static final int POSITION_LOCK_OWNER = 0;

    private static final int POSITION_LOCK_DATE = 1;

    private static final int POSITION_LOCK_MESSAGE = 2;

    ITenantedPrincipleNameResolver userNameUtils;

    // ~ Instance fields
    // =================================================================================================

    // ~ Constructors
    // ====================================================================================================

    public DefaultLockHelper(ITenantedPrincipleNameResolver userNameUtils) {
        super();
        this.userNameUtils = userNameUtils;
    }

    // ~ Methods
    // =========================================================================================================

    /**
     * Stores a lock token associated with the session's user.
     */
    protected void addLockToken(final Session session, final PentahoJcrConstants pentahoJcrConstants,
            final Lock lock) throws RepositoryException {
        Node lockTokensNode = getOrCreateLockTokensNode(session, pentahoJcrConstants, lock);
        Node newLockTokenNode = lockTokensNode.addNode(lock.getNode().getIdentifier(),
                pentahoJcrConstants.getPHO_NT_LOCKTOKENSTORAGE());
        newLockTokenNode.setProperty(pentahoJcrConstants.getPHO_LOCKEDNODEREF(), lock.getNode());
        newLockTokenNode.setProperty(pentahoJcrConstants.getPHO_LOCKTOKEN(), lock.getLockToken());
        session.save();
    }

    /**
     * Returns all lock tokens belonging to the session's user. Lock tokens can then be added to the session by
     * calling {@code Session.addLockToken(token)}.
     * 
     * <p>
     * Callers should call {#link {@link #canUnlock(Session, PentahoJcrConstants, Lock)} if the token is being
     * retrieved for the purpose of an unlock.
     * </p>
     */
    protected String getLockToken(final Session session, final PentahoJcrConstants pentahoJcrConstants,
            final Lock lock) throws RepositoryException {
        Node lockTokensNode = getOrCreateLockTokensNode(session, pentahoJcrConstants, lock);
        NodeIterator nodes = lockTokensNode.getNodes(lock.getNode().getIdentifier());
        Assert.isTrue(nodes.hasNext());
        return nodes.nextNode().getProperty(pentahoJcrConstants.getPHO_LOCKTOKEN()).getString();
    }

    /**
     * Removes a lock token so that it can never be associated with anyone's session again. (To be called after the
     * file has been unlocked and therefore the token associated with the lock is unnecessary.)
     */
    public void removeLockToken(final Session session, final PentahoJcrConstants pentahoJcrConstants,
            final Lock lock) throws RepositoryException {
        Node lockTokensNode = getOrCreateLockTokensNode(session, pentahoJcrConstants, lock);
        NodeIterator nodes = lockTokensNode.getNodes(lock.getNode().getIdentifier());
        if (nodes.hasNext()) {
            nodes.nextNode().remove();
        }
        session.save();
    }

    protected Node getOrCreateLockTokensNode(final Session session, final PentahoJcrConstants pentahoJcrConstants,
            final Lock lock) throws RepositoryException {
        String absPath = ServerRepositoryPaths.getUserHomeFolderPath(
                userNameUtils.getTenant(getLockOwner(session, pentahoJcrConstants, lock)),
                userNameUtils.getPrincipleName(getLockOwner(session, pentahoJcrConstants, lock)));
        Node userHomeFolderNode = (Node) session.getItem(absPath);
        if (userHomeFolderNode.hasNode(FOLDER_NAME_LOCK_TOKENS)) {
            return userHomeFolderNode.getNode(FOLDER_NAME_LOCK_TOKENS);
        } else {
            Node lockTokensNode = userHomeFolderNode.addNode(FOLDER_NAME_LOCK_TOKENS,
                    pentahoJcrConstants.getPHO_NT_INTERNALFOLDER());
            session.save();
            return lockTokensNode;
        }
    }

    /**
     * {@inheritDoc}
     */
    public boolean canUnlock(final Session session, final PentahoJcrConstants pentahoJcrConstants, final Lock lock)
            throws RepositoryException {
        String absPath = ServerRepositoryPaths.getUserHomeFolderPath(
                userNameUtils.getTenant(getLockOwner(session, pentahoJcrConstants, lock)),
                userNameUtils.getPrincipleName(getLockOwner(session, pentahoJcrConstants, lock)));
        AccessControlManager acMgr = session.getAccessControlManager();
        return acMgr.hasPrivileges(absPath,
                new Privilege[] { acMgr.privilegeFromName("jcr:read"), acMgr.privilegeFromName("jcr:write"), //$NON-NLS-1$ //$NON-NLS-2$
                        acMgr.privilegeFromName("jcr:lockManagement") }); //$NON-NLS-1$
    }

    /**
     * {@inheritDoc}
     */
    public void addLockTokenToSessionIfNecessary(final Session session,
            final PentahoJcrConstants pentahoJcrConstants, final Serializable fileId) throws RepositoryException {
        Node fileNode = session.getNodeByIdentifier(fileId.toString());
        if (fileNode.isLocked()) {
            LockManager lockManager = session.getWorkspace().getLockManager();
            Lock lock = lockManager.getLock(fileNode.getPath());
            String lockToken = getLockToken(session, pentahoJcrConstants, lock);
            lockManager.addLockToken(lockToken);
        }
    }

    /**
     * {@inheritDoc}
     */
    public void removeLockTokenFromSessionIfNecessary(final Session session,
            final PentahoJcrConstants pentahoJcrConstants, final Serializable fileId) throws RepositoryException {
        Node fileNode = session.getNodeByIdentifier(fileId.toString());
        if (fileNode.isLocked()) {
            LockManager lockManager = session.getWorkspace().getLockManager();
            Lock lock = lockManager.getLock(fileNode.getPath());
            String lockToken = getLockToken(session, pentahoJcrConstants, lock);
            lockManager.removeLockToken(lockToken);
        }
    }

    /**
     * {@inheritDoc}
     */
    public void unlockFile(final Session session, final PentahoJcrConstants pentahoJcrConstants,
            final Serializable fileId) throws RepositoryException {
        Node fileNode = session.getNodeByIdentifier(fileId.toString());
        LockManager lockManager = session.getWorkspace().getLockManager();
        Lock lock = lockManager.getLock(fileNode.getPath());
        String lockToken = getLockToken(session, pentahoJcrConstants, lock);
        lockManager.addLockToken(lockToken);
        // get the lock again so that it has a non-null lockToken
        lock = lockManager.getLock(fileNode.getPath());
        // don't need lock token anymore
        removeLockToken(session, pentahoJcrConstants, lock);
        lockManager.unlock(fileNode.getPath());
    }

    /**
     * {@inheritDoc}
     */
    public void lockFile(final Session session, final PentahoJcrConstants pentahoJcrConstants,
            final Serializable fileId, final String message) throws RepositoryException {
        LockManager lockManager = session.getWorkspace().getLockManager();
        // locks are always deep in this impl
        final boolean isDeep = true;
        // locks are always open-scoped since a session is short-lived and all work occurs in a transaction
        // anyway; from spec, "if a lock is enabled and then disabled within the same transaction, its effect never
        // makes it to the persistent workspace and therefore it does nothing"
        final boolean isSessionScoped = false;
        final long timeoutHint = Long.MAX_VALUE;
        final String ownerInfo = makeOwnerInfo(
                JcrTenantUtils.getTenantedUser(PentahoSessionHolder.getSession().getName()),
                Calendar.getInstance().getTime(), message);
        Node fileNode = session.getNodeByIdentifier(fileId.toString());
        Assert.isTrue(fileNode.isNodeType(pentahoJcrConstants.getMIX_LOCKABLE()));
        Lock lock = lockManager.lock(fileNode.getPath(), isDeep, isSessionScoped, timeoutHint, ownerInfo);
        addLockToken(session, pentahoJcrConstants, lock);
    }

    private String makeOwnerInfo(final String lockOwner, final Date lockDate, final String lockMessage) {
        return escape(lockOwner) + LOCK_OWNER_INFO_SEPARATOR + lockDate.getTime() + LOCK_OWNER_INFO_SEPARATOR
                + escape(lockMessage);
    }

    @Override
    public Date getLockDate(final Session session, final PentahoJcrConstants pentahoJcrConstants, final Lock lock)
            throws RepositoryException {
        String[] tokens = tokenize(lock.getLockOwner());
        if (tokens != null) {
            long date;
            try {
                date = Long.parseLong(tokens[POSITION_LOCK_DATE]);
                return new Date(date);
            } catch (NumberFormatException e) {
                logger.debug("could not parse lock date; returning null", e); //$NON-NLS-1$
            }
        }
        return null;
    }

    @Override
    public String getLockMessage(final Session session, final PentahoJcrConstants pentahoJcrConstants,
            final Lock lock) throws RepositoryException {
        String[] tokens = tokenize(lock.getLockOwner());
        if (tokens != null) {
            return unescape(tokens[POSITION_LOCK_MESSAGE]);
        }
        return null;
    }

    @Override
    public String getLockOwner(final Session session, final PentahoJcrConstants pentahoJcrConstants,
            final Lock lock) throws RepositoryException {
        String[] tokens = tokenize(lock.getLockOwner());
        if (tokens != null) {
            return unescape(tokens[POSITION_LOCK_OWNER]);
        }
        // return whatever the implementation stored in this property
        return lock.getLockOwner();
    }

    private String[] tokenize(final String ownerInfo) {
        if (ownerInfo != null) {
            String[] tokens = ownerInfo.split(LOCK_OWNER_INFO_SEPARATOR_REGEX);
            if (tokens.length == 3) {
                return tokens;
            }
        }
        return null;
    }

    private static String escape(final String in) {
        if (in == null || in.trim().equals("")) { //$NON-NLS-1$
            return ""; //$NON-NLS-1$
        }
        return RepositoryFilenameUtils.escape(in, RESERVED_CHARS);
    }

    private static String unescape(final String in) {
        if (in == null || in.trim().equals("")) { //$NON-NLS-1$
            return ""; //$NON-NLS-1$
        }
        return RepositoryFilenameUtils.unescape(in);
    }

    // public static void main(final String[] args) {
    // System.out.println("'" + escape(null) + "'");
    // System.out.println("'" + escape("") + "'");
    // System.out.println("'" + escape("hello") + "'");
    // System.out.println("'" + escape("hell:o") + "'");
    // System.out.println("'" + escape("hello:") + "'");
    // System.out.println("'" + escape(":hello") + "'");
    // System.out.println("'" + escape("hell::o") + "'");
    // System.out.println("'" + escape("hell\\::o") + "'");
    //
    // System.out.println("'" + unescape(null) + "'");
    // System.out.println("'" + unescape("") + "'");
    // System.out.println("'" + unescape("hello") + "'");
    // System.out.println("'" + unescape("hell\\:o") + "'");
    // System.out.println("'" + unescape("hello\\:") + "'");
    // System.out.println("'" + unescape("\\:hello") + "'");
    // System.out.println("'" + unescape("hell\\:\\:o") + "'");
    // System.out.println("'" + unescape("hell\\\\:\\:o") + "'");
    //
    // System.out.println(Arrays.toString("su%3Azy:1332272120111:lock within versioned folder"
    // .split(LOCK_OWNER_INFO_SEPARATOR_REGEX)));
    // }
}