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