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.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.lang.StringUtils; 041import org.apache.commons.logging.Log; 042import org.apache.commons.logging.LogFactory; 043import org.nuxeo.ecm.core.api.Blob; 044import org.nuxeo.ecm.core.api.CoreSession; 045import org.nuxeo.ecm.core.api.DocumentModel; 046import org.nuxeo.ecm.core.api.DocumentModelList; 047import org.nuxeo.ecm.core.api.DocumentNotFoundException; 048import org.nuxeo.ecm.core.api.DocumentRef; 049import org.nuxeo.ecm.core.api.IdRef; 050import org.nuxeo.ecm.core.api.IterableQueryResult; 051import org.nuxeo.ecm.core.api.LifeCycleConstants; 052import org.nuxeo.ecm.core.api.NuxeoException; 053import org.nuxeo.ecm.core.api.NuxeoGroup; 054import org.nuxeo.ecm.core.api.NuxeoPrincipal; 055import org.nuxeo.ecm.core.api.PartialList; 056import org.nuxeo.ecm.core.api.PropertyException; 057import org.nuxeo.ecm.core.api.UnrestrictedSessionRunner; 058import org.nuxeo.ecm.core.api.impl.blob.URLBlob; 059import org.nuxeo.ecm.core.api.pathsegment.PathSegmentService; 060import org.nuxeo.ecm.core.api.security.ACE; 061import org.nuxeo.ecm.core.api.security.ACL; 062import org.nuxeo.ecm.core.api.security.ACP; 063import org.nuxeo.ecm.core.api.security.SecurityConstants; 064import org.nuxeo.ecm.core.api.security.impl.ACLImpl; 065import org.nuxeo.ecm.core.event.EventProducer; 066import org.nuxeo.ecm.core.event.impl.DocumentEventContext; 067import org.nuxeo.ecm.core.query.sql.NXQL; 068import org.nuxeo.ecm.core.repository.RepositoryInitializationHandler; 069import org.nuxeo.ecm.platform.filemanager.api.FileManager; 070import org.nuxeo.ecm.platform.query.api.PageProvider; 071import org.nuxeo.ecm.platform.query.api.PageProviderService; 072import org.nuxeo.ecm.platform.routing.api.DocumentRoute; 073import org.nuxeo.ecm.platform.routing.api.DocumentRouteElement; 074import org.nuxeo.ecm.platform.routing.api.DocumentRouteTableElement; 075import org.nuxeo.ecm.platform.routing.api.DocumentRoutingConstants; 076import org.nuxeo.ecm.platform.routing.api.DocumentRoutingPersister; 077import org.nuxeo.ecm.platform.routing.api.DocumentRoutingService; 078import org.nuxeo.ecm.platform.routing.api.LockableDocumentRoute; 079import org.nuxeo.ecm.platform.routing.api.RouteFolderElement; 080import org.nuxeo.ecm.platform.routing.api.RouteModelResourceType; 081import org.nuxeo.ecm.platform.routing.api.RouteTable; 082import org.nuxeo.ecm.platform.routing.api.exception.DocumentRouteAlredayLockedException; 083import org.nuxeo.ecm.platform.routing.api.exception.DocumentRouteException; 084import org.nuxeo.ecm.platform.routing.api.exception.DocumentRouteNotLockedException; 085import org.nuxeo.ecm.platform.routing.core.api.DocumentRoutingEngineService; 086import org.nuxeo.ecm.platform.routing.core.audit.RoutingAuditHelper; 087import org.nuxeo.ecm.platform.routing.core.listener.RouteModelsInitializator; 088import org.nuxeo.ecm.platform.routing.core.registries.RouteTemplateResourceRegistry; 089import org.nuxeo.ecm.platform.task.Task; 090import org.nuxeo.ecm.platform.task.TaskConstants; 091import org.nuxeo.ecm.platform.task.TaskEventNames; 092import org.nuxeo.ecm.platform.task.TaskService; 093import org.nuxeo.ecm.platform.task.core.helpers.TaskActorsHelper; 094import org.nuxeo.ecm.platform.task.core.service.TaskEventNotificationHelper; 095import org.nuxeo.ecm.platform.usermanager.UserManager; 096import org.nuxeo.runtime.api.Framework; 097import org.nuxeo.runtime.model.ComponentContext; 098import org.nuxeo.runtime.model.ComponentInstance; 099import org.nuxeo.runtime.model.DefaultComponent; 100import org.nuxeo.runtime.model.RuntimeContext; 101 102import com.google.common.cache.Cache; 103import com.google.common.cache.CacheBuilder; 104 105/** 106 * The implementation of the routing service. 107 */ 108public class DocumentRoutingServiceImpl extends DefaultComponent implements DocumentRoutingService { 109 110 private static Log log = LogFactory.getLog(DocumentRoutingServiceImpl.class); 111 112 /** Routes in any state (model or not). */ 113 private static final String AVAILABLE_ROUTES_QUERY = String.format("SELECT * FROM %s", 114 DocumentRoutingConstants.DOCUMENT_ROUTE_DOCUMENT_TYPE); 115 116 /** Routes Models. */ 117 private static final String AVAILABLE_ROUTES_MODEL_QUERY = String.format( 118 "SELECT * FROM %s WHERE ecm:currentLifeCycleState = '%s'", 119 DocumentRoutingConstants.DOCUMENT_ROUTE_DOCUMENT_TYPE, 120 DocumentRoutingConstants.DOCUMENT_ROUTE_MODEL_LIFECYCLESTATE); 121 122 /** Route models that have been validated. */ 123 private static final String ROUTE_MODEL_WITH_ID_QUERY = String.format( 124 "SELECT * FROM %s WHERE ecm:name = %%s AND ecm:currentLifeCycleState = 'validated' AND ecm:isCheckedInVersion = 0 AND ecm:isProxy = 0 ", 125 DocumentRoutingConstants.DOCUMENT_ROUTE_DOCUMENT_TYPE); 126 127 /** Route models that have been validated. */ 128 private static final String ROUTE_MODEL_DOC_ID_WITH_ID_QUERY = String.format( 129 "SELECT ecm:uuid FROM %s WHERE ecm:name = %%s AND ecm:currentLifeCycleState = 'validated' AND ecm:isCheckedInVersion = 0 AND ecm:isProxy = 0 ", 130 DocumentRoutingConstants.DOCUMENT_ROUTE_DOCUMENT_TYPE); 131 132 private static final String ORDERED_CHILDREN_QUERY = "SELECT * FROM Document WHERE" 133 + " ecm:parentId = '%s' AND ecm:isCheckedInVersion = 0 AND " 134 + "ecm:currentLifeCycleState != 'deleted' ORDER BY ecm:pos"; 135 136 public static final String CHAINS_TO_TYPE_XP = "chainsToType"; 137 138 public static final String PERSISTER_XP = "persister"; 139 140 /** 141 * @since 7.10 142 */ 143 public static final String ACTOR_ACE_CREATOR = "Workflow"; 144 145 // FIXME: use ContributionFragmentRegistry instances instead to handle hot 146 // reload 147 148 public static final String ROUTE_MODELS_IMPORTER_XP = "routeModelImporter"; 149 150 protected Map<String, String> typeToChain = new HashMap<>(); 151 152 protected Map<String, String> undoChainIdFromRunning = new HashMap<>(); 153 154 protected Map<String, String> undoChainIdFromDone = new HashMap<>(); 155 156 protected DocumentRoutingPersister persister; 157 158 protected RouteTemplateResourceRegistry routeResourcesRegistry = new RouteTemplateResourceRegistry(); 159 160 protected RepositoryInitializationHandler repositoryInitializationHandler; 161 162 private Cache<String, String> modelsChache; 163 164 @Override 165 public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 166 if (CHAINS_TO_TYPE_XP.equals(extensionPoint)) { 167 ChainToTypeMappingDescriptor desc = (ChainToTypeMappingDescriptor) contribution; 168 typeToChain.put(desc.getDocumentType(), desc.getChainId()); 169 undoChainIdFromRunning.put(desc.getDocumentType(), desc.getUndoChainIdFromRunning()); 170 undoChainIdFromDone.put(desc.getDocumentType(), desc.getUndoChainIdFromDone()); 171 } else if (PERSISTER_XP.equals(extensionPoint)) { 172 PersisterDescriptor des = (PersisterDescriptor) contribution; 173 try { 174 persister = des.getKlass().newInstance(); 175 } catch (ReflectiveOperationException e) { 176 throw new RuntimeException(e); 177 } 178 } else if (ROUTE_MODELS_IMPORTER_XP.equals(extensionPoint)) { 179 RouteModelResourceType res = (RouteModelResourceType) contribution; 180 registerRouteResource(res, contributor.getRuntimeContext()); 181 } 182 } 183 184 @Override 185 public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 186 if (contribution instanceof RouteModelResourceType) { 187 routeResourcesRegistry.removeContribution((RouteModelResourceType) contribution); 188 } 189 super.unregisterContribution(contribution, extensionPoint, contributor); 190 } 191 192 protected static void fireEvent(String eventName, Map<String, Serializable> eventProperties, DocumentRoute route, 193 CoreSession session) { 194 eventProperties.put(DocumentRoutingConstants.DOCUMENT_ELEMENT_EVENT_CONTEXT_KEY, route); 195 eventProperties.put(DocumentEventContext.CATEGORY_PROPERTY_KEY, DocumentRoutingConstants.ROUTING_CATEGORY); 196 DocumentEventContext envContext = new DocumentEventContext(session, session.getPrincipal(), 197 route.getDocument()); 198 envContext.setProperties(eventProperties); 199 EventProducer eventProducer = Framework.getService(EventProducer.class); 200 eventProducer.fireEvent(envContext.newEvent(eventName)); 201 } 202 203 @Override 204 public String createNewInstance(final String routeModelId, final List<String> docIds, 205 final Map<String, Serializable> map, CoreSession session, final boolean startInstance) { 206 final String initiator = session.getPrincipal().getName(); 207 final String res[] = new String[1]; 208 new UnrestrictedSessionRunner(session) { 209 210 protected DocumentRoute route; 211 212 @Override 213 public void run() { 214 String routeDocId = getRouteModelDocIdWithId(session, routeModelId); 215 DocumentModel model = session.getDocument(new IdRef(routeDocId)); 216 DocumentModel instance = persister.createDocumentRouteInstanceFromDocumentRouteModel(model, session); 217 route = instance.getAdapter(DocumentRoute.class); 218 route.setAttachedDocuments(docIds); 219 route.save(session); 220 Map<String, Serializable> props = new HashMap<>(); 221 props.put(DocumentRoutingConstants.INITIATOR_EVENT_CONTEXT_KEY, initiator); 222 fireEvent(DocumentRoutingConstants.Events.beforeRouteReady.name(), props); 223 route.setReady(session); 224 fireEvent(DocumentRoutingConstants.Events.afterRouteReady.name(), props); 225 route.save(session); 226 if (startInstance) { 227 fireEvent(DocumentRoutingConstants.Events.beforeRouteStart.name(), new HashMap<>()); 228 DocumentRoutingEngineService routingEngine = Framework.getService( 229 DocumentRoutingEngineService.class); 230 routingEngine.start(route, map, session); 231 fireEventAfterWorkflowStarted(route, session); 232 } 233 res[0] = instance.getId(); 234 } 235 236 protected void fireEvent(String eventName, Map<String, Serializable> eventProperties) { 237 DocumentRoutingServiceImpl.fireEvent(eventName, eventProperties, route, session); 238 } 239 240 }.runUnrestricted(); 241 242 return res[0]; 243 } 244 245 @Override 246 public String createNewInstance(String routeModelId, List<String> docIds, CoreSession session, 247 boolean startInstance) { 248 return createNewInstance(routeModelId, docIds, null, session, startInstance); 249 } 250 251 @Override 252 public DocumentRoute createNewInstance(DocumentRoute model, List<String> docIds, CoreSession session, 253 boolean startInstance) { 254 String id = createNewInstance(model.getDocument().getName(), docIds, session, startInstance); 255 return session.getDocument(new IdRef(id)).getAdapter(DocumentRoute.class); 256 } 257 258 @Override 259 @Deprecated 260 public DocumentRoute createNewInstance(DocumentRoute model, String documentId, CoreSession session, 261 boolean startInstance) { 262 return createNewInstance(model, Collections.singletonList(documentId), session, startInstance); 263 } 264 265 @Override 266 @Deprecated 267 public DocumentRoute createNewInstance(DocumentRoute model, List<String> documentIds, CoreSession session) { 268 return createNewInstance(model, documentIds, session, true); 269 } 270 271 @Override 272 @Deprecated 273 public DocumentRoute createNewInstance(DocumentRoute model, String documentId, CoreSession session) { 274 return createNewInstance(model, Collections.singletonList(documentId), session, true); 275 } 276 277 @Override 278 public void startInstance(final String routeInstanceId, final List<String> docIds, 279 final Map<String, Serializable> map, CoreSession session) { 280 new UnrestrictedSessionRunner(session) { 281 @Override 282 public void run() { 283 DocumentModel instance = session.getDocument(new IdRef(routeInstanceId)); 284 DocumentRoute route = instance.getAdapter(DocumentRoute.class); 285 if (docIds != null) { 286 route.setAttachedDocuments(docIds); 287 route.save(session); 288 } 289 fireEvent(DocumentRoutingConstants.Events.beforeRouteStart.name(), new HashMap<>(), route, session); 290 DocumentRoutingEngineService routingEngine = Framework.getService( 291 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, (NuxeoPrincipal) session.getPrincipal(), task, 377 comment, 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 doc = getFileManager().createDocumentFromBlob(session, blob, 704 persister.getParentFolderForDocumentRouteModels(session).getPathAsString(), true, file); 705 } catch (IOException e) { 706 throw new NuxeoException(e); 707 } 708 if (doc == null) { 709 throw new NuxeoException("Can not import document " + file); 710 } 711 // remove model from cache if any model with the same id existed 712 if (modelsChache != null) { 713 modelsChache.invalidate(doc.getName()); 714 } 715 716 return doc.getAdapter(DocumentRoute.class); 717 } 718 719 protected FileManager getFileManager() { 720 return Framework.getService(FileManager.class); 721 } 722 723 @Override 724 public void activate(ComponentContext context) { 725 super.activate(context); 726 modelsChache = CacheBuilder.newBuilder().maximumSize(100).expireAfterWrite(10, TimeUnit.MINUTES).build(); 727 repositoryInitializationHandler = new RouteModelsInitializator(); 728 repositoryInitializationHandler.install(); 729 } 730 731 @Override 732 public void deactivate(ComponentContext context) { 733 super.deactivate(context); 734 if (repositoryInitializationHandler != null) { 735 repositoryInitializationHandler.uninstall(); 736 } 737 } 738 739 @Override 740 public List<URL> getRouteModelTemplateResources() { 741 List<URL> urls = new ArrayList<>(); 742 for (URL url : routeResourcesRegistry.getRouteModelTemplateResources()) { 743 urls.add(url); // test contrib parsing and deployment 744 } 745 return urls; 746 } 747 748 @SuppressWarnings("unchecked") 749 @Override 750 public List<DocumentModel> searchRouteModels(CoreSession session, String searchString) { 751 List<DocumentModel> allRouteModels = new ArrayList<>(); 752 PageProviderService pageProviderService = Framework.getService(PageProviderService.class); 753 Map<String, Serializable> props = new HashMap<>(); 754 props.put(MAX_RESULTS_PROPERTY, PAGE_SIZE_RESULTS_KEY); 755 props.put(CORE_SESSION_PROPERTY, (Serializable) session); 756 PageProvider<DocumentModel> pageProvider; 757 if (StringUtils.isEmpty(searchString)) { 758 pageProvider = (PageProvider<DocumentModel>) pageProviderService.getPageProvider( 759 DOC_ROUTING_SEARCH_ALL_ROUTE_MODELS_PROVIDER_NAME, null, null, 0L, props); 760 } else { 761 pageProvider = (PageProvider<DocumentModel>) pageProviderService.getPageProvider( 762 DOC_ROUTING_SEARCH_ROUTE_MODELS_WITH_TITLE_PROVIDER_NAME, null, null, 0L, props, 763 searchString + '%'); 764 } 765 allRouteModels.addAll(pageProvider.getCurrentPage()); 766 while (pageProvider.isNextPageAvailable()) { 767 pageProvider.nextPage(); 768 allRouteModels.addAll(pageProvider.getCurrentPage()); 769 } 770 return allRouteModels; 771 } 772 773 @Override 774 public void registerRouteResource(RouteModelResourceType res, RuntimeContext context) { 775 if (res.getPath() != null && res.getId() != null) { 776 if (routeResourcesRegistry.getResource(res.getId()) != null) { 777 routeResourcesRegistry.removeContribution(res); 778 } 779 if (res.getUrl() == null) { 780 res.setUrl(getUrlFromPath(res, context)); 781 } 782 routeResourcesRegistry.addContribution(res); 783 } 784 } 785 786 protected URL getUrlFromPath(RouteModelResourceType res, RuntimeContext extensionContext) { 787 String path = res.getPath(); 788 if (path == null) { 789 return null; 790 } 791 URL url; 792 try { 793 url = new URL(path); 794 } catch (MalformedURLException e) { 795 url = extensionContext.getLocalResource(path); 796 if (url == null) { 797 url = extensionContext.getResource(path); 798 } 799 if (url == null) { 800 url = res.getClass().getResource(path); 801 } 802 } 803 return url; 804 } 805 806 @Override 807 public DocumentRoute getRouteModelWithId(CoreSession session, String id) { 808 String routeDocModelId = getRouteModelDocIdWithId(session, id); 809 DocumentModel routeDoc = session.getDocument(new IdRef(routeDocModelId)); 810 return routeDoc.getAdapter(DocumentRoute.class); 811 } 812 813 @Override 814 public String getRouteModelDocIdWithId(CoreSession session, String id) { 815 if (modelsChache != null) { 816 String routeDocId = modelsChache.getIfPresent(id); 817 if (routeDocId != null) { 818 return routeDocId; 819 } 820 } 821 String query = String.format(ROUTE_MODEL_DOC_ID_WITH_ID_QUERY, NXQL.escapeString(id)); 822 List<String> routeIds = new ArrayList<>(); 823 try (IterableQueryResult results = session.queryAndFetch(query, "NXQL")) { 824 if (results.size() == 0) { 825 throw new NuxeoException("No route found for id: " + id); 826 } 827 if (results.size() != 1) { 828 throw new NuxeoException("More than one route model found with id: " + id); 829 } 830 for (Map<String, Serializable> map : results) { 831 routeIds.add(map.get("ecm:uuid").toString()); 832 } 833 } 834 String routeDocId = routeIds.get(0); 835 if (modelsChache == null) { 836 modelsChache = CacheBuilder.newBuilder().maximumSize(100).expireAfterWrite(10, TimeUnit.MINUTES).build(); 837 } 838 modelsChache.put(id, routeDocId); 839 return routeDocId; 840 } 841 842 @Override 843 @Deprecated 844 public void makeRoutingTasks(CoreSession coreSession, final List<Task> tasks) { 845 new UnrestrictedSessionRunner(coreSession) { 846 @Override 847 public void run() { 848 for (Task task : tasks) { 849 DocumentModel taskDoc = task.getDocument(); 850 taskDoc.addFacet(DocumentRoutingConstants.ROUTING_TASK_FACET_NAME); 851 session.saveDocument(taskDoc); 852 } 853 } 854 }.runUnrestricted(); 855 } 856 857 @Override 858 public void endTask(CoreSession session, Task task, Map<String, Object> data, String status) { 859 String comment = (String) data.get(GraphNode.NODE_VARIABLE_COMMENT); 860 TaskService taskService = Framework.getService(TaskService.class); 861 taskService.endTask(session, (NuxeoPrincipal) session.getPrincipal(), task, comment, null, false); 862 863 Map<String, String> taskVariables = task.getVariables(); 864 String routeInstanceId = taskVariables.get(DocumentRoutingConstants.TASK_ROUTE_INSTANCE_DOCUMENT_ID_KEY); 865 if (StringUtils.isEmpty(routeInstanceId)) { 866 throw new DocumentRouteException("Can not resume workflow, no related route"); 867 } 868 completeTask(routeInstanceId, null, task, data, status, session); 869 } 870 871 @Override 872 public List<DocumentModel> getWorkflowInputDocuments(CoreSession session, Task task) { 873 String routeInstanceId; 874 try { 875 routeInstanceId = task.getProcessId(); 876 } catch (PropertyException e) { 877 throw new DocumentRouteException("Can not get the related workflow instance"); 878 } 879 if (StringUtils.isEmpty(routeInstanceId)) { 880 throw new DocumentRouteException("Can not get the related workflow instance"); 881 } 882 DocumentModel routeDoc; 883 try { 884 routeDoc = session.getDocument(new IdRef(routeInstanceId)); 885 } catch (DocumentNotFoundException e) { 886 throw new DocumentRouteException("No workflow with the id:" + routeInstanceId); 887 } 888 DocumentRoute route = routeDoc.getAdapter(DocumentRoute.class); 889 return route.getAttachedDocuments(session); 890 } 891 892 @Override 893 public void grantPermissionToTaskAssignees(CoreSession session, String permission, List<DocumentModel> docs, 894 Task task) { 895 setAclForActors(session, getRoutingACLName(task), permission, docs, task.getActors()); 896 } 897 898 @Override 899 public void grantPermissionToTaskDelegatedActors(CoreSession session, String permission, List<DocumentModel> docs, 900 Task task) { 901 setAclForActors(session, getDelegationACLName(task), permission, docs, task.getDelegatedActors()); 902 } 903 904 @Override 905 public void removePermissionFromTaskAssignees(CoreSession session, final List<DocumentModel> docs, Task task) { 906 final String aclName = getRoutingACLName(task); 907 new UnrestrictedSessionRunner(session) { 908 @Override 909 public void run() { 910 for (DocumentModel doc : docs) { 911 ACP acp = doc.getACP(); 912 acp.removeACL(aclName); 913 doc.setACP(acp, true); 914 session.saveDocument(doc); 915 } 916 } 917 }.runUnrestricted(); 918 } 919 920 /** 921 * @since 7.4 922 */ 923 @Override 924 public void removePermissionsForTaskActors(CoreSession session, final List<DocumentModel> docs, String taskId) { 925 final String aclRoutingName = getRoutingACLName(taskId); 926 final String aclDelegationName = getDelegationACLName(taskId); 927 new UnrestrictedSessionRunner(session) { 928 @Override 929 public void run() { 930 for (DocumentModel doc : docs) { 931 ACP acp = doc.getACP(); 932 acp.removeACL(aclRoutingName); 933 acp.removeACL(aclDelegationName); 934 doc.setACP(acp, true); 935 session.saveDocument(doc); 936 } 937 } 938 }.runUnrestricted(); 939 } 940 941 @Override 942 public void removePermissionsForTaskActors(CoreSession session, final List<DocumentModel> docs, Task task) { 943 removePermissionsForTaskActors(session, docs, task.getId()); 944 } 945 946 /** 947 * Finds an ACL name specific to the task (there may be several tasks applying permissions to the same document). 948 */ 949 protected static String getRoutingACLName(Task task) { 950 return getRoutingACLName(task.getId()); 951 } 952 953 /** 954 * @since 7.4 955 */ 956 protected static String getRoutingACLName(String taskId) { 957 return DocumentRoutingConstants.DOCUMENT_ROUTING_ACL + '/' + taskId; 958 } 959 960 protected static String getDelegationACLName(Task task) { 961 return getDelegationACLName(task.getId()); 962 } 963 964 /** 965 * @since 7.4 966 */ 967 protected static String getDelegationACLName(String taskId) { 968 return DocumentRoutingConstants.DOCUMENT_ROUTING_DELEGATION_ACL + '/' + taskId; 969 } 970 971 /** 972 * @since 7.1 973 */ 974 private final class WfCleaner extends UnrestrictedSessionRunner { 975 976 private static final String WORKFLOWS_QUERY = "SELECT ecm:uuid FROM DocumentRoute WHERE ecm:currentLifeCycleState IN ('done', 'canceled')"; 977 978 private static final String TASKS_QUERY = "SELECT ecm:uuid FROM Document WHERE ecm:mixinType = 'Task' AND nt:processId = '%s'"; 979 980 private final int limit; 981 982 private int numberOfCleanedUpWorkflows = 0; 983 984 private WfCleaner(String repositoryName, int limit) { 985 super(repositoryName); 986 this.limit = limit; 987 } 988 989 @Override 990 public void run() { 991 PartialList<Map<String, Serializable>> workflows = session.queryProjection(WORKFLOWS_QUERY, limit, 0); 992 numberOfCleanedUpWorkflows = workflows.size(); 993 994 for (Map<String, Serializable> workflow : workflows) { 995 String routeDocId = workflow.get(ECM_UUID).toString(); 996 final String associatedTaskQuery = String.format(TASKS_QUERY, routeDocId); 997 session.queryProjection(associatedTaskQuery, 0, 0) 998 .stream() 999 .map(task -> new IdRef(task.get(ECM_UUID).toString())) 1000 .forEach(session::removeDocument); 1001 session.removeDocument(new IdRef(routeDocId)); 1002 } 1003 } 1004 1005 public int getNumberOfCleanedUpWf() { 1006 return numberOfCleanedUpWorkflows; 1007 } 1008 } 1009 1010 class UnrestrictedQueryRunner extends UnrestrictedSessionRunner { 1011 1012 String query; 1013 1014 DocumentModelList docs; 1015 1016 protected UnrestrictedQueryRunner(CoreSession session, String query) { 1017 super(session); 1018 this.query = query; 1019 } 1020 1021 @Override 1022 public void run() { 1023 docs = session.query(query); 1024 for (DocumentModel documentModel : docs) { 1025 documentModel.detach(true); 1026 } 1027 } 1028 1029 public DocumentModelList runQuery() { 1030 runUnrestricted(); 1031 return docs; 1032 } 1033 } 1034 1035 /** 1036 * Cancel the workflow instance if all its attached document don't exist anymore. If the workflow is cancelled then 1037 * the isWowkflowCanceled is set to true. 1038 * 1039 * @since 8.4 1040 */ 1041 public static class AttachedDocumentsChecker extends UnrestrictedSessionRunner { 1042 1043 String workflowInstanceId; 1044 1045 boolean isWorkflowCanceled; 1046 1047 protected AttachedDocumentsChecker(CoreSession session, String workflowInstanceId) { 1048 super(session); 1049 this.workflowInstanceId = workflowInstanceId; 1050 } 1051 1052 @Override 1053 public void run() { 1054 DocumentModel routeDoc = session.getDocument(new IdRef(workflowInstanceId)); 1055 DocumentRoute routeInstance = routeDoc.getAdapter(DocumentRoute.class); 1056 List<String> attachedDocumentIds = routeInstance.getAttachedDocuments(); 1057 if (attachedDocumentIds.isEmpty()) { 1058 return; 1059 } 1060 for (String attachedDocumentId : attachedDocumentIds) { 1061 if (session.exists(new IdRef(attachedDocumentId))) { 1062 return; 1063 } 1064 } 1065 DocumentRoutingEngineService routingEngine = Framework.getService(DocumentRoutingEngineService.class); 1066 routingEngine.cancel(routeInstance, session); 1067 isWorkflowCanceled = true; 1068 } 1069 } 1070 1071 @Override 1072 public void finishTask(CoreSession session, DocumentRoute route, Task task, boolean delete) 1073 throws DocumentRouteException { 1074 DocumentModelList docs = route.getAttachedDocuments(session); 1075 try { 1076 removePermissionsForTaskActors(session, docs, task); 1077 // delete task 1078 if (delete) { 1079 session.removeDocument(new IdRef(task.getId())); 1080 } 1081 } catch (DocumentNotFoundException e) { 1082 throw new DocumentRouteException("Cannot finish task", e); 1083 } 1084 } 1085 1086 @Override 1087 public void cancelTask(CoreSession session, final String taskId) throws DocumentRouteException { 1088 new UnrestrictedSessionRunner(session) { 1089 @Override 1090 public void run() { 1091 DocumentModel taskDoc = session.getDocument(new IdRef(taskId)); 1092 Task task = taskDoc.getAdapter(Task.class); 1093 if (task == null) { 1094 throw new DocumentRouteException("Invalid taskId: " + taskId); 1095 } 1096 1097 if (!task.isOpened()) { 1098 log.info("Can not cancel task " + taskId + "as is not open"); 1099 return; 1100 } 1101 task.cancel(session); 1102 1103 // if the task was created by an workflow , update info 1104 String routeId = task.getProcessId(); 1105 if (routeId != null) { 1106 DocumentModel routeDoc = session.getDocument(new IdRef(routeId)); 1107 GraphRoute routeInstance = routeDoc.getAdapter(GraphRoute.class); 1108 if (routeInstance == null) { 1109 throw new DocumentRouteException("Invalid routeInstanceId: " + routeId); 1110 } 1111 1112 DocumentModelList docs = routeInstance.getAttachedDocumentModels(); 1113 removePermissionsForTaskActors(session, docs, task); 1114 // task is considered processed with the status "null" 1115 // when 1116 // is 1117 // canceled 1118 updateTaskInfo(session, routeInstance, task, null); 1119 } 1120 session.saveDocument(task.getDocument()); 1121 1122 } 1123 }.runUnrestricted(); 1124 } 1125 1126 protected void updateTaskInfo(CoreSession session, GraphRoute graph, Task task, String status) { 1127 String nodeId = task.getVariable(DocumentRoutingConstants.TASK_NODE_ID_KEY); 1128 if (StringUtils.isEmpty(nodeId)) { 1129 throw new DocumentRouteException("No nodeId found on task: " + task.getId()); 1130 } 1131 GraphNode node = graph.getNode(nodeId); 1132 1133 NuxeoPrincipal principal = (NuxeoPrincipal) session.getPrincipal(); 1134 String actor = principal.getActingUser(); 1135 node.updateTaskInfo(task.getId(), true, status, actor, null); 1136 } 1137 1138 @Override 1139 public void reassignTask(CoreSession session, final String taskId, final List<String> actors, final String comment) 1140 throws DocumentRouteException { 1141 new UnrestrictedSessionRunner(session) { 1142 1143 @Override 1144 public void run() { 1145 DocumentModel taskDoc = session.getDocument(new IdRef(taskId)); 1146 Task task = taskDoc.getAdapter(Task.class); 1147 if (task == null) { 1148 throw new DocumentRouteException("Invalid taskId: " + taskId); 1149 } 1150 if (!task.isOpened()) { 1151 throw new DocumentRouteException("Task " + taskId + " is not opened, can not reassign it"); 1152 } 1153 String routeId = task.getProcessId(); 1154 if (routeId != null) { 1155 DocumentModel routeDoc = session.getDocument(new IdRef(routeId)); 1156 GraphRoute routeInstance = routeDoc.getAdapter(GraphRoute.class); 1157 if (routeInstance == null) { 1158 throw new DocumentRouteException( 1159 "Invalid routeInstanceId: " + routeId + " referenced by the task " + taskId); 1160 } 1161 GraphNode node = routeInstance.getNode(task.getType()); 1162 if (node == null) { 1163 throw new DocumentRouteException( 1164 "Invalid node " + routeId + " referenced by the task " + taskId); 1165 } 1166 if (!node.allowTaskReassignment()) { 1167 throw new DocumentRouteException("Task " + taskId + " can not be reassigned. Node " 1168 + node.getId() + " doesn't allow reassignment."); 1169 } 1170 DocumentModelList docs = routeInstance.getAttachedDocumentModels(); 1171 // remove permissions on the document following the 1172 // workflow for the current assignees 1173 removePermissionFromTaskAssignees(session, docs, task); 1174 Framework.getService(TaskService.class).reassignTask(session, taskId, actors, comment); 1175 // refresh task 1176 task.getDocument().refresh(); 1177 // grant permission to the new assignees 1178 grantPermissionToTaskAssignees(session, node.getTaskAssigneesPermission(), docs, task); 1179 1180 // Audit task reassignment 1181 Map<String, Serializable> eventProperties = new HashMap<>(); 1182 eventProperties.put(DocumentEventContext.CATEGORY_PROPERTY_KEY, 1183 DocumentRoutingConstants.ROUTING_CATEGORY); 1184 eventProperties.put("taskName", task.getName()); 1185 eventProperties.put("actors", (Serializable) actors); 1186 eventProperties.put("modelId", routeInstance.getModelId()); 1187 eventProperties.put("modelName", routeInstance.getModelName()); 1188 eventProperties.put(RoutingAuditHelper.WORKFLOW_INITATIOR, routeInstance.getInitiator()); 1189 eventProperties.put(RoutingAuditHelper.TASK_ACTOR, 1190 ((NuxeoPrincipal) session.getPrincipal()).getActingUser()); 1191 eventProperties.put("comment", comment); 1192 // compute duration since workflow started 1193 long timeSinceWfStarted = RoutingAuditHelper.computeDurationSinceWfStarted(task.getProcessId()); 1194 if (timeSinceWfStarted >= 0) { 1195 eventProperties.put(RoutingAuditHelper.TIME_SINCE_WF_STARTED, timeSinceWfStarted); 1196 } 1197 // compute duration since task started 1198 long timeSinceTaskStarted = RoutingAuditHelper.computeDurationSinceTaskStarted(task.getId()); 1199 if (timeSinceWfStarted >= 0) { 1200 eventProperties.put(RoutingAuditHelper.TIME_SINCE_TASK_STARTED, timeSinceTaskStarted); 1201 } 1202 DocumentEventContext envContext = new DocumentEventContext(session, session.getPrincipal(), 1203 task.getDocument()); 1204 envContext.setProperties(eventProperties); 1205 EventProducer eventProducer = Framework.getService(EventProducer.class); 1206 eventProducer.fireEvent( 1207 envContext.newEvent(DocumentRoutingConstants.Events.afterWorkflowTaskReassigned.name())); 1208 } 1209 } 1210 }.runUnrestricted(); 1211 } 1212 1213 @Override 1214 public void delegateTask(CoreSession session, final String taskId, final List<String> delegatedActors, 1215 final String comment) throws DocumentRouteException { 1216 new UnrestrictedSessionRunner(session) { 1217 1218 @Override 1219 public void run() { 1220 DocumentModel taskDoc = session.getDocument(new IdRef(taskId)); 1221 Task task = taskDoc.getAdapter(Task.class); 1222 if (task == null) { 1223 throw new DocumentRouteException("Invalid taskId: " + taskId); 1224 } 1225 String routeId = task.getProcessId(); 1226 if (routeId != null) { 1227 DocumentModel routeDoc = session.getDocument(new IdRef(routeId)); 1228 GraphRoute routeInstance = routeDoc.getAdapter(GraphRoute.class); 1229 if (routeInstance == null) { 1230 throw new DocumentRouteException( 1231 "Invalid routeInstanceId: " + routeId + " referenced by the task " + taskId); 1232 } 1233 GraphNode node = routeInstance.getNode(task.getType()); 1234 if (node == null) { 1235 throw new DocumentRouteException( 1236 "Invalid node " + routeId + " referenced by the task " + taskId); 1237 } 1238 DocumentModelList docs = routeInstance.getAttachedDocumentModels(); 1239 Framework.getService(TaskService.class).delegateTask(session, taskId, delegatedActors, 1240 comment); 1241 // refresh task 1242 task.getDocument().refresh(); 1243 // grant permission to the new assignees 1244 grantPermissionToTaskDelegatedActors(session, node.getTaskAssigneesPermission(), docs, task); 1245 1246 // Audit task delegation 1247 Map<String, Serializable> eventProperties = new HashMap<>(); 1248 eventProperties.put(DocumentEventContext.CATEGORY_PROPERTY_KEY, 1249 DocumentRoutingConstants.ROUTING_CATEGORY); 1250 eventProperties.put("taskName", task.getName()); 1251 eventProperties.put("delegatedActors", (Serializable) delegatedActors); 1252 eventProperties.put("modelId", routeInstance.getModelId()); 1253 eventProperties.put("modelName", routeInstance.getModelName()); 1254 eventProperties.put(RoutingAuditHelper.WORKFLOW_INITATIOR, routeInstance.getInitiator()); 1255 eventProperties.put(RoutingAuditHelper.TASK_ACTOR, 1256 ((NuxeoPrincipal) session.getPrincipal()).getActingUser()); 1257 eventProperties.put("comment", comment); 1258 1259 // compute duration since workflow started 1260 long timeSinceWfStarted = RoutingAuditHelper.computeDurationSinceWfStarted(task.getProcessId()); 1261 if (timeSinceWfStarted >= 0) { 1262 eventProperties.put(RoutingAuditHelper.TIME_SINCE_WF_STARTED, timeSinceWfStarted); 1263 } 1264 // compute duration since task started 1265 long timeSinceTaskStarted = RoutingAuditHelper.computeDurationSinceTaskStarted(task.getId()); 1266 if (timeSinceWfStarted >= 0) { 1267 eventProperties.put(RoutingAuditHelper.TIME_SINCE_TASK_STARTED, timeSinceTaskStarted); 1268 } 1269 1270 DocumentEventContext envContext = new DocumentEventContext(session, session.getPrincipal(), 1271 task.getDocument()); 1272 envContext.setProperties(eventProperties); 1273 EventProducer eventProducer = Framework.getService(EventProducer.class); 1274 eventProducer.fireEvent( 1275 envContext.newEvent(DocumentRoutingConstants.Events.afterWorkflowTaskDelegated.name())); 1276 } 1277 } 1278 }.runUnrestricted(); 1279 } 1280 1281 protected void setAclForActors(CoreSession session, final String aclName, final String permission, 1282 final List<DocumentModel> docs, List<String> actors) { 1283 final List<String> actorIds = new ArrayList<>(); 1284 for (String actor : actors) { 1285 if (actor.startsWith(NuxeoPrincipal.PREFIX)) { 1286 actorIds.add(actor.substring(NuxeoPrincipal.PREFIX.length())); 1287 } else if (actor.startsWith(NuxeoGroup.PREFIX)) { 1288 actorIds.add(actor.substring(NuxeoGroup.PREFIX.length())); 1289 } else { 1290 actorIds.add(actor); 1291 } 1292 } 1293 new UnrestrictedSessionRunner(session) { 1294 @Override 1295 public void run() { 1296 for (DocumentModel doc : docs) { 1297 ACP acp = doc.getACP(); 1298 acp.removeACL(aclName); 1299 ACL acl = new ACLImpl(aclName); 1300 for (String actorId : actorIds) { 1301 acl.add(ACE.builder(actorId, permission).creator(ACTOR_ACE_CREATOR).build()); 1302 } 1303 acp.addACL(0, acl); // add first to get before blocks 1304 doc.setACP(acp, true); 1305 session.saveDocument(doc); 1306 } 1307 } 1308 1309 }.runUnrestricted(); 1310 } 1311 1312 @Override 1313 public void cleanupDoneAndCanceledRouteInstances(final String reprositoryName, final int limit) { 1314 doCleanupDoneAndCanceledRouteInstances(reprositoryName, limit); 1315 } 1316 1317 @Override 1318 public int doCleanupDoneAndCanceledRouteInstances(final String reprositoryName, final int limit) { 1319 WfCleaner unrestrictedSessionRunner = new WfCleaner(reprositoryName, limit); 1320 unrestrictedSessionRunner.runUnrestricted(); 1321 return unrestrictedSessionRunner.getNumberOfCleanedUpWf(); 1322 } 1323 1324 @Override 1325 public void invalidateRouteModelsCache() { 1326 modelsChache.invalidateAll(); 1327 } 1328 1329 /** 1330 * @since 7.2 1331 */ 1332 @Override 1333 public List<Task> getTasks(final DocumentModel document, String actorId, String workflowInstanceId, 1334 final String worflowModelName, CoreSession session) { 1335 StringBuilder query = new StringBuilder( 1336 String.format("SELECT * FROM Document WHERE ecm:mixinType = '%s' AND ecm:currentLifeCycleState = '%s'", 1337 TaskConstants.TASK_FACET_NAME, TaskConstants.TASK_OPENED_LIFE_CYCLE_STATE)); 1338 if (StringUtils.isNotBlank(actorId)) { 1339 List<String> actors = new ArrayList<String>(); 1340 UserManager userManager = Framework.getService(UserManager.class); 1341 NuxeoPrincipal principal = userManager.getPrincipal(actorId); 1342 if (principal != null) { 1343 for (String actor : TaskActorsHelper.getTaskActors(principal)) { 1344 actors.add(NXQL.escapeString(actor)); 1345 } 1346 } else { 1347 actors.add(NXQL.escapeString(actorId)); 1348 } 1349 String actorsParam = StringUtils.join(actors, ", "); 1350 query.append(String.format(" AND (nt:actors/* IN (%s) OR nt:delegatedActors/* IN (%s))", actorsParam, 1351 actorsParam)); 1352 } 1353 if (StringUtils.isNotBlank(workflowInstanceId)) { 1354 query.append(String.format(" AND nt:processId = %s", NXQL.escapeString(workflowInstanceId))); 1355 } 1356 if (document != null) { 1357 query.append(String.format(" AND nt:targetDocumentsIds = '%s'", document.getId())); 1358 } 1359 query.append(String.format(" ORDER BY %s ASC", TaskConstants.TASK_DUE_DATE_PROPERTY_NAME)); 1360 final DocumentModelList documentModelList = session.query(query.toString()); 1361 final List<Task> result = new ArrayList<>(); 1362 1363 // User does not necessary have READ on the workflow instance 1364 new UnrestrictedSessionRunner(session) { 1365 1366 @Override 1367 public void run() { 1368 for (DocumentModel documentModel : documentModelList) { 1369 final Task task = documentModel.getAdapter(Task.class); 1370 if (StringUtils.isNotBlank(worflowModelName)) { 1371 1372 final String processId = task.getProcessId(); 1373 if (processId != null && session.exists(new IdRef(processId))) { 1374 final DocumentRoute routeInstance = session.getDocument(new IdRef(processId)) 1375 .getAdapter(DocumentRoute.class); 1376 if (routeInstance != null) { 1377 final String routeInstanceName = routeInstance.getName(); 1378 if (routeInstanceName != null && (routeInstanceName.equals(worflowModelName) 1379 || routeInstanceName.matches("^(" + worflowModelName + ")\\.\\d+"))) { 1380 result.add(task); 1381 } 1382 } 1383 } 1384 } else { 1385 result.add(task); 1386 } 1387 } 1388 } 1389 }.runUnrestricted(); 1390 1391 return result; 1392 } 1393 1394 /** 1395 * @since 7.2 1396 */ 1397 @Override 1398 public List<DocumentRoute> getDocumentRelatedWorkflows(DocumentModel document, CoreSession session) { 1399 final String query = String.format( 1400 "SELECT * FROM %s WHERE docri:participatingDocuments/* = '%s' AND ecm:currentLifeCycleState = '%s'", 1401 DocumentRoutingConstants.DOCUMENT_ROUTE_DOCUMENT_TYPE, document.getId(), 1402 DocumentRouteElement.ElementLifeCycleState.running); 1403 DocumentModelList documentModelList = session.query(query); 1404 List<DocumentRoute> result = new ArrayList<>(); 1405 for (DocumentModel documentModel : documentModelList) { 1406 result.add(documentModel.getAdapter(GraphRoute.class)); 1407 } 1408 return result; 1409 } 1410 1411 /** 1412 * @since 7.2 1413 */ 1414 @Override 1415 public List<DocumentRoute> getRunningWorkflowInstancesLaunchedByCurrentUser(CoreSession session) { 1416 return getRunningWorkflowInstancesLaunchedByCurrentUser(session, null); 1417 } 1418 1419 /** 1420 * @since 7.2 1421 */ 1422 @Override 1423 public List<DocumentRoute> getRunningWorkflowInstancesLaunchedByCurrentUser(CoreSession session, 1424 String worflowModelName) { 1425 final String query = String.format( 1426 "SELECT * FROM %s WHERE docri:initiator = '%s' AND ecm:currentLifeCycleState = '%s'", 1427 DocumentRoutingConstants.DOCUMENT_ROUTE_DOCUMENT_TYPE, session.getPrincipal().getName(), 1428 DocumentRouteElement.ElementLifeCycleState.running); 1429 DocumentModelList documentModelList = session.query(query); 1430 List<DocumentRoute> result = new ArrayList<>(); 1431 for (DocumentModel documentModel : documentModelList) { 1432 final GraphRoute graphRoute = documentModel.getAdapter(GraphRoute.class); 1433 if (StringUtils.isNotBlank(worflowModelName)) { 1434 final String modelId = graphRoute.getModelId(); 1435 if (StringUtils.isNotBlank(modelId)) { 1436 DocumentRoute model = session.getDocument(new IdRef(modelId)).getAdapter(DocumentRoute.class); 1437 if (worflowModelName.equals(model.getName())) { 1438 result.add(graphRoute); 1439 } 1440 } 1441 } else { 1442 result.add(graphRoute); 1443 } 1444 } 1445 return result; 1446 } 1447 1448 /** 1449 * Returns true id the document route is a model, false if it is just an instance i.e. a running workflow. 1450 * 1451 * @since 7.2 1452 */ 1453 @Override 1454 public boolean isWorkflowModel(final DocumentRoute documentRoute) { 1455 return documentRoute.isValidated(); 1456 } 1457}