001/* 002 * Copyright 2016 Anyware Services 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 */ 016package org.ametys.plugins.workflow.support; 017 018import java.util.Arrays; 019import java.util.Collection; 020import java.util.Collections; 021import java.util.Properties; 022 023import javax.jcr.NamespaceRegistry; 024import javax.jcr.Repository; 025import javax.jcr.RepositoryException; 026import javax.jcr.Session; 027 028import org.apache.avalon.framework.activity.Disposable; 029import org.apache.avalon.framework.activity.Initializable; 030import org.apache.avalon.framework.component.Component; 031import org.apache.avalon.framework.context.ContextException; 032import org.apache.avalon.framework.context.Contextualizable; 033import org.apache.avalon.framework.service.ServiceException; 034import org.apache.avalon.framework.service.ServiceManager; 035import org.apache.avalon.framework.service.Serviceable; 036import org.apache.cocoon.Constants; 037import org.apache.cocoon.environment.Context; 038import org.apache.excalibur.source.SourceResolver; 039import org.slf4j.Logger; 040import org.slf4j.LoggerFactory; 041 042import org.ametys.core.user.CurrentUserProvider; 043import org.ametys.core.user.UserIdentity; 044import org.ametys.plugins.workflow.AbstractAmetysWorkflow; 045import org.ametys.plugins.workflow.SimpleConfiguration; 046import org.ametys.plugins.workflow.XmlWorkflowFactory; 047import org.ametys.plugins.workflow.repository.WorkflowAwareAmetysObject; 048import org.ametys.plugins.workflow.store.AbstractJackrabbitWorkflowStore; 049import org.ametys.plugins.workflow.store.AmetysObjectWorkflowStore; 050import org.ametys.plugins.workflow.store.GenericWorkflowStore; 051 052import com.opensymphony.workflow.FactoryException; 053import com.opensymphony.workflow.StoreException; 054import com.opensymphony.workflow.TypeResolver; 055import com.opensymphony.workflow.Workflow; 056import com.opensymphony.workflow.WorkflowContext; 057import com.opensymphony.workflow.WorkflowException; 058import com.opensymphony.workflow.loader.WorkflowFactory; 059import com.opensymphony.workflow.spi.WorkflowStore; 060 061/** 062 * Component able to provide several workflow objects. 063 * <ul> 064 * <li>The generic workflow: A standalone workflow which can be used for various 065 * purposes. All entries are stored under a global workflow root node in the JCR 066 * repository. 067 * <li>Dedicated ametys object workflows: A workflow specific to an ametys 068 * object. The workflow is stored under the ametys object node the JCR 069 * repository. 070 * </ul> 071 */ 072public class WorkflowProvider implements Component, Serviceable, Contextualizable, Initializable, Disposable 073{ 074 /** Avalon role. */ 075 public static final String ROLE = WorkflowProvider.class.getName(); 076 077 /** Logger available to subclasses. */ 078 protected static Logger _logger = LoggerFactory.getLogger(WorkflowProvider.class); 079 080 /** Service manager. */ 081 protected ServiceManager _manager; 082 083 /** Current user provider. */ 084 protected CurrentUserProvider _currentUserProvider; 085 086 /** The repository */ 087 protected Repository _repository; 088 089 /** The workflow helper */ 090 protected WorkflowHelper _workflowHelper; 091 092 /** Cocoon context */ 093 protected Context _cocoonContext; 094 095 /** Workflow context */ 096 protected WorkflowContext _workflowContext; 097 098 /** Workflow factory */ 099 protected WorkflowFactory _workflowFactory; 100 101 /** Type resolver */ 102 protected TypeResolver _typeResolver; 103 104 /** Generic workflow instance */ 105 protected GenericWorkflow _genericWorkflow; 106 107 /** The source resolver */ 108 protected SourceResolver _resolver; 109 110 public void contextualize(org.apache.avalon.framework.context.Context ctx) throws ContextException 111 { 112 _cocoonContext = (Context) ctx.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT); 113 } 114 115 public void service(ServiceManager manager) throws ServiceException 116 { 117 _manager = manager; 118 _currentUserProvider = (CurrentUserProvider) _manager.lookup(CurrentUserProvider.ROLE); 119 _repository = (Repository) _manager.lookup("javax.jcr.Repository"); 120 _workflowHelper = (WorkflowHelper) _manager.lookup(WorkflowHelper.ROLE); 121 _resolver = (SourceResolver) manager.lookup(SourceResolver.ROLE); 122 } 123 124 public void initialize() throws Exception 125 { 126 // Standard workflow context 127 _workflowContext = new WorkflowContext() 128 { 129 public String getCaller() 130 { 131 return UserIdentity.userIdentityToString(_currentUserProvider.getUser()); 132 } 133 134 public void setRollbackOnly() 135 { 136 // Transaction not supported 137 } 138 }; 139 140 // Standard workflow factory 141 _workflowFactory = new XmlWorkflowFactory(_cocoonContext.getRealPath("/WEB-INF/param")); 142 try 143 { 144 _workflowFactory.init(new Properties()); 145 _workflowFactory.initDone(); 146 } 147 catch (FactoryException e) 148 { 149 throw new RuntimeException("Unable to init workflow factory", e); 150 } 151 152 // Standard type resolver 153 _typeResolver = new AvalonTypeResolver(_manager); 154 155 // Register oswf namespaces and nodetypes 156 registerNamespace(); 157 } 158 159 public void dispose() 160 { 161 _manager = null; 162 _currentUserProvider = null; 163 _cocoonContext = null; 164 165 _workflowContext = null; 166 _workflowFactory = null; 167 _typeResolver = null; 168 _genericWorkflow = null; 169 } 170 171 /** 172 * Workflow factory getter 173 * @return The workflow factory 174 */ 175 WorkflowFactory getWorkflowFactory() 176 { 177 return _workflowFactory; 178 } 179 180 /** 181 * Register osworkflow namespace. 182 * @throws RepositoryException if an error occurs. 183 */ 184 protected void registerNamespace() throws RepositoryException 185 { 186 if (_logger.isDebugEnabled()) 187 { 188 _logger.debug("Registering namespaces"); 189 } 190 191 Session session = null; 192 try 193 { 194 session = _repository.login(); 195 196 NamespaceRegistry registry = session.getWorkspace().getNamespaceRegistry(); 197 Collection prefixes = Arrays.asList(registry.getPrefixes()); 198 199 if (!prefixes.contains(AbstractJackrabbitWorkflowStore.__NAMESPACE_PREFIX)) 200 { 201 registry.registerNamespace(AbstractJackrabbitWorkflowStore.__NAMESPACE_PREFIX, AbstractJackrabbitWorkflowStore.__NAMESPACE); 202 } 203 204 session.save(); 205 } 206 finally 207 { 208 if (session != null) 209 { 210 session.logout(); 211 } 212 } 213 } 214 215 /** 216 * Provides the generic workflow 217 * @return The generic workflow 218 */ 219 public Workflow getGenericWorkflow() 220 { 221 if (_genericWorkflow == null) 222 { 223 // Setup the generic workflow 224 GenericWorkflowStore store = _createGenericWorkflowStore(); 225 try 226 { 227 store.init(Collections.EMPTY_MAP); 228 } 229 catch (StoreException e) 230 { 231 throw new RuntimeException("Unable to init workflow store", e); 232 } 233 234 _genericWorkflow = new GenericWorkflow(_workflowHelper, _workflowContext); 235 236 _genericWorkflow.setConfiguration(new SimpleConfiguration(_workflowFactory, store)); 237 _genericWorkflow.setResolver(_typeResolver); 238 } 239 240 return _genericWorkflow; 241 } 242 243 /** 244 * Provide the generic workflow store 245 * @return The generic workflow store 246 */ 247 protected GenericWorkflowStore _createGenericWorkflowStore() 248 { 249 return new GenericWorkflowStore(_repository); 250 } 251 252 /** 253 * The Generic worklfow 254 */ 255 public static class GenericWorkflow extends AbstractAmetysWorkflow 256 { 257 GenericWorkflow(WorkflowHelper workflowHelper, WorkflowContext workflowContext) 258 { 259 super(workflowHelper, workflowContext); 260 } 261 } 262 263 /** 264 * Provide a local workflow to an Ametys object which do not preserve history on workflow complete 265 * Must be used to initialize a workflow that will create an ametys object. 266 * {@link AmetysObjectWorkflow#getAmetysObject()} could then be used to directly retrieves the ametys object. 267 * @return The local workflow 268 */ 269 public AmetysObjectWorkflow getAmetysObjectWorkflow() 270 { 271 return getAmetysObjectWorkflow(null, false); 272 } 273 274 /** 275 * Provide a local workflow to an Ametys object 276 * Must be used to initialize a workflow that will create an ametys object. 277 * {@link AmetysObjectWorkflow#getAmetysObject()} could then be used to directly retrieves the ametys object. 278 * @param preserveHistory true if the history should be preserve when workflow is complete 279 * @return The local workflow 280 */ 281 public AmetysObjectWorkflow getAmetysObjectWorkflow(boolean preserveHistory) 282 { 283 return getAmetysObjectWorkflow(null, preserveHistory); 284 } 285 286 /** 287 * Provide a local workflow to an Ametys object which do not preserve history on workflow complete 288 * @param ametysObject The ametys object (can be null in case of initialization) 289 * @return the local workflow to an Ametys object 290 */ 291 public AmetysObjectWorkflow getAmetysObjectWorkflow(WorkflowAwareAmetysObject ametysObject) 292 { 293 return getAmetysObjectWorkflow(ametysObject, false); 294 } 295 296 /** 297 * Provide a local workflow to an Ametys object 298 * @param ametysObject The ametys object (can be null in case of initialization) 299 * @param preserveHistory true if the history steps should be preserve when workflow is complete 300 * @return the local workflow to an Ametys object 301 */ 302 public AmetysObjectWorkflow getAmetysObjectWorkflow(WorkflowAwareAmetysObject ametysObject, boolean preserveHistory) 303 { 304 AmetysObjectWorkflowStore store = _createAmetysObjectWorkflowStore(ametysObject, preserveHistory); 305 306 try 307 { 308 store.init(Collections.EMPTY_MAP); 309 } 310 catch (StoreException e) 311 { 312 throw new RuntimeException("Unable to init workflow store", e); 313 } 314 315 AmetysObjectWorkflow ametysObjectWorkflow = new AmetysObjectWorkflow(_workflowHelper, _workflowContext, store); 316 317 ametysObjectWorkflow.setConfiguration(new SimpleConfiguration(_workflowFactory, store)); 318 ametysObjectWorkflow.setResolver(_typeResolver); 319 320 return ametysObjectWorkflow; 321 } 322 323 /** 324 * Provide an ametys object workflow store instance 325 * @param ametysObject The ametys object bound to this store (can be null in case of initialization) 326 * @param preserveHistory true if the history steps should be preserve when workflow is complete 327 * @return the local workflow store of an Ametys object 328 */ 329 protected AmetysObjectWorkflowStore _createAmetysObjectWorkflowStore(WorkflowAwareAmetysObject ametysObject, boolean preserveHistory) 330 { 331 return new AmetysObjectWorkflowStore(_repository, ametysObject, preserveHistory); 332 } 333 334 /** 335 * Local workflow to an ametys object 336 */ 337 public static class AmetysObjectWorkflow extends AbstractAmetysWorkflow 338 { 339 /** The ametys object */ 340 protected AmetysObjectWorkflowStore _workflowStore; 341 342 AmetysObjectWorkflow(WorkflowHelper workflowHelper, WorkflowContext workflowContext, AmetysObjectWorkflowStore store) 343 { 344 super(workflowHelper, workflowContext); 345 _workflowStore = store; 346 } 347 348 /** 349 * Ametys object getter 350 * @param <A> A WorkflowAwareAmetysObject class 351 * @return The ametys object 352 */ 353 @SuppressWarnings("unchecked") 354 public <A extends WorkflowAwareAmetysObject> A getAmetysObject() 355 { 356 return (A) _workflowStore.getAmetysObject(); 357 } 358 359 /** 360 * Delete a workflow instance 361 * @param wId The id of workflow instance 362 * @throws WorkflowException if failed to delete workflow instance 363 */ 364 public void removeWorkflow (long wId) throws WorkflowException 365 { 366 _workflowStore.removeEntry(wId); 367 } 368 } 369 370 /** 371 * Provides an external workflow 372 * @param workflowStoreRole The component role of the workflow store to use. 373 * @return The external workflow 374 */ 375 public Workflow getExternalWorkflow(String workflowStoreRole) 376 { 377 WorkflowStore workflowStore; 378 try 379 { 380 workflowStore = (WorkflowStore) _manager.lookup(workflowStoreRole); 381 } 382 catch (ServiceException e) 383 { 384 throw new RuntimeException(String.format("Unable to lookup workflow store for role '%s'.", workflowStoreRole), e); 385 } 386 387 ExternalWorkflow workflow = new ExternalWorkflow(_workflowHelper, _workflowContext, workflowStore); 388 389 workflow.setConfiguration(new SimpleConfiguration(_workflowFactory, workflowStore)); 390 workflow.setResolver(_typeResolver); 391 392 return workflow; 393 } 394 395 /** 396 * Provides an external workflow 397 * @param workflowStore An (already initialized) workflow store 398 * @return The external workflow 399 */ 400 public Workflow getExternalWorkflow(WorkflowStore workflowStore) 401 { 402 ExternalWorkflow workflow = new ExternalWorkflow(_workflowHelper, _workflowContext, workflowStore); 403 404 workflow.setConfiguration(new SimpleConfiguration(_workflowFactory, workflowStore)); 405 workflow.setResolver(_typeResolver); 406 407 return workflow; 408 } 409 410 /** 411 * The External worklfow 412 */ 413 public static class ExternalWorkflow extends AbstractAmetysWorkflow 414 { 415 /** The external workflow store */ 416 protected WorkflowStore _store; 417 418 ExternalWorkflow(WorkflowHelper workflowHelper, WorkflowContext workflowContext, WorkflowStore workflowStore) 419 { 420 super(workflowHelper, workflowContext); 421 _store = workflowStore; 422 } 423 } 424}