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