001/* 002 * (C) Copyright 2009-2012 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 * Alexandre Russel 016 * Florent Guillaume 017 */ 018package org.nuxeo.ecm.platform.routing.core.impl; 019 020import static org.nuxeo.ecm.platform.query.nxql.CoreQueryDocumentPageProvider.CORE_SESSION_PROPERTY; 021import static org.nuxeo.ecm.platform.query.nxql.CoreQueryDocumentPageProvider.MAX_RESULTS_PROPERTY; 022import static org.nuxeo.ecm.platform.query.nxql.CoreQueryDocumentPageProvider.PAGE_SIZE_RESULTS_KEY; 023import static org.nuxeo.ecm.platform.routing.api.DocumentRoutingConstants.DOC_ROUTING_SEARCH_ALL_ROUTE_MODELS_PROVIDER_NAME; 024import static org.nuxeo.ecm.platform.routing.api.DocumentRoutingConstants.DOC_ROUTING_SEARCH_ROUTE_MODELS_WITH_TITLE_PROVIDER_NAME; 025 026import java.io.IOException; 027import java.io.Serializable; 028import java.net.MalformedURLException; 029import java.net.URL; 030import java.util.ArrayList; 031import java.util.Collections; 032import java.util.HashMap; 033import java.util.List; 034import java.util.Map; 035import java.util.concurrent.TimeUnit; 036 037import org.apache.commons.lang.StringUtils; 038import org.apache.commons.logging.Log; 039import org.apache.commons.logging.LogFactory; 040import org.nuxeo.ecm.core.api.Blob; 041import org.nuxeo.ecm.core.api.CoreSession; 042import org.nuxeo.ecm.core.api.DocumentModel; 043import org.nuxeo.ecm.core.api.DocumentModelList; 044import org.nuxeo.ecm.core.api.DocumentNotFoundException; 045import org.nuxeo.ecm.core.api.DocumentRef; 046import org.nuxeo.ecm.core.api.IdRef; 047import org.nuxeo.ecm.core.api.IterableQueryResult; 048import org.nuxeo.ecm.core.api.LifeCycleConstants; 049import org.nuxeo.ecm.core.api.NuxeoException; 050import org.nuxeo.ecm.core.api.NuxeoPrincipal; 051import org.nuxeo.ecm.core.api.PropertyException; 052import org.nuxeo.ecm.core.api.UnrestrictedSessionRunner; 053import org.nuxeo.ecm.core.api.impl.blob.URLBlob; 054import org.nuxeo.ecm.core.api.pathsegment.PathSegmentService; 055import org.nuxeo.ecm.core.api.security.ACE; 056import org.nuxeo.ecm.core.api.security.ACL; 057import org.nuxeo.ecm.core.api.security.ACP; 058import org.nuxeo.ecm.core.api.security.SecurityConstants; 059import org.nuxeo.ecm.core.api.security.impl.ACLImpl; 060import org.nuxeo.ecm.core.event.EventProducer; 061import org.nuxeo.ecm.core.event.impl.DocumentEventContext; 062import org.nuxeo.ecm.core.query.sql.NXQL; 063import org.nuxeo.ecm.core.repository.RepositoryInitializationHandler; 064import org.nuxeo.ecm.platform.filemanager.api.FileManager; 065import org.nuxeo.ecm.platform.query.api.PageProvider; 066import org.nuxeo.ecm.platform.query.api.PageProviderService; 067import org.nuxeo.ecm.platform.routing.api.DocumentRoute; 068import org.nuxeo.ecm.platform.routing.api.DocumentRouteElement; 069import org.nuxeo.ecm.platform.routing.api.DocumentRouteTableElement; 070import org.nuxeo.ecm.platform.routing.api.DocumentRoutingConstants; 071import org.nuxeo.ecm.platform.routing.api.DocumentRoutingPersister; 072import org.nuxeo.ecm.platform.routing.api.DocumentRoutingService; 073import org.nuxeo.ecm.platform.routing.api.LockableDocumentRoute; 074import org.nuxeo.ecm.platform.routing.api.RouteFolderElement; 075import org.nuxeo.ecm.platform.routing.api.RouteModelResourceType; 076import org.nuxeo.ecm.platform.routing.api.RouteTable; 077import org.nuxeo.ecm.platform.routing.api.exception.DocumentRouteAlredayLockedException; 078import org.nuxeo.ecm.platform.routing.api.exception.DocumentRouteException; 079import org.nuxeo.ecm.platform.routing.api.exception.DocumentRouteNotLockedException; 080import org.nuxeo.ecm.platform.routing.core.api.DocumentRoutingEngineService; 081import org.nuxeo.ecm.platform.routing.core.audit.RoutingAuditHelper; 082import org.nuxeo.ecm.platform.routing.core.listener.RouteModelsInitializator; 083import org.nuxeo.ecm.platform.routing.core.registries.RouteTemplateResourceRegistry; 084import org.nuxeo.ecm.platform.task.Task; 085import org.nuxeo.ecm.platform.task.TaskConstants; 086import org.nuxeo.ecm.platform.task.TaskEventNames; 087import org.nuxeo.ecm.platform.task.TaskService; 088import org.nuxeo.ecm.platform.task.core.service.TaskEventNotificationHelper; 089import org.nuxeo.runtime.api.Framework; 090import org.nuxeo.runtime.model.ComponentContext; 091import org.nuxeo.runtime.model.ComponentInstance; 092import org.nuxeo.runtime.model.DefaultComponent; 093import org.nuxeo.runtime.model.RuntimeContext; 094 095import com.google.common.cache.Cache; 096import com.google.common.cache.CacheBuilder; 097 098/** 099 * The implementation of the routing service. 100 */ 101public class DocumentRoutingServiceImpl extends DefaultComponent implements DocumentRoutingService { 102 103 private static Log log = LogFactory.getLog(DocumentRoutingServiceImpl.class); 104 105 /** Routes in any state (model or not). */ 106 private static final String AVAILABLE_ROUTES_QUERY = String.format("SELECT * FROM %s", 107 DocumentRoutingConstants.DOCUMENT_ROUTE_DOCUMENT_TYPE); 108 109 /** Routes Models. */ 110 private static final String AVAILABLE_ROUTES_MODEL_QUERY = String.format( 111 "SELECT * FROM %s WHERE ecm:currentLifeCycleState = '%s'", 112 DocumentRoutingConstants.DOCUMENT_ROUTE_DOCUMENT_TYPE, 113 DocumentRoutingConstants.DOCUMENT_ROUTE_MODEL_LIFECYCLESTATE); 114 115 /** Route models that have been validated. */ 116 private static final String ROUTE_MODEL_WITH_ID_QUERY = String.format( 117 "SELECT * FROM %s WHERE ecm:name = %%s AND ecm:currentLifeCycleState = 'validated' AND ecm:isCheckedInVersion = 0 AND ecm:isProxy = 0 ", 118 DocumentRoutingConstants.DOCUMENT_ROUTE_DOCUMENT_TYPE); 119 120 /** Route models that have been validated. */ 121 private static final String ROUTE_MODEL_DOC_ID_WITH_ID_QUERY = String.format( 122 "SELECT ecm:uuid FROM %s WHERE ecm:name = %%s AND ecm:currentLifeCycleState = 'validated' AND ecm:isCheckedInVersion = 0 AND ecm:isProxy = 0 ", 123 DocumentRoutingConstants.DOCUMENT_ROUTE_DOCUMENT_TYPE); 124 125 private static final String ORDERED_CHILDREN_QUERY = "SELECT * FROM Document WHERE" 126 + " ecm:parentId = '%s' AND ecm:isCheckedInVersion = 0 AND " 127 + "ecm:currentLifeCycleState != 'deleted' ORDER BY ecm:pos"; 128 129 public static final String CHAINS_TO_TYPE_XP = "chainsToType"; 130 131 public static final String PERSISTER_XP = "persister"; 132 133 /** 134 * @since 7.10 135 */ 136 public static final String ACTOR_ACE_CREATOR = "Workflow"; 137 138 // FIXME: use ContributionFragmentRegistry instances instead to handle hot 139 // reload 140 141 public static final String ROUTE_MODELS_IMPORTER_XP = "routeModelImporter"; 142 143 protected Map<String, String> typeToChain = new HashMap<String, String>(); 144 145 protected Map<String, String> undoChainIdFromRunning = new HashMap<String, String>(); 146 147 protected Map<String, String> undoChainIdFromDone = new HashMap<String, String>(); 148 149 protected DocumentRoutingPersister persister; 150 151 protected RouteTemplateResourceRegistry routeResourcesRegistry = new RouteTemplateResourceRegistry(); 152 153 protected RepositoryInitializationHandler repositoryInitializationHandler; 154 155 private Cache<String, String> modelsChache; 156 157 @Override 158 public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 159 if (CHAINS_TO_TYPE_XP.equals(extensionPoint)) { 160 ChainToTypeMappingDescriptor desc = (ChainToTypeMappingDescriptor) contribution; 161 typeToChain.put(desc.getDocumentType(), desc.getChainId()); 162 undoChainIdFromRunning.put(desc.getDocumentType(), desc.getUndoChainIdFromRunning()); 163 undoChainIdFromDone.put(desc.getDocumentType(), desc.getUndoChainIdFromDone()); 164 } else if (PERSISTER_XP.equals(extensionPoint)) { 165 PersisterDescriptor des = (PersisterDescriptor) contribution; 166 try { 167 persister = des.getKlass().newInstance(); 168 } catch (ReflectiveOperationException e) { 169 throw new RuntimeException(e); 170 } 171 } else if (ROUTE_MODELS_IMPORTER_XP.equals(extensionPoint)) { 172 RouteModelResourceType res = (RouteModelResourceType) contribution; 173 registerRouteResource(res, contributor.getRuntimeContext()); 174 } 175 } 176 177 @Override 178 public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 179 if (contribution instanceof RouteModelResourceType) { 180 routeResourcesRegistry.removeContribution((RouteModelResourceType) contribution); 181 } 182 super.unregisterContribution(contribution, extensionPoint, contributor); 183 } 184 185 protected static void fireEvent(String eventName, Map<String, Serializable> eventProperties, DocumentRoute route, 186 CoreSession session) { 187 eventProperties.put(DocumentRoutingConstants.DOCUMENT_ELEMENT_EVENT_CONTEXT_KEY, route); 188 eventProperties.put(DocumentEventContext.CATEGORY_PROPERTY_KEY, DocumentRoutingConstants.ROUTING_CATEGORY); 189 DocumentEventContext envContext = new DocumentEventContext(session, session.getPrincipal(), route.getDocument()); 190 envContext.setProperties(eventProperties); 191 EventProducer eventProducer = Framework.getLocalService(EventProducer.class); 192 eventProducer.fireEvent(envContext.newEvent(eventName)); 193 } 194 195 @Override 196 public String createNewInstance(final String routeModelId, final List<String> docIds, 197 final Map<String, Serializable> map, CoreSession session, final boolean startInstance) { 198 final String initiator = session.getPrincipal().getName(); 199 final String res[] = new String[1]; 200 new UnrestrictedSessionRunner(session) { 201 202 protected DocumentRoute route; 203 204 @Override 205 public void run() { 206 String routeDocId = getRouteModelDocIdWithId(session, routeModelId); 207 DocumentModel model = session.getDocument(new IdRef(routeDocId)); 208 DocumentModel instance = persister.createDocumentRouteInstanceFromDocumentRouteModel(model, session); 209 route = instance.getAdapter(DocumentRoute.class); 210 route.setAttachedDocuments(docIds); 211 route.save(session); 212 Map<String, Serializable> props = new HashMap<String, Serializable>(); 213 props.put(DocumentRoutingConstants.INITIATOR_EVENT_CONTEXT_KEY, initiator); 214 fireEvent(DocumentRoutingConstants.Events.beforeRouteReady.name(), props); 215 route.setReady(session); 216 fireEvent(DocumentRoutingConstants.Events.afterRouteReady.name(), props); 217 route.save(session); 218 if (startInstance) { 219 fireEvent(DocumentRoutingConstants.Events.beforeRouteStart.name(), 220 new HashMap<String, Serializable>()); 221 DocumentRoutingEngineService routingEngine = Framework.getLocalService(DocumentRoutingEngineService.class); 222 routingEngine.start(route, map, session); 223 fireEventAfterWorkflowStarted(route, session); 224 } 225 res[0] = instance.getId(); 226 } 227 228 protected void fireEvent(String eventName, Map<String, Serializable> eventProperties) { 229 DocumentRoutingServiceImpl.fireEvent(eventName, eventProperties, route, session); 230 } 231 232 }.runUnrestricted(); 233 234 return res[0]; 235 } 236 237 @Override 238 public String createNewInstance(String routeModelId, List<String> docIds, CoreSession session, boolean startInstance) { 239 return createNewInstance(routeModelId, docIds, null, session, startInstance); 240 } 241 242 @Override 243 public DocumentRoute createNewInstance(DocumentRoute model, List<String> docIds, CoreSession session, 244 boolean startInstance) { 245 String id = createNewInstance(model.getDocument().getName(), docIds, session, startInstance); 246 return session.getDocument(new IdRef(id)).getAdapter(DocumentRoute.class); 247 } 248 249 @Override 250 @Deprecated 251 public DocumentRoute createNewInstance(DocumentRoute model, String documentId, CoreSession session, 252 boolean startInstance) { 253 return createNewInstance(model, Collections.singletonList(documentId), session, startInstance); 254 } 255 256 @Override 257 @Deprecated 258 public DocumentRoute createNewInstance(DocumentRoute model, List<String> documentIds, CoreSession session) { 259 return createNewInstance(model, documentIds, session, true); 260 } 261 262 @Override 263 @Deprecated 264 public DocumentRoute createNewInstance(DocumentRoute model, String documentId, CoreSession session) { 265 return createNewInstance(model, Collections.singletonList(documentId), session, true); 266 } 267 268 @Override 269 public void startInstance(final String routeInstanceId, final List<String> docIds, 270 final Map<String, Serializable> map, CoreSession session) { 271 new UnrestrictedSessionRunner(session) { 272 @Override 273 public void run() { 274 DocumentModel instance = session.getDocument(new IdRef(routeInstanceId)); 275 DocumentRoute route = instance.getAdapter(DocumentRoute.class); 276 if (docIds != null) { 277 route.setAttachedDocuments(docIds); 278 route.save(session); 279 } 280 fireEvent(DocumentRoutingConstants.Events.beforeRouteStart.name(), new HashMap<String, Serializable>(), 281 route, session); 282 DocumentRoutingEngineService routingEngine = Framework.getLocalService(DocumentRoutingEngineService.class); 283 routingEngine.start(route, map, session); 284 fireEventAfterWorkflowStarted(route, session); 285 } 286 287 }.runUnrestricted(); 288 } 289 290 protected void fireEventAfterWorkflowStarted(DocumentRoute route, CoreSession session) { 291 Map<String, Serializable> eventProperties = new HashMap<String, Serializable>(); 292 eventProperties.put(RoutingAuditHelper.WORKFLOW_INITATIOR, route.getInitiator()); 293 eventProperties.put("modelId", route.getModelId()); 294 eventProperties.put("modelName", route.getModelName()); 295 if (route instanceof GraphRoute) { 296 eventProperties.put(RoutingAuditHelper.WORKFLOW_VARIABLES, 297 (Serializable) ((GraphRoute) route).getVariables()); 298 } 299 fireEvent(DocumentRoutingConstants.Events.afterWorkflowStarted.name(), eventProperties, route, session); 300 } 301 302 @Override 303 public void resumeInstance(String routeId, String nodeId, Map<String, Object> data, String status, 304 CoreSession session) { 305 completeTask(routeId, nodeId, null, data, status, session); 306 } 307 308 @Override 309 public void completeTask(String routeId, String taskId, Map<String, Object> data, String status, CoreSession session) { 310 DocumentModel task = session.getDocument(new IdRef(taskId)); 311 completeTask(routeId, null, task != null ? task.getAdapter(Task.class) : null, data, status, session); 312 } 313 314 protected void completeTask(final String routeId, final String nodeId, final Task task, 315 final Map<String, Object> data, final String status, CoreSession session) { 316 CompleteTaskRunner runner = new CompleteTaskRunner(routeId, nodeId, task, data, status, session); 317 runner.runUnrestricted(); 318 } 319 320 /** 321 * @since 7.4 322 */ 323 private class CompleteTaskRunner extends UnrestrictedSessionRunner { 324 325 String routeId; 326 327 String nodeId; 328 329 Task task; 330 331 Map<String, Object> data; 332 333 String status; 334 335 protected CompleteTaskRunner(final String routeId, final String nodeId, final Task task, 336 final Map<String, Object> data, final String status, CoreSession session) { 337 super(session); 338 this.routeId = routeId; 339 this.nodeId = nodeId; 340 this.task = task; 341 this.data = data; 342 this.status = status; 343 } 344 345 @Override 346 public void run() { 347 DocumentRoutingEngineService routingEngine = Framework.getLocalService(DocumentRoutingEngineService.class); 348 DocumentModel routeDoc = session.getDocument(new IdRef(routeId)); 349 DocumentRoute routeInstance = routeDoc.getAdapter(DocumentRoute.class); 350 routingEngine.resume(routeInstance, nodeId, task != null ? task.getId() : null, data, status, session); 351 } 352 353 } 354 355 @Override 356 public List<DocumentRoute> getAvailableDocumentRouteModel(CoreSession session) { 357 DocumentModelList list = session.query(AVAILABLE_ROUTES_MODEL_QUERY); 358 List<DocumentRoute> routes = new ArrayList<DocumentRoute>(); 359 for (DocumentModel model : list) { 360 routes.add(model.getAdapter(DocumentRoute.class)); 361 } 362 return routes; 363 } 364 365 @Override 366 public List<DocumentRoute> getAvailableDocumentRoute(CoreSession session) { 367 DocumentModelList list = session.query(AVAILABLE_ROUTES_QUERY); 368 List<DocumentRoute> routes = new ArrayList<DocumentRoute>(); 369 for (DocumentModel model : list) { 370 routes.add(model.getAdapter(DocumentRoute.class)); 371 } 372 return routes; 373 } 374 375 @Override 376 public String getOperationChainId(String documentType) { 377 return typeToChain.get(documentType); 378 } 379 380 @Override 381 public String getUndoFromRunningOperationChainId(String documentType) { 382 return undoChainIdFromRunning.get(documentType); 383 } 384 385 @Override 386 public String getUndoFromDoneOperationChainId(String documentType) { 387 return undoChainIdFromDone.get(documentType); 388 } 389 390 @Override 391 public DocumentRoute unlockDocumentRouteUnrestrictedSession(final DocumentRoute routeModel, CoreSession userSession) { 392 new UnrestrictedSessionRunner(userSession) { 393 @Override 394 public void run() { 395 DocumentRoute route = session.getDocument(routeModel.getDocument().getRef()).getAdapter( 396 DocumentRoute.class); 397 LockableDocumentRoute lockableRoute = route.getDocument().getAdapter(LockableDocumentRoute.class); 398 lockableRoute.unlockDocument(session); 399 } 400 }.runUnrestricted(); 401 return userSession.getDocument(routeModel.getDocument().getRef()).getAdapter(DocumentRoute.class); 402 } 403 404 @Override 405 public DocumentRoute validateRouteModel(final DocumentRoute routeModel, CoreSession userSession) 406 throws DocumentRouteNotLockedException { 407 if (!routeModel.getDocument().isLocked()) { 408 throw new DocumentRouteNotLockedException(); 409 } 410 new UnrestrictedSessionRunner(userSession) { 411 @Override 412 public void run() { 413 DocumentRoute route = session.getDocument(routeModel.getDocument().getRef()).getAdapter( 414 DocumentRoute.class); 415 route.validate(session); 416 } 417 }.runUnrestricted(); 418 return userSession.getDocument(routeModel.getDocument().getRef()).getAdapter(DocumentRoute.class); 419 } 420 421 /** 422 * @deprecated since 5.9.2 - Use only routes of type 'graph' 423 */ 424 @Deprecated 425 @Override 426 public List<DocumentRouteTableElement> getRouteElements(DocumentRoute route, CoreSession session) { 427 RouteTable table = new RouteTable(route); 428 List<DocumentRouteTableElement> elements = new ArrayList<DocumentRouteTableElement>(); 429 processElementsInFolder(route.getDocument(), elements, table, session, 0, null); 430 int maxDepth = 0; 431 for (DocumentRouteTableElement element : elements) { 432 int d = element.getDepth(); 433 maxDepth = d > maxDepth ? d : maxDepth; 434 } 435 table.setMaxDepth(maxDepth); 436 for (DocumentRouteTableElement element : elements) { 437 element.computeFirstChildList(); 438 } 439 return elements; 440 } 441 442 /** 443 * @deprecated since 5.9.2 - Use only routes of type 'graph' 444 */ 445 @Deprecated 446 protected void processElementsInFolder(DocumentModel doc, List<DocumentRouteTableElement> elements, 447 RouteTable table, CoreSession session, int depth, RouteFolderElement folder) { 448 DocumentModelList children = session.getChildren(doc.getRef()); 449 boolean first = true; 450 for (DocumentModel child : children) { 451 if (child.isFolder() && !session.getChildren(child.getRef()).isEmpty()) { 452 RouteFolderElement thisFolder = new RouteFolderElement(child.getAdapter(DocumentRouteElement.class), 453 table, first, folder, depth); 454 processElementsInFolder(child, elements, table, session, depth + 1, thisFolder); 455 } else { 456 if (folder != null) { 457 folder.increaseTotalChildCount(); 458 } else { 459 table.increaseTotalChildCount(); 460 } 461 elements.add(new DocumentRouteTableElement(child.getAdapter(DocumentRouteElement.class), table, depth, 462 folder, first)); 463 } 464 first = false; 465 } 466 } 467 468 @Deprecated 469 protected List<DocumentRouteTableElement> getRouteElements(DocumentRouteElement routeElementDocument, 470 CoreSession session, List<DocumentRouteTableElement> routeElements, int depth) { 471 return null; 472 } 473 474 @Override 475 public List<DocumentRoute> getDocumentRoutesForAttachedDocument(CoreSession session, String attachedDocId) { 476 List<DocumentRouteElement.ElementLifeCycleState> states = new ArrayList<DocumentRouteElement.ElementLifeCycleState>(); 477 states.add(DocumentRouteElement.ElementLifeCycleState.ready); 478 states.add(DocumentRouteElement.ElementLifeCycleState.running); 479 return getDocumentRoutesForAttachedDocument(session, attachedDocId, states); 480 } 481 482 @Override 483 public List<DocumentRoute> getDocumentRoutesForAttachedDocument(CoreSession session, String attachedDocId, 484 List<DocumentRouteElement.ElementLifeCycleState> states) { 485 DocumentModelList list = null; 486 StringBuilder statesString = new StringBuilder(); 487 if (states != null && !states.isEmpty()) { 488 statesString.append(" ecm:currentLifeCycleState IN ("); 489 for (DocumentRouteElement.ElementLifeCycleState state : states) { 490 statesString.append("'" + state.name() + "',"); 491 } 492 statesString.deleteCharAt(statesString.length() - 1); 493 statesString.append(") AND"); 494 } 495 String query = String.format("SELECT * FROM DocumentRoute WHERE " + statesString.toString() 496 + " docri:participatingDocuments/* = '%s'" 497 // ordering by dc:created makes sure that 498 // a sub-workflow is listed under its parent 499 + " ORDER BY dc:created", attachedDocId); 500 UnrestrictedQueryRunner queryRunner = new UnrestrictedQueryRunner(session, query); 501 list = queryRunner.runQuery(); 502 List<DocumentRoute> routes = new ArrayList<DocumentRoute>(); 503 for (DocumentModel model : list) { 504 routes.add(model.getAdapter(DocumentRoute.class)); 505 } 506 return routes; 507 } 508 509 @Override 510 public boolean canUserValidateRoute(NuxeoPrincipal currentUser) { 511 return currentUser.getGroups().contains(DocumentRoutingConstants.ROUTE_MANAGERS_GROUP_NAME); 512 } 513 514 @Override 515 public boolean canValidateRoute(DocumentModel documentRoute, CoreSession coreSession) { 516 if (!coreSession.hasChildren(documentRoute.getRef())) { 517 // Cannot validate an empty route 518 return false; 519 } 520 return coreSession.hasPermission(documentRoute.getRef(), SecurityConstants.EVERYTHING); 521 } 522 523 // @deprecated since 5.9.2 - Use only routes of type 'graph' 524 @Override 525 @Deprecated 526 public void addRouteElementToRoute(DocumentRef parentDocumentRef, int idx, DocumentRouteElement routeElement, 527 CoreSession session) throws DocumentRouteNotLockedException { 528 DocumentRoute route = getParentRouteModel(parentDocumentRef, session); 529 if (!isLockedByCurrentUser(route, session)) { 530 throw new DocumentRouteNotLockedException(); 531 } 532 DocumentModelList children = session.query(String.format(ORDERED_CHILDREN_QUERY, 533 session.getDocument(parentDocumentRef).getId())); 534 DocumentModel sourceDoc; 535 try { 536 sourceDoc = children.get(idx); 537 addRouteElementToRoute(parentDocumentRef, sourceDoc.getName(), routeElement, session); 538 } catch (IndexOutOfBoundsException e) { 539 addRouteElementToRoute(parentDocumentRef, null, routeElement, session); 540 } 541 } 542 543 // @deprecated since 5.9.2 - Use only routes of type 'graph' 544 @Override 545 @Deprecated 546 public void addRouteElementToRoute(DocumentRef parentDocumentRef, String sourceName, 547 DocumentRouteElement routeElement, CoreSession session) throws DocumentRouteNotLockedException { 548 DocumentRoute parentRoute = getParentRouteModel(parentDocumentRef, session); 549 if (!isLockedByCurrentUser(parentRoute, session)) { 550 throw new DocumentRouteNotLockedException(); 551 } 552 PathSegmentService pss = Framework.getService(PathSegmentService.class); 553 DocumentModel docRouteElement = routeElement.getDocument(); 554 DocumentModel parentDocument = session.getDocument(parentDocumentRef); 555 docRouteElement.setPathInfo(parentDocument.getPathAsString(), pss.generatePathSegment(docRouteElement)); 556 String lifecycleState = parentDocument.getCurrentLifeCycleState().equals( 557 DocumentRouteElement.ElementLifeCycleState.draft.name()) ? DocumentRouteElement.ElementLifeCycleState.draft.name() 558 : DocumentRouteElement.ElementLifeCycleState.ready.name(); 559 docRouteElement.putContextData(LifeCycleConstants.INITIAL_LIFECYCLE_STATE_OPTION_NAME, lifecycleState); 560 docRouteElement = session.createDocument(docRouteElement); 561 session.orderBefore(parentDocumentRef, docRouteElement.getName(), sourceName); 562 session.save();// the new document will be queried later on 563 } 564 565 @Override 566 public void removeRouteElement(DocumentRouteElement routeElement, CoreSession session) 567 throws DocumentRouteNotLockedException { 568 DocumentRoute parentRoute = routeElement.getDocumentRoute(session); 569 if (!isLockedByCurrentUser(parentRoute, session)) { 570 throw new DocumentRouteNotLockedException(); 571 } 572 session.removeDocument(routeElement.getDocument().getRef()); 573 session.save();// the document will be queried later on 574 } 575 576 @Override 577 public DocumentModelList getOrderedRouteElement(String routeElementId, CoreSession session) { 578 String query = String.format(ORDERED_CHILDREN_QUERY, routeElementId); 579 DocumentModelList orderedChildren = session.query(query); 580 return orderedChildren; 581 } 582 583 @Override 584 public void lockDocumentRoute(DocumentRoute routeModel, CoreSession session) 585 throws DocumentRouteAlredayLockedException { 586 LockableDocumentRoute lockableRoute = routeModel.getDocument().getAdapter(LockableDocumentRoute.class); 587 boolean lockedByCurrent = isLockedByCurrentUser(routeModel, session); 588 if (lockableRoute.isLocked(session) && !lockedByCurrent) { 589 throw new DocumentRouteAlredayLockedException(); 590 } 591 if (!lockedByCurrent) { 592 lockableRoute.lockDocument(session); 593 } 594 } 595 596 @Override 597 public void unlockDocumentRoute(DocumentRoute routeModel, CoreSession session) 598 throws DocumentRouteNotLockedException { 599 LockableDocumentRoute lockableRoute = routeModel.getDocument().getAdapter(LockableDocumentRoute.class); 600 if (!lockableRoute.isLockedByCurrentUser(session)) { 601 throw new DocumentRouteNotLockedException(); 602 } 603 lockableRoute.unlockDocument(session); 604 } 605 606 @Override 607 public boolean isLockedByCurrentUser(DocumentRoute routeModel, CoreSession session) { 608 LockableDocumentRoute lockableRoute = routeModel.getDocument().getAdapter(LockableDocumentRoute.class); 609 return lockableRoute.isLockedByCurrentUser(session); 610 } 611 612 @Override 613 public void updateRouteElement(DocumentRouteElement routeElement, CoreSession session) 614 throws DocumentRouteNotLockedException { 615 if (!isLockedByCurrentUser(routeElement.getDocumentRoute(session), session)) { 616 throw new DocumentRouteNotLockedException(); 617 } 618 routeElement.save(session); 619 } 620 621 private DocumentRoute getParentRouteModel(DocumentRef documentRef, CoreSession session) { 622 DocumentModel parentDoc = session.getDocument(documentRef); 623 if (parentDoc.hasFacet(DocumentRoutingConstants.DOCUMENT_ROUTE_DOCUMENT_FACET)) { 624 return parentDoc.getAdapter(DocumentRoute.class); 625 } 626 DocumentRouteElement rElement = parentDoc.getAdapter(DocumentRouteElement.class); 627 return rElement.getDocumentRoute(session); 628 629 } 630 631 @Override 632 public DocumentRoute saveRouteAsNewModel(DocumentRoute instance, CoreSession session) { 633 DocumentModel instanceModel = instance.getDocument(); 634 DocumentModel parent = persister.getParentFolderForNewModel(session, instanceModel); 635 String newName = persister.getNewModelName(instanceModel); 636 DocumentModel newmodel = persister.saveDocumentRouteInstanceAsNewModel(instanceModel, parent, newName, session); 637 DocumentRoute newRoute = newmodel.getAdapter(DocumentRoute.class); 638 if (!newRoute.isDraft()) { 639 newRoute.followTransition(DocumentRouteElement.ElementLifeCycleTransistion.toDraft, session, false); 640 } 641 newRoute.getDocument().setPropertyValue("dc:title", newName); 642 newRoute.setAttachedDocuments(new ArrayList<String>()); 643 newRoute.save(session); 644 return newRoute; 645 } 646 647 @Override 648 public boolean isRoutable(DocumentModel doc) { 649 if (doc == null) { 650 return false; 651 } 652 String type = doc.getType(); 653 // TODO make configurable 654 return type.equals("File") || type.equals("Note"); 655 } 656 657 @Override 658 public void importAllRouteModels(CoreSession session) { 659 for (URL url : getRouteModelTemplateResources()) { 660 importRouteModel(url, true, session); 661 } 662 } 663 664 @Override 665 public DocumentRoute importRouteModel(URL modelToImport, boolean overwrite, CoreSession session) { 666 if (modelToImport == null) { 667 throw new NuxeoException(("No resource containing route templates found")); 668 } 669 Blob blob = new URLBlob(modelToImport); 670 final String file = modelToImport.getFile(); 671 DocumentModel doc; 672 try { 673 doc = getFileManager().createDocumentFromBlob(session, blob, 674 persister.getParentFolderForDocumentRouteModels(session).getPathAsString(), true, file); 675 } catch (IOException e) { 676 throw new NuxeoException(e); 677 } 678 if (doc == null) { 679 throw new NuxeoException("Can not import document " + file); 680 } 681 // remove model from cache if any model with the same id existed 682 if (modelsChache != null) { 683 modelsChache.invalidate(doc.getName()); 684 } 685 686 return doc.getAdapter(DocumentRoute.class); 687 } 688 689 protected FileManager getFileManager() { 690 return Framework.getService(FileManager.class); 691 } 692 693 @Override 694 public void activate(ComponentContext context) { 695 super.activate(context); 696 modelsChache = CacheBuilder.newBuilder().maximumSize(100).expireAfterWrite(10, TimeUnit.MINUTES).build(); 697 repositoryInitializationHandler = new RouteModelsInitializator(); 698 repositoryInitializationHandler.install(); 699 } 700 701 @Override 702 public void deactivate(ComponentContext context) { 703 super.deactivate(context); 704 if (repositoryInitializationHandler != null) { 705 repositoryInitializationHandler.uninstall(); 706 } 707 } 708 709 @Override 710 public List<URL> getRouteModelTemplateResources() { 711 List<URL> urls = new ArrayList<URL>(); 712 for (URL url : routeResourcesRegistry.getRouteModelTemplateResources()) { 713 urls.add(url); // test contrib parsing and deployment 714 } 715 return urls; 716 } 717 718 @SuppressWarnings("unchecked") 719 @Override 720 public List<DocumentModel> searchRouteModels(CoreSession session, String searchString) { 721 List<DocumentModel> allRouteModels = new ArrayList<DocumentModel>(); 722 PageProviderService pageProviderService = Framework.getLocalService(PageProviderService.class); 723 Map<String, Serializable> props = new HashMap<String, Serializable>(); 724 props.put(MAX_RESULTS_PROPERTY, PAGE_SIZE_RESULTS_KEY); 725 props.put(CORE_SESSION_PROPERTY, (Serializable) session); 726 PageProvider<DocumentModel> pageProvider; 727 if (StringUtils.isEmpty(searchString)) { 728 pageProvider = (PageProvider<DocumentModel>) pageProviderService.getPageProvider( 729 DOC_ROUTING_SEARCH_ALL_ROUTE_MODELS_PROVIDER_NAME, null, null, 0L, props); 730 } else { 731 pageProvider = (PageProvider<DocumentModel>) pageProviderService.getPageProvider( 732 DOC_ROUTING_SEARCH_ROUTE_MODELS_WITH_TITLE_PROVIDER_NAME, null, null, 0L, props, searchString + '%'); 733 } 734 allRouteModels.addAll(pageProvider.getCurrentPage()); 735 while (pageProvider.isNextPageAvailable()) { 736 pageProvider.nextPage(); 737 allRouteModels.addAll(pageProvider.getCurrentPage()); 738 } 739 return allRouteModels; 740 } 741 742 @Override 743 public void registerRouteResource(RouteModelResourceType res, RuntimeContext context) { 744 if (res.getPath() != null && res.getId() != null) { 745 if (routeResourcesRegistry.getResource(res.getId()) != null) { 746 routeResourcesRegistry.removeContribution(res); 747 } 748 if (res.getUrl() == null) { 749 res.setUrl(getUrlFromPath(res, context)); 750 } 751 routeResourcesRegistry.addContribution(res); 752 } 753 } 754 755 protected URL getUrlFromPath(RouteModelResourceType res, RuntimeContext extensionContext) { 756 String path = res.getPath(); 757 if (path == null) { 758 return null; 759 } 760 URL url = null; 761 try { 762 url = new URL(path); 763 } catch (MalformedURLException e) { 764 url = extensionContext.getLocalResource(path); 765 if (url == null) { 766 url = extensionContext.getResource(path); 767 } 768 if (url == null) { 769 url = res.getClass().getResource(path); 770 } 771 } 772 return url; 773 } 774 775 @Override 776 public DocumentRoute getRouteModelWithId(CoreSession session, String id) { 777 String routeDocModelId = getRouteModelDocIdWithId(session, id); 778 DocumentModel routeDoc = session.getDocument(new IdRef(routeDocModelId)); 779 return routeDoc.getAdapter(DocumentRoute.class); 780 } 781 782 @Override 783 public String getRouteModelDocIdWithId(CoreSession session, String id) { 784 if (modelsChache != null) { 785 String routeDocId = modelsChache.getIfPresent(id); 786 if (routeDocId != null) { 787 return routeDocId; 788 } 789 } 790 String query = String.format(ROUTE_MODEL_DOC_ID_WITH_ID_QUERY, NXQL.escapeString(id)); 791 List<String> routeIds = new ArrayList<String>(); 792 IterableQueryResult results = session.queryAndFetch(query, "NXQL"); 793 try { 794 if (results.size() == 0) { 795 throw new NuxeoException("No route found for id: " + id); 796 } 797 if (results.size() != 1) { 798 throw new NuxeoException("More than one route model found with id: " + id); 799 } 800 for (Map<String, Serializable> map : results) { 801 routeIds.add(map.get("ecm:uuid").toString()); 802 } 803 } finally { 804 results.close(); 805 } 806 String routeDocId = routeIds.get(0); 807 if (modelsChache == null) { 808 modelsChache = CacheBuilder.newBuilder().maximumSize(100).expireAfterWrite(10, TimeUnit.MINUTES).build(); 809 } 810 modelsChache.put(id, routeDocId); 811 return routeDocId; 812 } 813 814 @Override 815 @Deprecated 816 public void makeRoutingTasks(CoreSession coreSession, final List<Task> tasks) { 817 new UnrestrictedSessionRunner(coreSession) { 818 @Override 819 public void run() { 820 for (Task task : tasks) { 821 DocumentModel taskDoc = task.getDocument(); 822 taskDoc.addFacet(DocumentRoutingConstants.ROUTING_TASK_FACET_NAME); 823 session.saveDocument(taskDoc); 824 } 825 } 826 }.runUnrestricted(); 827 } 828 829 @Override 830 public void endTask(CoreSession session, Task task, Map<String, Object> data, String status) { 831 String comment = (String) data.get(GraphNode.NODE_VARIABLE_COMMENT); 832 TaskService taskService = Framework.getLocalService(TaskService.class); 833 taskService.endTask(session, (NuxeoPrincipal) session.getPrincipal(), task, comment, null, false); 834 835 Map<String, String> taskVariables = task.getVariables(); 836 String routeInstanceId = taskVariables.get(DocumentRoutingConstants.TASK_ROUTE_INSTANCE_DOCUMENT_ID_KEY); 837 if (StringUtils.isEmpty(routeInstanceId)) { 838 throw new DocumentRouteException("Can not resume workflow, no related route"); 839 } 840 completeTask(routeInstanceId, null, task, data, status, session); 841 final Map<String, Serializable> extraEventProperties = new HashMap<String, Serializable>(); 842 extraEventProperties.put(DocumentRoutingConstants.WORKFLOW_TASK_COMPLETION_ACTION_KEY, status); 843 TaskEventNotificationHelper.notifyTaskEnded(session, (NuxeoPrincipal) session.getPrincipal(), task, comment, 844 TaskEventNames.WORKFLOW_TASK_COMPLETED, extraEventProperties); 845 846 } 847 848 @Override 849 public List<DocumentModel> getWorkflowInputDocuments(CoreSession session, Task task) { 850 String routeInstanceId; 851 try { 852 routeInstanceId = task.getProcessId(); 853 } catch (PropertyException e) { 854 throw new DocumentRouteException("Can not get the related workflow instance"); 855 } 856 if (StringUtils.isEmpty(routeInstanceId)) { 857 throw new DocumentRouteException("Can not get the related workflow instance"); 858 } 859 DocumentModel routeDoc; 860 try { 861 routeDoc = session.getDocument(new IdRef(routeInstanceId)); 862 } catch (DocumentNotFoundException e) { 863 throw new DocumentRouteException("No workflow with the id:" + routeInstanceId); 864 } 865 DocumentRoute route = routeDoc.getAdapter(DocumentRoute.class); 866 return route.getAttachedDocuments(session); 867 } 868 869 @Override 870 public void grantPermissionToTaskAssignees(CoreSession session, String permission, List<DocumentModel> docs, 871 Task task) { 872 setAclForActors(session, getRoutingACLName(task), permission, docs, task.getActors()); 873 } 874 875 @Override 876 public void grantPermissionToTaskDelegatedActors(CoreSession session, String permission, List<DocumentModel> docs, 877 Task task) { 878 setAclForActors(session, getDelegationACLName(task), permission, docs, task.getDelegatedActors()); 879 } 880 881 @Override 882 public void removePermissionFromTaskAssignees(CoreSession session, final List<DocumentModel> docs, Task task) { 883 final String aclName = getRoutingACLName(task); 884 new UnrestrictedSessionRunner(session) { 885 @Override 886 public void run() { 887 for (DocumentModel doc : docs) { 888 ACP acp = doc.getACP(); 889 acp.removeACL(aclName); 890 doc.setACP(acp, true); 891 session.saveDocument(doc); 892 } 893 }; 894 }.runUnrestricted(); 895 } 896 897 /** 898 * @since 7.4 899 */ 900 @Override 901 public void removePermissionsForTaskActors(CoreSession session, final List<DocumentModel> docs, String taskId) { 902 final String aclRoutingName = getRoutingACLName(taskId); 903 final String aclDelegationName = getDelegationACLName(taskId); 904 new UnrestrictedSessionRunner(session) { 905 @Override 906 public void run() { 907 for (DocumentModel doc : docs) { 908 ACP acp = doc.getACP(); 909 acp.removeACL(aclRoutingName); 910 acp.removeACL(aclDelegationName); 911 doc.setACP(acp, true); 912 session.saveDocument(doc); 913 } 914 }; 915 }.runUnrestricted(); 916 } 917 918 @Override 919 public void removePermissionsForTaskActors(CoreSession session, final List<DocumentModel> docs, Task task) { 920 removePermissionsForTaskActors(session, docs, task.getId()); 921 } 922 923 /** 924 * Finds an ACL name specific to the task (there may be several tasks applying permissions to the same document). 925 */ 926 protected static String getRoutingACLName(Task task) { 927 return getRoutingACLName(task.getId()); 928 } 929 930 /** 931 * @since 7.4 932 */ 933 protected static String getRoutingACLName(String taskId) { 934 return DocumentRoutingConstants.DOCUMENT_ROUTING_ACL + '/' + taskId; 935 } 936 937 protected static String getDelegationACLName(Task task) { 938 return getDelegationACLName(task.getId()); 939 } 940 941 /** 942 * @since 7.4 943 */ 944 protected static String getDelegationACLName(String taskId) { 945 return DocumentRoutingConstants.DOCUMENT_ROUTING_DELEGATION_ACL + '/' + taskId; 946 } 947 948 /** 949 * @since 7.1 950 */ 951 private final class WfCleaner extends UnrestrictedSessionRunner { 952 private final int limit; 953 954 protected int i = 0; 955 956 private WfCleaner(String repositoryName, int limit) { 957 super(repositoryName); 958 this.limit = limit; 959 } 960 961 @Override 962 public void run() { 963 List<String> routeIds = new ArrayList<String>(); 964 String query = "SELECT ecm:uuid FROM DocumentRoute WHERE (ecm:currentLifeCycleState = 'done' " 965 + "OR ecm:currentLifeCycleState = 'canceled') ORDER BY dc:created"; 966 IterableQueryResult results = session.queryAndFetch(query, "NXQL"); 967 try { 968 for (Map<String, Serializable> result : results) { 969 routeIds.add(result.get("ecm:uuid").toString()); 970 i++; 971 // stop when the limit is reached and close the resultSet 972 if (i == limit) { 973 break; 974 } 975 } 976 } finally { 977 results.close(); 978 } 979 IterableQueryResult tasks = null; 980 for (String routeDocId : routeIds) { 981 final String associatedTaskQuery = String.format( 982 "SELECT ecm:uuid FROM Document WHERE ecm:mixinType = 'Task' AND nt:processId = '%s'", 983 routeDocId); 984 try { 985 tasks = session.queryAndFetch(associatedTaskQuery, "NXQL"); 986 for (Map<String, Serializable> task : tasks) { 987 final String taskId = task.get("ecm:uuid").toString(); 988 session.removeDocument(new IdRef(taskId)); 989 } 990 } finally { 991 tasks.close(); 992 } 993 session.removeDocument(new IdRef(routeDocId)); 994 } 995 } 996 997 public int getNumberOfCleanedUpWf() { 998 return i; 999 } 1000 } 1001 1002 class UnrestrictedQueryRunner extends UnrestrictedSessionRunner { 1003 1004 String query; 1005 1006 DocumentModelList docs; 1007 1008 protected UnrestrictedQueryRunner(CoreSession session, String query) { 1009 super(session); 1010 this.query = query; 1011 } 1012 1013 @Override 1014 public void run() { 1015 docs = session.query(query); 1016 for (DocumentModel documentModel : docs) { 1017 documentModel.detach(true); 1018 } 1019 } 1020 1021 public DocumentModelList runQuery() { 1022 runUnrestricted(); 1023 return docs; 1024 } 1025 } 1026 1027 @Override 1028 public void finishTask(CoreSession session, DocumentRoute route, Task task, boolean delete) 1029 throws DocumentRouteException { 1030 DocumentModelList docs = route.getAttachedDocuments(session); 1031 try { 1032 removePermissionsForTaskActors(session, docs, task); 1033 // delete task 1034 if (delete) { 1035 session.removeDocument(new IdRef(task.getId())); 1036 } 1037 } catch (DocumentNotFoundException e) { 1038 throw new DocumentRouteException("Cannot finish task", e); 1039 } 1040 } 1041 1042 @Override 1043 public void cancelTask(CoreSession session, final String taskId) throws DocumentRouteException { 1044 new UnrestrictedSessionRunner(session) { 1045 @Override 1046 public void run() { 1047 DocumentModel taskDoc = session.getDocument(new IdRef(taskId)); 1048 Task task = taskDoc.getAdapter(Task.class); 1049 if (task == null) { 1050 throw new DocumentRouteException("Invalid taskId: " + taskId); 1051 } 1052 1053 if (!task.isOpened()) { 1054 log.info("Can not cancel task " + taskId + "as is not open"); 1055 return; 1056 } 1057 task.cancel(session); 1058 1059 // if the task was created by an workflow , update info 1060 String routeId = task.getProcessId(); 1061 if (routeId != null) { 1062 DocumentModel routeDoc = session.getDocument(new IdRef(routeId)); 1063 GraphRoute routeInstance = routeDoc.getAdapter(GraphRoute.class); 1064 if (routeInstance == null) { 1065 throw new DocumentRouteException("Invalid routeInstanceId: " + routeId); 1066 } 1067 1068 DocumentModelList docs = routeInstance.getAttachedDocumentModels(); 1069 removePermissionsForTaskActors(session, docs, task); 1070 // task is considered processed with the status "null" 1071 // when 1072 // is 1073 // canceled 1074 updateTaskInfo(session, routeInstance, task, null); 1075 } 1076 session.saveDocument(task.getDocument()); 1077 1078 } 1079 }.runUnrestricted(); 1080 } 1081 1082 protected void updateTaskInfo(CoreSession session, GraphRoute graph, Task task, String status) { 1083 String nodeId = task.getVariable(DocumentRoutingConstants.TASK_NODE_ID_KEY); 1084 if (StringUtils.isEmpty(nodeId)) { 1085 throw new DocumentRouteException("No nodeId found on task: " + task.getId()); 1086 } 1087 GraphNode node = graph.getNode(nodeId); 1088 1089 NuxeoPrincipal principal = (NuxeoPrincipal) session.getPrincipal(); 1090 String actor = principal.getActingUser(); 1091 node.updateTaskInfo(task.getId(), true, status, actor, null); 1092 } 1093 1094 @Override 1095 public void reassignTask(CoreSession session, final String taskId, final List<String> actors, final String comment) 1096 throws DocumentRouteException { 1097 new UnrestrictedSessionRunner(session) { 1098 1099 @Override 1100 public void run() { 1101 DocumentModel taskDoc = session.getDocument(new IdRef(taskId)); 1102 Task task = taskDoc.getAdapter(Task.class); 1103 if (task == null) { 1104 throw new DocumentRouteException("Invalid taskId: " + taskId); 1105 } 1106 if (!task.isOpened()) { 1107 throw new DocumentRouteException("Task " + taskId + " is not opened, can not reassign it"); 1108 } 1109 String routeId = task.getProcessId(); 1110 if (routeId != null) { 1111 DocumentModel routeDoc = session.getDocument(new IdRef(routeId)); 1112 GraphRoute routeInstance = routeDoc.getAdapter(GraphRoute.class); 1113 if (routeInstance == null) { 1114 throw new DocumentRouteException("Invalid routeInstanceId: " + routeId 1115 + " referenced by the task " + taskId); 1116 } 1117 GraphNode node = routeInstance.getNode(task.getType()); 1118 if (node == null) { 1119 throw new DocumentRouteException("Invalid node " + routeId + " referenced by the task " 1120 + taskId); 1121 } 1122 if (!node.allowTaskReassignment()) { 1123 throw new DocumentRouteException("Task " + taskId + " can not be reassigned. Node " 1124 + node.getId() + " doesn't allow reassignment."); 1125 } 1126 DocumentModelList docs = routeInstance.getAttachedDocumentModels(); 1127 // remove permissions on the document following the 1128 // workflow for the current assignees 1129 removePermissionFromTaskAssignees(session, docs, task); 1130 Framework.getLocalService(TaskService.class).reassignTask(session, taskId, actors, comment); 1131 // refresh task 1132 task.getDocument().refresh(); 1133 // grant permission to the new assignees 1134 grantPermissionToTaskAssignees(session, node.getTaskAssigneesPermission(), docs, task); 1135 1136 // Audit task reassignment 1137 Map<String, Serializable> eventProperties = new HashMap<String, Serializable>(); 1138 eventProperties.put(DocumentEventContext.CATEGORY_PROPERTY_KEY, 1139 DocumentRoutingConstants.ROUTING_CATEGORY); 1140 eventProperties.put("taskName", task.getName()); 1141 eventProperties.put("actors", (Serializable) actors); 1142 eventProperties.put("modelId", routeInstance.getModelId()); 1143 eventProperties.put("modelName", routeInstance.getModelName()); 1144 eventProperties.put(RoutingAuditHelper.WORKFLOW_INITATIOR, routeInstance.getInitiator()); 1145 eventProperties.put(RoutingAuditHelper.TASK_ACTOR, 1146 ((NuxeoPrincipal) session.getPrincipal()).getActingUser()); 1147 eventProperties.put("comment", comment); 1148 // compute duration since workflow started 1149 long timeSinceWfStarted = RoutingAuditHelper.computeDurationSinceWfStarted(task.getProcessId()); 1150 if (timeSinceWfStarted >= 0) { 1151 eventProperties.put(RoutingAuditHelper.TIME_SINCE_WF_STARTED, timeSinceWfStarted); 1152 } 1153 // compute duration since task started 1154 long timeSinceTaskStarted = RoutingAuditHelper.computeDurationSinceTaskStarted(task.getId()); 1155 if (timeSinceWfStarted >= 0) { 1156 eventProperties.put(RoutingAuditHelper.TIME_SINCE_TASK_STARTED, timeSinceTaskStarted); 1157 } 1158 DocumentEventContext envContext = new DocumentEventContext(session, session.getPrincipal(), 1159 task.getDocument()); 1160 envContext.setProperties(eventProperties); 1161 EventProducer eventProducer = Framework.getLocalService(EventProducer.class); 1162 eventProducer.fireEvent(envContext.newEvent(DocumentRoutingConstants.Events.afterWorkflowTaskReassigned.name())); 1163 } 1164 } 1165 }.runUnrestricted(); 1166 } 1167 1168 @Override 1169 public void delegateTask(CoreSession session, final String taskId, final List<String> delegatedActors, 1170 final String comment) throws DocumentRouteException { 1171 new UnrestrictedSessionRunner(session) { 1172 1173 @Override 1174 public void run() { 1175 DocumentModel taskDoc = session.getDocument(new IdRef(taskId)); 1176 Task task = taskDoc.getAdapter(Task.class); 1177 if (task == null) { 1178 throw new DocumentRouteException("Invalid taskId: " + taskId); 1179 } 1180 String routeId = task.getProcessId(); 1181 if (routeId != null) { 1182 DocumentModel routeDoc = session.getDocument(new IdRef(routeId)); 1183 GraphRoute routeInstance = routeDoc.getAdapter(GraphRoute.class); 1184 if (routeInstance == null) { 1185 throw new DocumentRouteException("Invalid routeInstanceId: " + routeId 1186 + " referenced by the task " + taskId); 1187 } 1188 GraphNode node = routeInstance.getNode(task.getType()); 1189 if (node == null) { 1190 throw new DocumentRouteException("Invalid node " + routeId + " referenced by the task " 1191 + taskId); 1192 } 1193 DocumentModelList docs = routeInstance.getAttachedDocumentModels(); 1194 Framework.getLocalService(TaskService.class) 1195 .delegateTask(session, taskId, delegatedActors, comment); 1196 // refresh task 1197 task.getDocument().refresh(); 1198 // grant permission to the new assignees 1199 grantPermissionToTaskDelegatedActors(session, node.getTaskAssigneesPermission(), docs, task); 1200 1201 // Audit task delegation 1202 Map<String, Serializable> eventProperties = new HashMap<String, Serializable>(); 1203 eventProperties.put(DocumentEventContext.CATEGORY_PROPERTY_KEY, 1204 DocumentRoutingConstants.ROUTING_CATEGORY); 1205 eventProperties.put("taskName", task.getName()); 1206 eventProperties.put("delegatedActors", (Serializable) delegatedActors); 1207 eventProperties.put("modelId", routeInstance.getModelId()); 1208 eventProperties.put("modelName", routeInstance.getModelName()); 1209 eventProperties.put(RoutingAuditHelper.WORKFLOW_INITATIOR, routeInstance.getInitiator()); 1210 eventProperties.put(RoutingAuditHelper.TASK_ACTOR, 1211 ((NuxeoPrincipal) session.getPrincipal()).getActingUser()); 1212 eventProperties.put("comment", comment); 1213 1214 // compute duration since workflow started 1215 long timeSinceWfStarted = RoutingAuditHelper.computeDurationSinceWfStarted(task.getProcessId()); 1216 if (timeSinceWfStarted >= 0) { 1217 eventProperties.put(RoutingAuditHelper.TIME_SINCE_WF_STARTED, timeSinceWfStarted); 1218 } 1219 // compute duration since task started 1220 long timeSinceTaskStarted = RoutingAuditHelper.computeDurationSinceTaskStarted(task.getId()); 1221 if (timeSinceWfStarted >= 0) { 1222 eventProperties.put(RoutingAuditHelper.TIME_SINCE_TASK_STARTED, timeSinceTaskStarted); 1223 } 1224 1225 DocumentEventContext envContext = new DocumentEventContext(session, session.getPrincipal(), 1226 task.getDocument()); 1227 envContext.setProperties(eventProperties); 1228 EventProducer eventProducer = Framework.getLocalService(EventProducer.class); 1229 eventProducer.fireEvent(envContext.newEvent(DocumentRoutingConstants.Events.afterWorkflowTaskDelegated.name())); 1230 } 1231 } 1232 }.runUnrestricted(); 1233 } 1234 1235 protected void setAclForActors(CoreSession session, final String aclName, final String permission, 1236 final List<DocumentModel> docs, List<String> actors) { 1237 final List<String> actorIds = new ArrayList<String>(); 1238 for (String actor : actors) { 1239 if (actor.contains(":")) { 1240 actorIds.add(actor.split(":")[1]); 1241 } else { 1242 actorIds.add(actor); 1243 } 1244 } 1245 new UnrestrictedSessionRunner(session) { 1246 @Override 1247 public void run() { 1248 for (DocumentModel doc : docs) { 1249 ACP acp = doc.getACP(); 1250 acp.removeACL(aclName); 1251 ACL acl = new ACLImpl(aclName); 1252 for (String actorId : actorIds) { 1253 acl.add(ACE.builder(actorId, permission).creator(ACTOR_ACE_CREATOR).build()); 1254 } 1255 acp.addACL(0, acl); // add first to get before blocks 1256 doc.setACP(acp, true); 1257 session.saveDocument(doc); 1258 } 1259 } 1260 1261 }.runUnrestricted(); 1262 } 1263 1264 @Override 1265 public void cleanupDoneAndCanceledRouteInstances(final String reprositoryName, final int limit) { 1266 doCleanupDoneAndCanceledRouteInstances(reprositoryName, limit); 1267 } 1268 1269 @Override 1270 public int doCleanupDoneAndCanceledRouteInstances(final String reprositoryName, final int limit) { 1271 WfCleaner unrestrictedSessionRunner = new WfCleaner(reprositoryName, limit); 1272 unrestrictedSessionRunner.runUnrestricted(); 1273 return unrestrictedSessionRunner.getNumberOfCleanedUpWf(); 1274 } 1275 1276 @Override 1277 public void invalidateRouteModelsCache() { 1278 modelsChache.invalidateAll(); 1279 } 1280 1281 /** 1282 * @since 7.2 1283 */ 1284 @Override 1285 public List<Task> getTasks(final DocumentModel document, String actorId, String workflowInstanceId, 1286 final String worflowModelName, CoreSession session) { 1287 StringBuilder query = new StringBuilder(String.format( 1288 "SELECT * FROM Document WHERE ecm:mixinType = '%s' AND ecm:currentLifeCycleState = '%s'", 1289 TaskConstants.TASK_FACET_NAME, TaskConstants.TASK_OPENED_LIFE_CYCLE_STATE)); 1290 if (StringUtils.isNotBlank(actorId)) { 1291 query.append(String.format(" AND nt:actors/* = '%s'", actorId)); 1292 } 1293 if (StringUtils.isNotBlank(workflowInstanceId)) { 1294 query.append(String.format(" AND nt:processId = '%s'", workflowInstanceId)); 1295 } 1296 if (document != null) { 1297 query.append(String.format(" AND nt:targetDocumentId = '%s'", document.getId())); 1298 } 1299 final DocumentModelList documentModelList = session.query(query.toString()); 1300 final List<Task> result = new ArrayList<Task>(); 1301 1302 // User does not necessary have READ on the workflow instance 1303 new UnrestrictedSessionRunner(session) { 1304 1305 @Override 1306 public void run() { 1307 for (DocumentModel documentModel : documentModelList) { 1308 final Task task = documentModel.getAdapter(Task.class); 1309 if (StringUtils.isNotBlank(worflowModelName)) { 1310 1311 final String processId = task.getProcessId(); 1312 final DocumentRoute routeInstance = session.getDocument(new IdRef(processId)).getAdapter( 1313 DocumentRoute.class); 1314 if (routeInstance != null) { 1315 final String routeInstanceName = routeInstance.getName(); 1316 if (routeInstanceName != null 1317 && (routeInstanceName.equals(worflowModelName) || routeInstanceName.matches("^(" 1318 + worflowModelName + ")\\.\\d+"))) { 1319 result.add(task); 1320 } 1321 } 1322 } else { 1323 result.add(task); 1324 } 1325 } 1326 } 1327 }.runUnrestricted(); 1328 1329 return result; 1330 } 1331 1332 /** 1333 * @since 7.2 1334 */ 1335 @Override 1336 public List<DocumentRoute> getDocumentRelatedWorkflows(DocumentModel document, CoreSession session) { 1337 final String query = String.format( 1338 "SELECT * FROM %s WHERE docri:participatingDocuments/* = '%s' AND ecm:currentLifeCycleState = '%s'", 1339 DocumentRoutingConstants.DOCUMENT_ROUTE_DOCUMENT_TYPE, document.getId(), 1340 DocumentRouteElement.ElementLifeCycleState.running); 1341 DocumentModelList documentModelList = session.query(query.toString()); 1342 List<DocumentRoute> result = new ArrayList<DocumentRoute>(); 1343 for (DocumentModel documentModel : documentModelList) { 1344 result.add(documentModel.getAdapter(GraphRoute.class)); 1345 } 1346 return result; 1347 } 1348 1349 /** 1350 * @since 7.2 1351 */ 1352 @Override 1353 public List<DocumentRoute> getRunningWorkflowInstancesLaunchedByCurrentUser(CoreSession session) { 1354 return getRunningWorkflowInstancesLaunchedByCurrentUser(session, null); 1355 } 1356 1357 /** 1358 * @since 7.2 1359 */ 1360 @Override 1361 public List<DocumentRoute> getRunningWorkflowInstancesLaunchedByCurrentUser(CoreSession session, 1362 String worflowModelName) { 1363 final String query = String.format( 1364 "SELECT * FROM %s WHERE docri:initiator = '%s' AND ecm:currentLifeCycleState = '%s'", 1365 DocumentRoutingConstants.DOCUMENT_ROUTE_DOCUMENT_TYPE, session.getPrincipal().getName(), 1366 DocumentRouteElement.ElementLifeCycleState.running); 1367 DocumentModelList documentModelList = session.query(query.toString()); 1368 List<DocumentRoute> result = new ArrayList<DocumentRoute>(); 1369 for (DocumentModel documentModel : documentModelList) { 1370 final GraphRoute graphRoute = documentModel.getAdapter(GraphRoute.class); 1371 if (StringUtils.isNotBlank(worflowModelName)) { 1372 final String modelId = graphRoute.getModelId(); 1373 if (StringUtils.isNotBlank(modelId)) { 1374 DocumentRoute model = session.getDocument(new IdRef(modelId)).getAdapter(DocumentRoute.class); 1375 if (worflowModelName.equals(model.getName())) { 1376 result.add(graphRoute); 1377 } 1378 } 1379 } else { 1380 result.add(graphRoute); 1381 } 1382 } 1383 return result; 1384 } 1385 1386 /** 1387 * Returns true id the document route is a model, false if it is just an instance i.e. a running workflow. 1388 * 1389 * @since 7.2 1390 */ 1391 @Override 1392 public boolean isWorkflowModel(final DocumentRoute documentRoute) { 1393 return documentRoute.isValidated(); 1394 } 1395}