org.nuxeo.ecm.webapp.edit.lock.LockActionsBean.java Source code

Java tutorial

Introduction

Here is the source code for org.nuxeo.ecm.webapp.edit.lock.LockActionsBean.java

Source

/*
 * (C) Copyright 2007 Nuxeo SA (http://nuxeo.com/) and others.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Contributors:
 *     Nuxeo - initial API and implementation
 *
 * $Id$
 */

package org.nuxeo.ecm.webapp.edit.lock;

import static org.jboss.seam.annotations.Install.FRAMEWORK;
import static org.nuxeo.ecm.core.api.security.SecurityConstants.EVERYTHING;
import static org.nuxeo.ecm.core.api.security.SecurityConstants.WRITE_PROPERTIES;

import java.io.Serializable;
import java.text.DateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Factory;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Install;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Observer;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.intercept.BypassInterceptors;
import org.jboss.seam.contexts.Context;
import org.jboss.seam.contexts.Contexts;
import org.jboss.seam.core.Events;
import org.jboss.seam.faces.FacesMessages;
import org.jboss.seam.international.StatusMessage;
import org.nuxeo.ecm.core.api.CoreSession;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.api.DocumentRef;
import org.nuxeo.ecm.core.api.Lock;
import org.nuxeo.ecm.core.api.NuxeoPrincipal;
import org.nuxeo.ecm.core.api.UnrestrictedSessionRunner;
import org.nuxeo.ecm.platform.actions.Action;
import org.nuxeo.ecm.platform.ui.web.api.NavigationContext;
import org.nuxeo.ecm.platform.ui.web.api.WebActions;
import org.nuxeo.ecm.webapp.helpers.EventNames;
import org.nuxeo.ecm.webapp.helpers.ResourcesAccessor;

/**
 * This is the action listener that knows to decide if an user has the right to take the lock or release the lock of a
 * document.
 * <p>
 * Most of the logic of this bean should either be moved into a DocumentModel adapter or directly into the core API.
 *
 * @author <a href="mailto:bt@nuxeo.com">Bogdan Tatar</a>
 */
@Name("lockActions")
@Scope(ScopeType.EVENT)
@Install(precedence = FRAMEWORK)
public class LockActionsBean implements LockActions {
    // XXX: OG: How a remote calls could possibly work without the seam
    // injected
    // components??

    private static final long serialVersionUID = -8050964269646803077L;

    private static final Log log = LogFactory.getLog(LockActionsBean.class);

    private static final String EDIT_ACTIONS = "EDIT_ACTIONS";

    @In
    private transient NavigationContext navigationContext;

    @In(create = true, required = false)
    protected transient FacesMessages facesMessages;

    @In(create = true)
    protected transient ResourcesAccessor resourcesAccessor;

    @In(create = true)
    protected transient WebActions webActions;

    @In(create = true, required = false)
    protected transient CoreSession documentManager;

    // cache lock details states to reduce costly core session remote calls
    private Map<String, Serializable> lockDetails;

    private String documentId;

    @Override
    public Boolean getCanLockDoc(DocumentModel document) {
        boolean canLock;
        if (document == null) {
            log.warn("Can't evaluate lock action : currentDocument is null");
            canLock = false;
        } else if (document.isProxy()) {
            canLock = false;
        } else {
            NuxeoPrincipal userName = (NuxeoPrincipal) documentManager.getPrincipal();
            Lock lock = documentManager.getLockInfo(document.getRef());
            canLock = lock == null
                    && (userName.isAdministrator() || isManagerOnDocument(document.getRef())
                            || documentManager.hasPermission(document.getRef(), WRITE_PROPERTIES))
                    && !document.isVersion();
        }
        return canLock;
    }

    protected boolean isManagerOnDocument(DocumentRef ref) {
        return documentManager.hasPermission(ref, EVERYTHING);
    }

    @Override
    @Factory(value = "currentDocumentCanBeLocked", scope = ScopeType.EVENT)
    public Boolean getCanLockCurrentDoc() {
        DocumentModel currentDocument = navigationContext.getCurrentDocument();
        return getCanLockDoc(currentDocument);
    }

    @Observer(value = { EventNames.USER_ALL_DOCUMENT_TYPES_SELECTION_CHANGED }, create = false)
    @BypassInterceptors
    public void resetEventContext() {
        Context evtCtx = Contexts.getEventContext();
        if (evtCtx != null) {
            evtCtx.remove("currentDocumentCanBeLocked");
            evtCtx.remove("currentDocumentLockDetails");
            evtCtx.remove("currentDocumentCanBeUnlocked");
        }
    }

    @Override
    public Boolean getCanUnlockDoc(DocumentModel document) {
        boolean canUnlock = false;
        if (document == null) {
            canUnlock = false;
        } else {
            NuxeoPrincipal userName = (NuxeoPrincipal) documentManager.getPrincipal();
            Map<String, Serializable> lockDetails = getLockDetails(document);
            if (lockDetails.isEmpty() || document.isProxy()) {
                canUnlock = false;
            } else {
                canUnlock = ((userName.isAdministrator()
                        || documentManager.hasPermission(document.getRef(), EVERYTHING))
                                ? true
                                : (userName.getName().equals(lockDetails.get(LOCKER))
                                        && documentManager.hasPermission(document.getRef(), WRITE_PROPERTIES)))
                        && !document.isVersion();
            }
        }
        return canUnlock;
    }

    @Override
    @Factory(value = "currentDocumentCanBeUnlocked", scope = ScopeType.EVENT)
    public Boolean getCanUnlockCurrentDoc() {
        DocumentModel currentDocument = navigationContext.getCurrentDocument();
        return getCanUnlockDoc(currentDocument);
    }

    @Override
    public String lockCurrentDocument() {
        String view = lockDocument(navigationContext.getCurrentDocument());
        navigationContext.invalidateCurrentDocument();
        return view;
    }

    @Override
    public String lockDocument(DocumentModel document) {
        log.debug("Lock a document ...");
        resetEventContext();
        String message = "document.lock.failed";
        DocumentRef ref = document.getRef();
        if (documentManager.hasPermission(ref, WRITE_PROPERTIES) && documentManager.getLockInfo(ref) == null) {
            documentManager.setLock(ref);
            documentManager.save();
            message = "document.lock";
            Events.instance().raiseEvent(EventNames.DOCUMENT_LOCKED, document);
            Events.instance().raiseEvent(EventNames.DOCUMENT_CHANGED, document);
        }
        facesMessages.add(StatusMessage.Severity.INFO, resourcesAccessor.getMessages().get(message));
        resetLockState();
        webActions.resetTabList();
        return null;
    }

    @Override
    public String unlockCurrentDocument() {
        String view = unlockDocument(navigationContext.getCurrentDocument());
        navigationContext.invalidateCurrentDocument();
        return view;
    }

    // helper inner class to do the unrestricted unlock
    protected class UnrestrictedUnlocker extends UnrestrictedSessionRunner {

        private final DocumentRef docRefToUnlock;

        protected UnrestrictedUnlocker(DocumentRef docRef) {
            super(documentManager);
            docRefToUnlock = docRef;
        }

        /*
         * Use an unrestricted session to unlock the document.
         */
        @Override
        public void run() {
            session.removeLock(docRefToUnlock);
            session.save();
        }
    }

    @Override
    public String unlockDocument(DocumentModel document) {
        log.debug("Unlock a document ...");
        resetEventContext();
        String message;
        Map<String, Serializable> lockDetails = getLockDetails(document);
        if (lockDetails == null) {
            message = "document.unlock.done";
        } else {
            NuxeoPrincipal userName = (NuxeoPrincipal) documentManager.getPrincipal();
            if (userName.isAdministrator() || documentManager.hasPermission(document.getRef(), EVERYTHING)
                    || userName.getName().equals(lockDetails.get(LOCKER))) {

                if (!documentManager.hasPermission(document.getRef(), WRITE_PROPERTIES)) {
                    // Here administrator should always be able to unlock so
                    // we need to grant him this possibility even if it
                    // doesn't have the write permission.

                    new UnrestrictedUnlocker(document.getRef()).runUnrestricted();

                    documentManager.save(); // process invalidations from unrestricted session

                    message = "document.unlock";
                } else {
                    documentManager.removeLock(document.getRef());
                    documentManager.save();
                    message = "document.unlock";
                }
                Events.instance().raiseEvent(EventNames.DOCUMENT_UNLOCKED, document);
                Events.instance().raiseEvent(EventNames.DOCUMENT_CHANGED, document);
            } else {
                message = "document.unlock.not.permitted";
            }
        }
        facesMessages.add(StatusMessage.Severity.INFO, resourcesAccessor.getMessages().get(message));
        resetLockState();
        webActions.resetTabList();
        return null;
    }

    @Override
    public Action getLockOrUnlockAction() {
        log.debug("Get lock or unlock action ...");
        Action lockOrUnlockAction = null;
        List<Action> actionsList = webActions.getActionsList(EDIT_ACTIONS);
        if (actionsList != null && !actionsList.isEmpty()) {
            lockOrUnlockAction = actionsList.get(0);
        }
        return lockOrUnlockAction;
    }

    @Override
    @Factory(value = "currentDocumentLockDetails", scope = ScopeType.EVENT)
    public Map<String, Serializable> getCurrentDocLockDetails() {
        Map<String, Serializable> details = null;
        if (navigationContext.getCurrentDocument() != null) {
            details = getLockDetails(navigationContext.getCurrentDocument());
        }
        return details;
    }

    @Override
    public Map<String, Serializable> getLockDetails(DocumentModel document) {
        if (lockDetails == null || !StringUtils.equals(documentId, document.getId())) {
            lockDetails = new HashMap<String, Serializable>();
            documentId = document.getId();
            Lock lock = documentManager.getLockInfo(document.getRef());
            if (lock == null) {
                return lockDetails;
            }
            lockDetails.put(LOCKER, lock.getOwner());
            lockDetails.put(LOCK_CREATED, lock.getCreated());
            lockDetails.put(LOCK_TIME, DateFormat.getDateInstance(DateFormat.MEDIUM)
                    .format(new Date(lock.getCreated().getTimeInMillis())));
        }
        return lockDetails;
    }

    @Override
    @BypassInterceptors
    public void resetLockState() {
        lockDetails = null;
        documentId = null;
    }

}