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