/* * (C) Copyright 2009 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: * Thomas Roger */ package org.nuxeo.ecm.platform.publisher.task; import java.io.Serializable; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.nuxeo.ecm.core.api.CoreSession; import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.api.DocumentModelList; import org.nuxeo.ecm.core.api.DocumentRef; import org.nuxeo.ecm.core.api.IdRef; import org.nuxeo.ecm.core.api.NuxeoPrincipal; import org.nuxeo.ecm.core.api.PathRef; import org.nuxeo.ecm.core.api.UnrestrictedSessionRunner; import org.nuxeo.ecm.core.api.security.ACE; import org.nuxeo.ecm.core.api.security.ACL; import org.nuxeo.ecm.core.api.security.ACP; import org.nuxeo.ecm.core.api.security.SecurityConstants; import org.nuxeo.ecm.core.event.impl.DocumentEventContext; import org.nuxeo.ecm.platform.ec.notification.NotificationConstants; import org.nuxeo.ecm.platform.publisher.api.PublicationNode; import org.nuxeo.ecm.platform.publisher.api.PublishedDocument; import org.nuxeo.ecm.platform.publisher.api.PublishedDocumentFactory; import org.nuxeo.ecm.platform.publisher.api.PublishingEvent; import org.nuxeo.ecm.platform.publisher.impl.core.CoreFolderPublicationNode; import org.nuxeo.ecm.platform.publisher.impl.core.CoreProxyFactory; import org.nuxeo.ecm.platform.publisher.impl.core.SimpleCorePublishedDocument; import org.nuxeo.ecm.platform.publisher.rules.ValidatorsRule; import org.nuxeo.ecm.platform.task.Task; import org.nuxeo.ecm.platform.task.TaskEventNames; import org.nuxeo.ecm.platform.task.TaskService; import org.nuxeo.runtime.api.Framework; /** * Implementation of the {@link PublishedDocumentFactory} for core implementation using native proxy system with * validation workflow. * * @author <a href="mailto:troger@nuxeo.com">Thomas Roger</a> * @author <a href="mailto:tmartins@nuxeo.com">Thierry Martins</a> * @author <a href="mailto:ataillefer@nuxeo.com">Antoine Taillefer</a> */ public class CoreProxyWithWorkflowFactory extends CoreProxyFactory implements PublishedDocumentFactory { public static final String TASK_NAME = "org.nuxeo.ecm.platform.publisher.task.CoreProxyWithWorkflowFactory"; public static final String ACL_NAME = "org.nuxeo.ecm.platform.publisher.task.CoreProxyWithWorkflowFactory"; // XXX ataillefer: remove if refactor old JBPM ACL name public static final String JBPM_ACL_NAME = "org.nuxeo.ecm.platform.publisher.jbpm.CoreProxyWithWorkflowFactory"; public static final String PUBLISH_TASK_TYPE = "publish_moderate"; public static final String LOOKUP_STATE_PARAM_KEY = "lookupState"; public static final String LOOKUP_STATE_PARAM_BYACL = "byACL"; public static final String LOOKUP_STATE_PARAM_BYTASK = "byTask"; protected LookupState lookupState = new LookupStateByACL(); @Override public void init(CoreSession coreSession, ValidatorsRule validatorsRule, Map<String, String> parameters) { super.init(coreSession, validatorsRule, parameters); // setup lookup state strategy if requested String lookupState = parameters.get(LOOKUP_STATE_PARAM_KEY); if (lookupState != null) { if (LOOKUP_STATE_PARAM_BYACL.equals(lookupState)) { setLookupByACL(); } else if (LOOKUP_STATE_PARAM_BYTASK.equals(lookupState)) { setLookupByTask(); } } } public void setLookupByTask() { this.lookupState = new LookupStateByTask(); } public void setLookupByACL() { this.lookupState = new LookupStateByACL(); } @Override public PublishedDocument publishDocument(DocumentModel doc, PublicationNode targetNode, Map<String, String> params) { DocumentModel targetDocModel; if (targetNode instanceof CoreFolderPublicationNode) { CoreFolderPublicationNode coreNode = (CoreFolderPublicationNode) targetNode; targetDocModel = coreNode.getTargetDocumentModel(); } else { targetDocModel = coreSession.getDocument(new PathRef(targetNode.getPath())); } NuxeoPrincipal principal = (NuxeoPrincipal) coreSession.getPrincipal(); DocumentPublisherUnrestricted runner = new DocumentPublisherUnrestricted(coreSession, doc.getRef(), targetDocModel.getRef(), principal, null); runner.runUnrestricted(); return runner.getPublishedDocument(); } protected boolean isPublishedDocWaitingForPublication(DocumentModel doc, CoreSession session) { return !lookupState.isPublished(doc, session); } protected boolean isValidator(DocumentModel document, NuxeoPrincipal principal) { String[] validators = getValidatorsFor(document); for (String s : validators) { if (principal.getName().equals(s) || principal.isMemberOf(s)) { return true; } } return false; } protected void restrictPermission(DocumentModel newProxy, NuxeoPrincipal principal, CoreSession coreSession, ACL acl) { ChangePermissionUnrestricted permissionChanger = new ChangePermissionUnrestricted(coreSession, newProxy, getValidatorsFor(newProxy), principal, ACL_NAME, acl); permissionChanger.runUnrestricted(); } protected void createTask(DocumentModel document, CoreSession session, NuxeoPrincipal principal) { String[] actorIds = getValidatorsFor(document); Map<String, String> variables = new HashMap<String, String>(); variables.put(Task.TaskVariableName.needi18n.name(), "true"); variables.put(Task.TaskVariableName.taskType.name(), PUBLISH_TASK_TYPE); variables.put(TaskService.VariableName.documentId.name(), document.getId()); variables.put(TaskService.VariableName.documentRepositoryName.name(), document.getRepositoryName()); variables.put(TaskService.VariableName.initiator.name(), principal.getName()); getTaskService().createTask(session, principal, document, TASK_NAME, Arrays.asList(actorIds), false, TASK_NAME, null, null, variables, null); DocumentEventContext ctx = new DocumentEventContext(session, principal, document); ctx.setProperty(NotificationConstants.RECIPIENTS_KEY, actorIds); getEventProducer().fireEvent(ctx.newEvent(TaskEventNames.WORKFLOW_TASK_START)); } protected TaskService getTaskService() { return Framework.getLocalService(TaskService.class); } protected void removeExistingProxiesOnPreviousVersions(DocumentModel newProxy) { new UnrestrictedSessionRunner(coreSession) { @Override public void run() { DocumentModel sourceVersion = session.getSourceDocument(newProxy.getRef()); DocumentModel dm = session.getSourceDocument(sourceVersion.getRef()); DocumentModelList brothers = session.getProxies(dm.getRef(), newProxy.getParentRef()); if (brothers != null && brothers.size() > 1) { // we remove the brothers of the published document if any // the use case is: // v1 is published, v2 is waiting for publication and was just // validated // v1 is removed and v2 is now being published for (DocumentModel doc : brothers) { if (!doc.getId().equals(newProxy.getId())) { session.removeDocument(doc.getRef()); } } } } }.runUnrestricted(); } @Override public void validatorPublishDocument(PublishedDocument publishedDocument, String comment) { DocumentModel proxy = ((SimpleCorePublishedDocument) publishedDocument).getProxy(); NuxeoPrincipal principal = (NuxeoPrincipal) coreSession.getPrincipal(); removeExistingProxiesOnPreviousVersions(proxy); removeACL(proxy, coreSession); endTask(proxy, principal, coreSession, comment, PublishingEvent.documentPublicationApproved); notifyEvent(PublishingEvent.documentPublicationApproved, proxy, coreSession); notifyEvent(PublishingEvent.documentPublished, proxy, coreSession); ((SimpleCorePublishedDocument) publishedDocument).setPending(false); } protected void removeACL(DocumentModel document, CoreSession coreSession) { RemoveACLUnrestricted remover = new RemoveACLUnrestricted(coreSession, document, ACL_NAME, JBPM_ACL_NAME); remover.runUnrestricted(); } protected void endTask(DocumentModel document, NuxeoPrincipal currentUser, CoreSession session, String comment, PublishingEvent event) { List<Task> tis = getTaskService().getTaskInstances(document, currentUser, session); String initiator = null; for (Task task : tis) { if (task.getName().equals(TASK_NAME)) { initiator = (String) task.getVariable(TaskService.VariableName.initiator.name()); task.end(session); // make sure taskDoc is attached to prevent sending event with null session DocumentModel taskDocument = task.getDocument(); if (taskDocument.getSessionId() == null) { taskDocument.attach(coreSession.getSessionId()); } session.saveDocument(taskDocument); break; } } DocumentModel liveDoc = getLiveDocument(session, document); Map<String, Serializable> properties = new HashMap<String, Serializable>(); if (initiator != null) { properties.put(NotificationConstants.RECIPIENTS_KEY, new String[] { initiator }); } notifyEvent(event.name(), properties, comment, null, liveDoc, session); } protected DocumentModel getLiveDocument(CoreSession session, DocumentModel proxy) { GetsProxySourceDocumentsUnrestricted runner = new GetsProxySourceDocumentsUnrestricted(session, proxy); runner.runUnrestricted(); return runner.liveDocument; } @Override public void validatorRejectPublication(PublishedDocument publishedDocument, String comment) { DocumentModel proxy = ((SimpleCorePublishedDocument) publishedDocument).getProxy(); NuxeoPrincipal principal = (NuxeoPrincipal) coreSession.getPrincipal(); notifyEvent(PublishingEvent.documentPublicationRejected, proxy, coreSession); endTask(proxy, principal, coreSession, comment, PublishingEvent.documentPublicationRejected); removeProxy(proxy, coreSession); } protected void removeProxy(DocumentModel doc, CoreSession coreSession) { DeleteDocumentUnrestricted deleter = new DeleteDocumentUnrestricted(coreSession, doc); deleter.runUnrestricted(); } @Override public PublishedDocument wrapDocumentModel(DocumentModel doc) { final SimpleCorePublishedDocument publishedDocument = (SimpleCorePublishedDocument) super.wrapDocumentModel( doc); new UnrestrictedSessionRunner(coreSession) { @Override public void run() { if (!isPublished(publishedDocument, session)) { publishedDocument.setPending(true); } } }.runUnrestricted(); return publishedDocument; } protected boolean isPublished(PublishedDocument publishedDocument, CoreSession session) { // FIXME: should be cached DocumentModel proxy = ((SimpleCorePublishedDocument) publishedDocument).getProxy(); return lookupState.isPublished(proxy, session); } @Override public boolean canManagePublishing(PublishedDocument publishedDocument) { DocumentModel proxy = ((SimpleCorePublishedDocument) publishedDocument).getProxy(); NuxeoPrincipal currentUser = (NuxeoPrincipal) coreSession.getPrincipal(); return proxy.isProxy() && hasValidationTask(proxy, currentUser); } protected boolean hasValidationTask(DocumentModel proxy, NuxeoPrincipal currentUser) { assert currentUser != null; List<Task> tasks = getTaskService().getTaskInstances(proxy, currentUser, coreSession); for (Task task : tasks) { if (task.getName().equals(TASK_NAME)) { return true; } } return false; } @Override public boolean hasValidationTask(PublishedDocument publishedDocument) { DocumentModel proxy = ((SimpleCorePublishedDocument) publishedDocument).getProxy(); NuxeoPrincipal currentUser = (NuxeoPrincipal) coreSession.getPrincipal(); return hasValidationTask(proxy, currentUser); } private class GetsProxySourceDocumentsUnrestricted extends UnrestrictedSessionRunner { public DocumentModel liveDocument; private DocumentModel sourceDocument; private final DocumentModel document; public GetsProxySourceDocumentsUnrestricted(CoreSession session, DocumentModel proxy) { super(session); this.document = proxy; } @Override public void run() { sourceDocument = session.getDocument(new IdRef(document.getSourceId())); liveDocument = session.getDocument(new IdRef(sourceDocument.getSourceId())); } } /** * @author arussel */ protected class DocumentPublisherUnrestricted extends UnrestrictedSessionRunner { protected PublishedDocument result; protected DocumentRef docRef; protected DocumentRef targetRef; protected NuxeoPrincipal principal; protected String comment = ""; public DocumentPublisherUnrestricted(CoreSession session, DocumentRef docRef, DocumentRef targetRef, NuxeoPrincipal principal, String comment) { super(session); this.docRef = docRef; this.targetRef = targetRef; this.principal = principal; this.comment = comment; } public PublishedDocument getPublishedDocument() { return result; } @Override public void run() { DocumentModelList list = session.getProxies(docRef, targetRef); DocumentModel proxy = null; if (list.isEmpty()) {// first publication proxy = session.publishDocument(session.getDocument(docRef), session.getDocument(targetRef)); SimpleCorePublishedDocument publishedDocument = new SimpleCorePublishedDocument(proxy); session.save(); if (!isValidator(proxy, principal)) { notifyEvent(PublishingEvent.documentWaitingPublication, coreSession.getDocument(proxy.getRef()), coreSession); restrictPermission(proxy, principal, session, null); createTask(proxy, coreSession, principal); publishedDocument.setPending(true); } else { notifyEvent(PublishingEvent.documentPublished, proxy, coreSession); } result = publishedDocument; } else if (list.size() == 1) { // one doc is already published or waiting for publication if (isPublishedDocWaitingForPublication(list.get(0), session)) { proxy = session.publishDocument(session.getDocument(docRef), session.getDocument(targetRef)); if (!isValidator(proxy, principal)) { // we're getting the old proxy acl ACL acl = list.get(0).getACP().getACL(ACL_NAME); acl.add(0, new ACE(principal.getName(), SecurityConstants.READ, true)); ACP acp = proxy.getACP(); acp.addACL(acl); session.setACP(proxy.getRef(), acp, true); session.save(); SimpleCorePublishedDocument publishedDocument = new SimpleCorePublishedDocument(proxy); publishedDocument.setPending(true); result = publishedDocument; } else { endTask(proxy, principal, coreSession, "", PublishingEvent.documentPublicationApproved); notifyEvent(PublishingEvent.documentPublished, proxy, coreSession); ACP acp = proxy.getACP(); acp.removeACL(ACL_NAME); session.setACP(proxy.getRef(), acp, true); session.save(); result = new SimpleCorePublishedDocument(proxy); } } else { if (!isValidator(list.get(0), principal)) { proxy = session.publishDocument(session.getDocument(docRef), session.getDocument(targetRef), false); // save needed to have the proxy visible from other // sessions in non-JCA mode session.save(); SimpleCorePublishedDocument publishedDocument = new SimpleCorePublishedDocument(proxy); notifyEvent(PublishingEvent.documentWaitingPublication, proxy, coreSession); restrictPermission(proxy, principal, coreSession, null); session.save(); // process invalidations (non-JCA) createTask(proxy, coreSession, principal); publishedDocument.setPending(true); result = publishedDocument; } else { proxy = session.publishDocument(session.getDocument(docRef), session.getDocument(targetRef)); // save needed to have the proxy visible from other // sessions in non-JCA mode session.save(); notifyEvent(PublishingEvent.documentPublished, proxy, coreSession); SimpleCorePublishedDocument publishedDocument = new SimpleCorePublishedDocument(proxy); result = publishedDocument; } } } else if (list.size() == 2) { DocumentModel waitingForPublicationDoc = null; session.save(); // process invalidations (non-JCA) for (DocumentModel dm : list) { if (session.getACP(dm.getRef()).getACL(ACL_NAME) != null) { waitingForPublicationDoc = dm; } } if (!isValidator(waitingForPublicationDoc, principal)) { // we're getting the old proxy acl ACL acl = waitingForPublicationDoc.getACP().getACL(ACL_NAME); acl.add(0, new ACE(principal.getName(), SecurityConstants.READ, true)); // remove publishedDoc ACP acp = session.getACP(waitingForPublicationDoc.getRef()); acp.addACL(acl); session.setACP(waitingForPublicationDoc.getRef(), acp, true); SimpleCorePublishedDocument publishedDocument = new SimpleCorePublishedDocument( waitingForPublicationDoc); publishedDocument.setPending(true); result = publishedDocument; } else { endTask(waitingForPublicationDoc, principal, coreSession, comment, PublishingEvent.documentPublicationApproved); session.removeDocument(waitingForPublicationDoc.getRef()); proxy = session.publishDocument(session.getDocument(docRef), session.getDocument(targetRef)); notifyEvent(PublishingEvent.documentPublished, proxy, coreSession); SimpleCorePublishedDocument publishedDocument = new SimpleCorePublishedDocument(proxy); result = publishedDocument; } } if (proxy != null) { proxy.detach(true); } } } }