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