blob: bec5649a64fdebeea49000aade93f8de4afae586 [file] [log] [blame]
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.wiki.workflow;
import org.apache.wiki.WikiEngine;
import org.apache.wiki.WikiSession;
import org.apache.wiki.api.exceptions.WikiException;
import org.apache.wiki.auth.acl.UnresolvedPrincipal;
import org.apache.wiki.event.WikiEvent;
import org.apache.wiki.event.WikiEventListener;
import org.apache.wiki.event.WorkflowEvent;
import java.security.Principal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* <p>
* Monitor class that tracks running Workflows. The WorkflowManager also keeps track of the names of
* users or groups expected to approve particular Workflows.
* </p>
*/
public class DefaultWorkflowManager implements WorkflowManager, WikiEventListener {
private final DecisionQueue m_queue = new DecisionQueue();
private final Set<Workflow> m_workflows;
private final Map<String, Principal> m_approvers;
private final List<Workflow> m_completed;
private WikiEngine m_engine = null;
/**
* Constructs a new WorkflowManager, with an empty workflow cache. New Workflows are automatically assigned unique identifiers,
* starting with 1.
*/
public DefaultWorkflowManager() {
m_next = 1;
m_workflows = ConcurrentHashMap.newKeySet();
m_approvers = new ConcurrentHashMap<>();
m_completed = new CopyOnWriteArrayList<>();
}
/**
* {@inheritDoc}
*/
@Override
public void start( final Workflow workflow ) throws WikiException {
m_workflows.add( workflow );
workflow.setWorkflowManager( this );
workflow.setId( nextId() );
workflow.start();
}
/**
* {@inheritDoc}
*/
@Override
public Set< Workflow > getWorkflows() {
final Set< Workflow > workflows = ConcurrentHashMap.newKeySet();
workflows.addAll( m_workflows );
return workflows;
}
/**
* {@inheritDoc}
*/
@Override
public List< Workflow > getCompletedWorkflows() {
return new CopyOnWriteArrayList< >( m_completed );
}
/**
* {@inheritDoc}
*/
@Override
public void initialize( final WikiEngine engine, final Properties props ) {
m_engine = engine;
// Identify the workflows requiring approvals
for( final Object o : props.keySet() ) {
final String prop = ( String )o;
if( prop.startsWith( PROPERTY_APPROVER_PREFIX ) ) {
// For the key, everything after the prefix is the workflow name
final String key = prop.substring( PROPERTY_APPROVER_PREFIX.length() );
if( key.length() > 0 ) {
// Only use non-null/non-blank approvers
final String approver = props.getProperty( prop );
if( approver != null && approver.length() > 0 ) {
m_approvers.put( key, new UnresolvedPrincipal( approver ) );
}
}
}
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean requiresApproval( final String messageKey ) {
return m_approvers.containsKey( messageKey );
}
/**
* {@inheritDoc}
*/
@Override
public Principal getApprover( final String messageKey ) throws WikiException {
Principal approver = m_approvers.get( messageKey );
if ( approver == null ) {
throw new WikiException( "Workflow '" + messageKey + "' does not require approval." );
}
// Try to resolve UnresolvedPrincipals
if ( approver instanceof UnresolvedPrincipal ) {
final String name = approver.getName();
approver = m_engine.getAuthorizationManager().resolvePrincipal( name );
// If still unresolved, throw exception; otherwise, freshen our cache
if ( approver instanceof UnresolvedPrincipal ) {
throw new WikiException( "Workflow approver '" + name + "' cannot not be resolved." );
}
m_approvers.put( messageKey, approver );
}
return approver;
}
/**
* Protected helper method that returns the associated WikiEngine
*
* @return the wiki engine
*/
protected WikiEngine getEngine() {
if ( m_engine == null ) {
throw new IllegalStateException( "WikiEngine cannot be null; please initialize WorkflowManager first." );
}
return m_engine;
}
/**
* Returns the DecisionQueue associated with this WorkflowManager
*
* @return the decision queue
*/
public DecisionQueue getDecisionQueue()
{
return m_queue;
}
private volatile int m_next;
/**
* Returns the next available unique identifier, which is subsequently
* incremented.
*
* @return the id
*/
private synchronized int nextId() {
final int current = m_next;
m_next++;
return current;
}
/**
* {@inheritDoc}
*/
@Override
public List< Workflow > getOwnerWorkflows( final WikiSession session ) {
final List< Workflow > workflows = new ArrayList<>();
if ( session.isAuthenticated() ) {
final Principal[] sessionPrincipals = session.getPrincipals();
for( final Workflow w : m_workflows ) {
final Principal owner = w.getOwner();
for ( final Principal sessionPrincipal : sessionPrincipals ) {
if ( sessionPrincipal.equals( owner ) ) {
workflows.add( w );
break;
}
}
}
}
return workflows;
}
/**
* Listens for {@link WorkflowEvent} objects emitted by Workflows. In particular, this
* method listens for {@link WorkflowEvent#CREATED},
* {@link WorkflowEvent#ABORTED} and {@link WorkflowEvent#COMPLETED}
* events. If a workflow is created, it is automatically added to the cache. If one is aborted or completed, it
* is automatically removed.
*
* @param event the event passed to this listener
*/
@Override
public void actionPerformed( final WikiEvent event ) {
if( event instanceof WorkflowEvent ) {
final Workflow workflow = event.getSrc();
switch ( event.getType() ) {
// Remove from manager
case WorkflowEvent.ABORTED :
case WorkflowEvent.COMPLETED : remove( workflow ); break;
// Add to manager
case WorkflowEvent.CREATED : add( workflow ); break;
default: break;
}
}
}
/**
* Protected helper method that adds a newly created Workflow to the cache, and sets its {@code workflowManager} and
* {@code Id} properties if not set.
*
* @param workflow the workflow to add
*/
protected void add( final Workflow workflow ) {
if ( workflow.getWorkflowManager() == null ) {
workflow.setWorkflowManager( this );
}
if ( workflow.getId() == Workflow.ID_NOT_SET ) {
workflow.setId( nextId() );
}
m_workflows.add( workflow );
}
/**
* Protected helper method that removes a specified Workflow from the cache, and moves it to the workflow history list. This method
* defensively checks to see if the workflow has not yet been removed.
*
* @param workflow the workflow to remove
*/
protected void remove( final Workflow workflow ) {
if( m_workflows.contains( workflow ) ) {
m_workflows.remove( workflow );
m_completed.add( workflow );
}
}
}