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