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