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