/* * (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.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()); } return lockDetails; } @Override @BypassInterceptors public void resetLockState() { lockDetails = null; documentId = null; } }