001/* 002 * (C) Copyright 2009 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Thomas Roger 016 */ 017 018package org.nuxeo.ecm.platform.publisher.task; 019 020import java.io.Serializable; 021import java.util.Arrays; 022import java.util.HashMap; 023import java.util.List; 024import java.util.Map; 025 026import org.nuxeo.ecm.core.api.CoreSession; 027import org.nuxeo.ecm.core.api.DocumentModel; 028import org.nuxeo.ecm.core.api.DocumentModelList; 029import org.nuxeo.ecm.core.api.DocumentRef; 030import org.nuxeo.ecm.core.api.IdRef; 031import org.nuxeo.ecm.core.api.NuxeoPrincipal; 032import org.nuxeo.ecm.core.api.PathRef; 033import org.nuxeo.ecm.core.api.UnrestrictedSessionRunner; 034import org.nuxeo.ecm.core.api.security.ACE; 035import org.nuxeo.ecm.core.api.security.ACL; 036import org.nuxeo.ecm.core.api.security.ACP; 037import org.nuxeo.ecm.core.api.security.SecurityConstants; 038import org.nuxeo.ecm.core.event.impl.DocumentEventContext; 039import org.nuxeo.ecm.platform.ec.notification.NotificationConstants; 040import org.nuxeo.ecm.platform.publisher.api.PublicationNode; 041import org.nuxeo.ecm.platform.publisher.api.PublishedDocument; 042import org.nuxeo.ecm.platform.publisher.api.PublishedDocumentFactory; 043import org.nuxeo.ecm.platform.publisher.api.PublishingEvent; 044import org.nuxeo.ecm.platform.publisher.impl.core.CoreFolderPublicationNode; 045import org.nuxeo.ecm.platform.publisher.impl.core.CoreProxyFactory; 046import org.nuxeo.ecm.platform.publisher.impl.core.SimpleCorePublishedDocument; 047import org.nuxeo.ecm.platform.publisher.rules.ValidatorsRule; 048import org.nuxeo.ecm.platform.task.Task; 049import org.nuxeo.ecm.platform.task.TaskEventNames; 050import org.nuxeo.ecm.platform.task.TaskService; 051import org.nuxeo.runtime.api.Framework; 052 053/** 054 * Implementation of the {@link PublishedDocumentFactory} for core implementation using native proxy system with 055 * validation workflow. 056 * 057 * @author <a href="mailto:troger@nuxeo.com">Thomas Roger</a> 058 * @author <a href="mailto:tmartins@nuxeo.com">Thierry Martins</a> 059 * @author <a href="mailto:ataillefer@nuxeo.com">Antoine Taillefer</a> 060 */ 061public class CoreProxyWithWorkflowFactory extends CoreProxyFactory implements PublishedDocumentFactory { 062 063 public static final String TASK_NAME = "org.nuxeo.ecm.platform.publisher.task.CoreProxyWithWorkflowFactory"; 064 065 public static final String ACL_NAME = "org.nuxeo.ecm.platform.publisher.task.CoreProxyWithWorkflowFactory"; 066 067 // XXX ataillefer: remove if refactor old JBPM ACL name 068 public static final String JBPM_ACL_NAME = "org.nuxeo.ecm.platform.publisher.jbpm.CoreProxyWithWorkflowFactory"; 069 070 public static final String PUBLISH_TASK_TYPE = "publish_moderate"; 071 072 public static final String LOOKUP_STATE_PARAM_KEY = "lookupState"; 073 074 public static final String LOOKUP_STATE_PARAM_BYACL = "byACL"; 075 076 public static final String LOOKUP_STATE_PARAM_BYTASK = "byTask"; 077 078 protected LookupState lookupState = new LookupStateByACL(); 079 080 @Override 081 public void init(CoreSession coreSession, ValidatorsRule validatorsRule, Map<String, String> parameters) { 082 super.init(coreSession, validatorsRule, parameters); 083 // setup lookup state strategy if requested 084 String lookupState = parameters.get(LOOKUP_STATE_PARAM_KEY); 085 if (lookupState != null) { 086 if (LOOKUP_STATE_PARAM_BYACL.equals(lookupState)) { 087 setLookupByACL(); 088 } else if (LOOKUP_STATE_PARAM_BYTASK.equals(lookupState)) { 089 setLookupByTask(); 090 } 091 } 092 } 093 094 public void setLookupByTask() { 095 this.lookupState = new LookupStateByTask(); 096 } 097 098 public void setLookupByACL() { 099 this.lookupState = new LookupStateByACL(); 100 } 101 102 @Override 103 public PublishedDocument publishDocument(DocumentModel doc, PublicationNode targetNode, 104 Map<String, String> params) { 105 DocumentModel targetDocModel; 106 if (targetNode instanceof CoreFolderPublicationNode) { 107 CoreFolderPublicationNode coreNode = (CoreFolderPublicationNode) targetNode; 108 targetDocModel = coreNode.getTargetDocumentModel(); 109 } else { 110 targetDocModel = coreSession.getDocument(new PathRef(targetNode.getPath())); 111 } 112 NuxeoPrincipal principal = (NuxeoPrincipal) coreSession.getPrincipal(); 113 DocumentPublisherUnrestricted runner = new DocumentPublisherUnrestricted(coreSession, doc.getRef(), 114 targetDocModel.getRef(), principal, null); 115 runner.runUnrestricted(); 116 return runner.getPublishedDocument(); 117 } 118 119 protected boolean isPublishedDocWaitingForPublication(DocumentModel doc, CoreSession session) { 120 return !lookupState.isPublished(doc, session); 121 } 122 123 protected boolean isValidator(DocumentModel document, NuxeoPrincipal principal) { 124 String[] validators = getValidatorsFor(document); 125 for (String s : validators) { 126 if (principal.getName().equals(s) || principal.isMemberOf(s)) { 127 return true; 128 } 129 } 130 return false; 131 } 132 133 protected void restrictPermission(DocumentModel newProxy, NuxeoPrincipal principal, CoreSession coreSession, 134 ACL acl) { 135 ChangePermissionUnrestricted permissionChanger = new ChangePermissionUnrestricted(coreSession, newProxy, 136 getValidatorsFor(newProxy), principal, ACL_NAME, acl); 137 permissionChanger.runUnrestricted(); 138 } 139 140 protected void createTask(DocumentModel document, CoreSession session, NuxeoPrincipal principal) { 141 String[] actorIds = getValidatorsFor(document); 142 Map<String, String> variables = new HashMap<String, String>(); 143 variables.put(Task.TaskVariableName.needi18n.name(), "true"); 144 variables.put(Task.TaskVariableName.taskType.name(), PUBLISH_TASK_TYPE); 145 variables.put(TaskService.VariableName.documentId.name(), document.getId()); 146 variables.put(TaskService.VariableName.documentRepositoryName.name(), document.getRepositoryName()); 147 variables.put(TaskService.VariableName.initiator.name(), principal.getName()); 148 149 getTaskService().createTask(session, principal, document, TASK_NAME, Arrays.asList(actorIds), false, TASK_NAME, 150 null, null, variables, null); 151 DocumentEventContext ctx = new DocumentEventContext(session, principal, document); 152 ctx.setProperty(NotificationConstants.RECIPIENTS_KEY, actorIds); 153 getEventProducer().fireEvent(ctx.newEvent(TaskEventNames.WORKFLOW_TASK_START)); 154 } 155 156 protected TaskService getTaskService() { 157 return Framework.getLocalService(TaskService.class); 158 } 159 160 protected void removeExistingProxiesOnPreviousVersions(DocumentModel newProxy) { 161 new UnrestrictedSessionRunner(coreSession) { 162 @Override 163 public void run() { 164 DocumentModel sourceVersion = session.getSourceDocument(newProxy.getRef()); 165 DocumentModel dm = session.getSourceDocument(sourceVersion.getRef()); 166 DocumentModelList brothers = session.getProxies(dm.getRef(), newProxy.getParentRef()); 167 if (brothers != null && brothers.size() > 1) { 168 // we remove the brothers of the published document if any 169 // the use case is: 170 // v1 is published, v2 is waiting for publication and was just 171 // validated 172 // v1 is removed and v2 is now being published 173 for (DocumentModel doc : brothers) { 174 if (!doc.getId().equals(newProxy.getId())) { 175 session.removeDocument(doc.getRef()); 176 } 177 } 178 } 179 } 180 }.runUnrestricted(); 181 } 182 183 @Override 184 public void validatorPublishDocument(PublishedDocument publishedDocument, String comment) { 185 DocumentModel proxy = ((SimpleCorePublishedDocument) publishedDocument).getProxy(); 186 NuxeoPrincipal principal = (NuxeoPrincipal) coreSession.getPrincipal(); 187 removeExistingProxiesOnPreviousVersions(proxy); 188 removeACL(proxy, coreSession); 189 endTask(proxy, principal, coreSession, comment, PublishingEvent.documentPublicationApproved); 190 notifyEvent(PublishingEvent.documentPublicationApproved, proxy, coreSession); 191 notifyEvent(PublishingEvent.documentPublished, proxy, coreSession); 192 ((SimpleCorePublishedDocument) publishedDocument).setPending(false); 193 } 194 195 protected void removeACL(DocumentModel document, CoreSession coreSession) { 196 RemoveACLUnrestricted remover = new RemoveACLUnrestricted(coreSession, document, ACL_NAME, JBPM_ACL_NAME); 197 remover.runUnrestricted(); 198 } 199 200 protected void endTask(DocumentModel document, NuxeoPrincipal currentUser, CoreSession session, String comment, 201 PublishingEvent event) { 202 List<Task> tis = getTaskService().getTaskInstances(document, currentUser, session); 203 String initiator = null; 204 for (Task task : tis) { 205 if (task.getName().equals(TASK_NAME)) { 206 initiator = (String) task.getVariable(TaskService.VariableName.initiator.name()); 207 task.end(session); 208 // make sure taskDoc is attached to prevent sending event with null session 209 DocumentModel taskDocument = task.getDocument(); 210 if (taskDocument.getSessionId() == null) { 211 taskDocument.attach(coreSession.getSessionId()); 212 } 213 session.saveDocument(taskDocument); 214 break; 215 } 216 } 217 DocumentModel liveDoc = getLiveDocument(session, document); 218 Map<String, Serializable> properties = new HashMap<String, Serializable>(); 219 if (initiator != null) { 220 properties.put(NotificationConstants.RECIPIENTS_KEY, new String[] { initiator }); 221 } 222 notifyEvent(event.name(), properties, comment, null, liveDoc, session); 223 } 224 225 protected DocumentModel getLiveDocument(CoreSession session, DocumentModel proxy) { 226 GetsProxySourceDocumentsUnrestricted runner = new GetsProxySourceDocumentsUnrestricted(session, proxy); 227 runner.runUnrestricted(); 228 return runner.liveDocument; 229 } 230 231 @Override 232 public void validatorRejectPublication(PublishedDocument publishedDocument, String comment) { 233 DocumentModel proxy = ((SimpleCorePublishedDocument) publishedDocument).getProxy(); 234 NuxeoPrincipal principal = (NuxeoPrincipal) coreSession.getPrincipal(); 235 notifyEvent(PublishingEvent.documentPublicationRejected, proxy, coreSession); 236 endTask(proxy, principal, coreSession, comment, PublishingEvent.documentPublicationRejected); 237 removeProxy(proxy, coreSession); 238 } 239 240 protected void removeProxy(DocumentModel doc, CoreSession coreSession) { 241 DeleteDocumentUnrestricted deleter = new DeleteDocumentUnrestricted(coreSession, doc); 242 deleter.runUnrestricted(); 243 } 244 245 @Override 246 public PublishedDocument wrapDocumentModel(DocumentModel doc) { 247 final SimpleCorePublishedDocument publishedDocument = (SimpleCorePublishedDocument) super.wrapDocumentModel( 248 doc); 249 250 new UnrestrictedSessionRunner(coreSession) { 251 @Override 252 public void run() { 253 if (!isPublished(publishedDocument, session)) { 254 publishedDocument.setPending(true); 255 } 256 257 } 258 }.runUnrestricted(); 259 260 return publishedDocument; 261 } 262 263 protected boolean isPublished(PublishedDocument publishedDocument, CoreSession session) { 264 // FIXME: should be cached 265 DocumentModel proxy = ((SimpleCorePublishedDocument) publishedDocument).getProxy(); 266 return lookupState.isPublished(proxy, session); 267 } 268 269 @Override 270 public boolean canManagePublishing(PublishedDocument publishedDocument) { 271 DocumentModel proxy = ((SimpleCorePublishedDocument) publishedDocument).getProxy(); 272 NuxeoPrincipal currentUser = (NuxeoPrincipal) coreSession.getPrincipal(); 273 return proxy.isProxy() && hasValidationTask(proxy, currentUser); 274 } 275 276 protected boolean hasValidationTask(DocumentModel proxy, NuxeoPrincipal currentUser) { 277 assert currentUser != null; 278 List<Task> tasks = getTaskService().getTaskInstances(proxy, currentUser, coreSession); 279 for (Task task : tasks) { 280 if (task.getName().equals(TASK_NAME)) { 281 return true; 282 } 283 } 284 return false; 285 } 286 287 @Override 288 public boolean hasValidationTask(PublishedDocument publishedDocument) { 289 DocumentModel proxy = ((SimpleCorePublishedDocument) publishedDocument).getProxy(); 290 NuxeoPrincipal currentUser = (NuxeoPrincipal) coreSession.getPrincipal(); 291 return hasValidationTask(proxy, currentUser); 292 } 293 294 private class GetsProxySourceDocumentsUnrestricted extends UnrestrictedSessionRunner { 295 296 public DocumentModel liveDocument; 297 298 private DocumentModel sourceDocument; 299 300 private final DocumentModel document; 301 302 public GetsProxySourceDocumentsUnrestricted(CoreSession session, DocumentModel proxy) { 303 super(session); 304 this.document = proxy; 305 } 306 307 @Override 308 public void run() { 309 sourceDocument = session.getDocument(new IdRef(document.getSourceId())); 310 liveDocument = session.getDocument(new IdRef(sourceDocument.getSourceId())); 311 } 312 } 313 314 /** 315 * @author arussel 316 */ 317 protected class DocumentPublisherUnrestricted extends UnrestrictedSessionRunner { 318 319 protected PublishedDocument result; 320 321 protected DocumentRef docRef; 322 323 protected DocumentRef targetRef; 324 325 protected NuxeoPrincipal principal; 326 327 protected String comment = ""; 328 329 public DocumentPublisherUnrestricted(CoreSession session, DocumentRef docRef, DocumentRef targetRef, 330 NuxeoPrincipal principal, String comment) { 331 super(session); 332 this.docRef = docRef; 333 this.targetRef = targetRef; 334 this.principal = principal; 335 this.comment = comment; 336 } 337 338 public PublishedDocument getPublishedDocument() { 339 return result; 340 } 341 342 @Override 343 public void run() { 344 DocumentModelList list = session.getProxies(docRef, targetRef); 345 DocumentModel proxy = null; 346 if (list.isEmpty()) {// first publication 347 proxy = session.publishDocument(session.getDocument(docRef), session.getDocument(targetRef)); 348 SimpleCorePublishedDocument publishedDocument = new SimpleCorePublishedDocument(proxy); 349 session.save(); 350 if (!isValidator(proxy, principal)) { 351 notifyEvent(PublishingEvent.documentWaitingPublication, coreSession.getDocument(proxy.getRef()), 352 coreSession); 353 restrictPermission(proxy, principal, session, null); 354 createTask(proxy, coreSession, principal); 355 publishedDocument.setPending(true); 356 } else { 357 notifyEvent(PublishingEvent.documentPublished, proxy, coreSession); 358 } 359 result = publishedDocument; 360 } else if (list.size() == 1) { 361 // one doc is already published or waiting for publication 362 if (isPublishedDocWaitingForPublication(list.get(0), session)) { 363 proxy = session.publishDocument(session.getDocument(docRef), session.getDocument(targetRef)); 364 if (!isValidator(proxy, principal)) { 365 // we're getting the old proxy acl 366 ACL acl = list.get(0).getACP().getACL(ACL_NAME); 367 acl.add(0, new ACE(principal.getName(), SecurityConstants.READ, true)); 368 ACP acp = proxy.getACP(); 369 acp.addACL(acl); 370 session.setACP(proxy.getRef(), acp, true); 371 session.save(); 372 SimpleCorePublishedDocument publishedDocument = new SimpleCorePublishedDocument(proxy); 373 publishedDocument.setPending(true); 374 result = publishedDocument; 375 } else { 376 endTask(proxy, principal, coreSession, "", PublishingEvent.documentPublicationApproved); 377 notifyEvent(PublishingEvent.documentPublished, proxy, coreSession); 378 ACP acp = proxy.getACP(); 379 acp.removeACL(ACL_NAME); 380 session.setACP(proxy.getRef(), acp, true); 381 session.save(); 382 result = new SimpleCorePublishedDocument(proxy); 383 } 384 } else { 385 if (!isValidator(list.get(0), principal)) { 386 proxy = session.publishDocument(session.getDocument(docRef), session.getDocument(targetRef), 387 false); 388 // save needed to have the proxy visible from other 389 // sessions in non-JCA mode 390 session.save(); 391 SimpleCorePublishedDocument publishedDocument = new SimpleCorePublishedDocument(proxy); 392 notifyEvent(PublishingEvent.documentWaitingPublication, proxy, coreSession); 393 restrictPermission(proxy, principal, coreSession, null); 394 session.save(); // process invalidations (non-JCA) 395 createTask(proxy, coreSession, principal); 396 publishedDocument.setPending(true); 397 result = publishedDocument; 398 } else { 399 proxy = session.publishDocument(session.getDocument(docRef), session.getDocument(targetRef)); 400 // save needed to have the proxy visible from other 401 // sessions in non-JCA mode 402 session.save(); 403 notifyEvent(PublishingEvent.documentPublished, proxy, coreSession); 404 SimpleCorePublishedDocument publishedDocument = new SimpleCorePublishedDocument(proxy); 405 result = publishedDocument; 406 } 407 } 408 } else if (list.size() == 2) { 409 DocumentModel waitingForPublicationDoc = null; 410 session.save(); // process invalidations (non-JCA) 411 for (DocumentModel dm : list) { 412 if (session.getACP(dm.getRef()).getACL(ACL_NAME) != null) { 413 waitingForPublicationDoc = dm; 414 } 415 } 416 if (!isValidator(waitingForPublicationDoc, principal)) { 417 // we're getting the old proxy acl 418 ACL acl = waitingForPublicationDoc.getACP().getACL(ACL_NAME); 419 acl.add(0, new ACE(principal.getName(), SecurityConstants.READ, true)); 420 // remove publishedDoc 421 ACP acp = session.getACP(waitingForPublicationDoc.getRef()); 422 acp.addACL(acl); 423 session.setACP(waitingForPublicationDoc.getRef(), acp, true); 424 SimpleCorePublishedDocument publishedDocument = new SimpleCorePublishedDocument( 425 waitingForPublicationDoc); 426 publishedDocument.setPending(true); 427 result = publishedDocument; 428 } else { 429 endTask(waitingForPublicationDoc, principal, coreSession, comment, 430 PublishingEvent.documentPublicationApproved); 431 session.removeDocument(waitingForPublicationDoc.getRef()); 432 proxy = session.publishDocument(session.getDocument(docRef), session.getDocument(targetRef)); 433 notifyEvent(PublishingEvent.documentPublished, proxy, coreSession); 434 SimpleCorePublishedDocument publishedDocument = new SimpleCorePublishedDocument(proxy); 435 result = publishedDocument; 436 } 437 } 438 if (proxy != null) { 439 proxy.detach(true); 440 } 441 } 442 } 443 444}