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