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