/**
* Licensed to The Apereo Foundation under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
*
* The Apereo Foundation licenses this file to you under the Educational
* Community 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://opensource.org/licenses/ecl2.txt
*
* 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.opencastproject.workflow.api;
import static com.entwinemedia.fn.Stream.$;
import static org.opencastproject.workflow.api.WorkflowOperationInstance.OperationState.FAILED;
import static org.opencastproject.workflow.api.WorkflowOperationInstance.OperationState.INSTANTIATED;
import static org.opencastproject.workflow.api.WorkflowOperationInstance.OperationState.RETRY;
import static org.opencastproject.workflow.api.WorkflowOperationInstance.OperationState.SKIPPED;
import static org.opencastproject.workflow.api.WorkflowOperationInstance.OperationState.SUCCEEDED;
import org.opencastproject.mediapackage.MediaPackage;
import org.opencastproject.security.api.JaxbOrganization;
import org.opencastproject.security.api.JaxbUser;
import org.opencastproject.security.api.Organization;
import org.opencastproject.security.api.User;
import org.opencastproject.workflow.api.WorkflowOperationInstance.OperationState;
import com.entwinemedia.fn.Fn;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
@XmlType(name = "workflow", namespace = "http://workflow.opencastproject.org")
@XmlRootElement(name = "workflow", namespace = "http://workflow.opencastproject.org")
@XmlAccessorType(XmlAccessType.NONE)
public class WorkflowInstanceImpl implements WorkflowInstance {
@XmlAttribute()
private long id;
@XmlAttribute()
private WorkflowState state;
@XmlElement(name = "template")
private String template;
@XmlElement(name = "title")
private String title;
@XmlElement(name = "description")
private String description;
@XmlElement(name = "parent", nillable = true)
private Long parentId;
@XmlJavaTypeAdapter(UserAdapter.class)
@XmlElement(name = "creator", namespace = "http://org.opencastproject.security")
private User creator;
@XmlJavaTypeAdapter(OrganizationAdapter.class)
@XmlElement(name = "organization", namespace = "http://org.opencastproject.security")
private JaxbOrganization organization;
@XmlElement(name = "mediapackage", namespace = "http://mediapackage.opencastproject.org")
private MediaPackage mediaPackage;
@XmlElement(name = "operation")
@XmlElementWrapper(name = "operations")
protected List<WorkflowOperationInstance> operations;
@XmlElement(name = "configuration")
@XmlElementWrapper(name = "configurations")
protected Set<WorkflowConfiguration> configurations;
@XmlTransient
protected boolean initialized = false;
/**
* Default no-arg constructor needed by JAXB
*/
public WorkflowInstanceImpl() {
}
/**
* Constructs a new workflow instance from the given definition, mediapackage, and optional parent workflow ID and
* properties.
*
* @param def
* the workflow definition
* @param mediaPackage
* the mediapackage
* @param parentWorkflowId
* the parent workflow ID
* @param creator
* the user that created this workflow instance
* @param organization
* the organization
* @param properties
* the properties
*/
public WorkflowInstanceImpl(WorkflowDefinition def, MediaPackage mediaPackage, Long parentWorkflowId, User creator,
Organization organization, Map<String, String> properties) {
this.id = -1; // this should be set by the workflow service once the workflow is persisted
this.title = def.getTitle();
this.template = def.getId();
this.description = def.getDescription();
this.parentId = parentWorkflowId;
this.creator = creator;
if (organization != null)
this.organization = JaxbOrganization.fromOrganization(organization);
this.state = WorkflowState.INSTANTIATED;
this.mediaPackage = mediaPackage;
this.operations = new ArrayList<WorkflowOperationInstance>();
this.configurations = new TreeSet<WorkflowConfiguration>();
if (properties != null) {
for (Entry<String, String> entry : properties.entrySet()) {
configurations.add(new WorkflowConfigurationImpl(entry.getKey(), entry.getValue()));
}
}
extend(def);
}
/**
* {@inheritDoc}
*
* @see org.opencastproject.workflow.api.WorkflowInstance#getId()
*/
@Override
public long getId() {
return id;
}
/**
* Sets the identifier of this workflow instance
*
* @param id
*/
@Override
public void setId(long id) {
this.id = id;
}
/**
* {@inheritDoc}
*
* @see org.opencastproject.workflow.api.WorkflowInstance#getTitle()
*/
@Override
public String getTitle() {
return title;
}
/**
* Sets the title of this workflow instance
*
* @param title
*/
public void setTitle(String title) {
this.title = title;
}
/**
* {@inheritDoc}
*
* @see org.opencastproject.workflow.api.WorkflowInstance#getCreator()
*/
@Override
public User getCreator() {
return creator;
}
/**
* {@inheritDoc}
*
* @see org.opencastproject.workflow.api.WorkflowInstance#getOrganization()
*/
@Override
public Organization getOrganization() {
return organization;
}
/**
* @param creator
* the creator to set
*/
public void setCreator(User creator) {
this.creator = creator;
}
/**
* Sets the workflow's organization.
*
* @param organization
* the organization
*/
public void setOrganization(Organization organization) {
if (organization == null)
this.organization = null;
else
this.organization = JaxbOrganization.fromOrganization(organization);
}
/**
* {@inheritDoc}
*
* @see org.opencastproject.workflow.api.WorkflowInstance#getDescription()
*/
@Override
public String getDescription() {
return description;
}
/**
* Sets the description of this workflow instance
*
* @param description
*/
public void setDescription(String description) {
this.description = description;
}
/**
* @return the parentId
*/
@Override
public Long getParentId() {
return parentId;
}
/**
* @param parentId
* the parentId to set
*/
public void setParentId(Long parentId) {
this.parentId = parentId;
}
/**
* {@inheritDoc}
*
* @see org.opencastproject.workflow.api.WorkflowInstance#getState()
*/
@Override
public WorkflowState getState() {
return state;
}
/**
* Sets the state of this workflow instance
*
* @param state
*/
@Override
public void setState(WorkflowState state) {
this.state = state;
}
@Override
public boolean isActive() {
return WorkflowUtil.isActive(getState());
}
/**
* {@inheritDoc}
*
* @see org.opencastproject.workflow.api.WorkflowInstance#getCurrentOperation()
*/
@Override
public WorkflowOperationInstance getCurrentOperation() throws IllegalStateException {
if (!initialized)
init();
if (operations == null || operations.isEmpty())
throw new IllegalStateException("Workflow " + id + " has no operations");
WorkflowOperationInstance currentOperation = null;
// Handle newly instantiated workflows
if (INSTANTIATED.equals(operations.get(0).getState()) || RETRY.equals(operations.get(0).getState())) {
currentOperation = operations.get(0);
} else {
OperationState previousState = null;
int position = 0;
while (currentOperation == null && position < operations.size()) {
WorkflowOperationInstance operation = operations.get(position);
switch (operation.getState()) {
case FAILED:
break;
case RETRY:
case INSTANTIATED:
if (SUCCEEDED.equals(previousState) || SKIPPED.equals(previousState) || FAILED.equals(previousState))
currentOperation = operation;
break;
case PAUSED:
currentOperation = operation;
break;
case RUNNING:
currentOperation = operation;
break;
case SKIPPED:
break;
case SUCCEEDED:
break;
default:
throw new IllegalStateException("Found operation in unknown state '" + operation.getState() + "'");
}
previousState = operation.getState();
position++;
}
// If we are at the last operation and there is no more work to do, we're done
if (operations.get(operations.size() - 1) == currentOperation) {
switch (currentOperation.getState()) {
case FAILED:
case SKIPPED:
case SUCCEEDED:
currentOperation = null;
break;
case INSTANTIATED:
case PAUSED:
case RUNNING:
case RETRY:
break;
default:
throw new IllegalStateException("Found operation in unknown state '" + currentOperation.getState() + "'");
}
}
}
return currentOperation;
}
/**
* {@inheritDoc}
*
* @see org.opencastproject.workflow.api.WorkflowInstance#getOperations()
*/
@Override
public List<WorkflowOperationInstance> getOperations() {
if (operations == null)
operations = new ArrayList<WorkflowOperationInstance>();
if (!initialized)
init();
return new ArrayList<WorkflowOperationInstance>(operations);
}
/**
* Sets the workflow operations on this workflow instance
*
* @param workflowOperationInstanceList
*/
@Override
public final void setOperations(List<WorkflowOperationInstance> workflowOperationInstanceList) {
this.operations = workflowOperationInstanceList;
init();
}
protected void init() {
if (operations == null || operations.isEmpty())
return;
// Jaxb will lose the workflow operation's position, so we fix it here
for (int i = 0; i < operations.size(); i++) {
((WorkflowOperationInstanceImpl) operations.get(i)).setPosition(i);
}
initialized = true;
}
/**
* {@inheritDoc}
*
* @see org.opencastproject.workflow.api.WorkflowInstance#getMediaPackage()
*/
@Override
public MediaPackage getMediaPackage() {
return mediaPackage;
}
/**
* {@inheritDoc}
*
* @see org.opencastproject.workflow.api.WorkflowInstance#setMediaPackage(org.opencastproject.mediapackage.MediaPackage)
*/
@Override
public void setMediaPackage(MediaPackage mediaPackage) {
this.mediaPackage = mediaPackage;
}
/**
* {@inheritDoc}
*
* @see org.opencastproject.workflow.api.Configurable#getConfiguration(java.lang.String)
*/
@Override
public String getConfiguration(String key) {
if (key == null || configurations == null)
return null;
for (WorkflowConfiguration config : configurations) {
if (config.getKey().equals(key))
return config.getValue();
}
return null;
}
/**
* {@inheritDoc}
*
* @see org.opencastproject.workflow.api.Configurable#getConfigurationKeys()
*/
@Override
public Set<String> getConfigurationKeys() {
Set<String> keys = new TreeSet<String>();
if (configurations != null && !configurations.isEmpty()) {
for (WorkflowConfiguration config : configurations) {
keys.add(config.getKey());
}
}
return keys;
}
/**
* {@inheritDoc}
*
* @see org.opencastproject.workflow.api.Configurable#removeConfiguration(java.lang.String)
*/
@Override
public void removeConfiguration(String key) {
if (key == null || configurations == null)
return;
for (Iterator<WorkflowConfiguration> configIter = configurations.iterator(); configIter.hasNext();) {
WorkflowConfiguration config = configIter.next();
if (config.getKey().equals(key)) {
configIter.remove();
return;
}
}
}
/**
* {@inheritDoc}
*
* @see org.opencastproject.workflow.api.Configurable#setConfiguration(java.lang.String, java.lang.String)
*/
@Override
public void setConfiguration(String key, String value) {
if (key == null)
return;
if (configurations == null)
configurations = new HashSet<WorkflowConfiguration>();
// Adjust already existing values
for (WorkflowConfiguration config : configurations) {
if (config.getKey().equals(key)) {
((WorkflowConfigurationImpl) config).setValue(value);
return;
}
}
// No configurations were found, so add a new one
configurations.add(new WorkflowConfigurationImpl(key, value));
}
/**
* {@inheritDoc}
*
* @see org.opencastproject.workflow.api.WorkflowInstance#next()
*/
@Override
public WorkflowOperationInstance next() {
if (operations == null || operations.size() == 0)
throw new IllegalStateException("Operations list must contain operations");
if (!initialized)
init();
WorkflowOperationInstance currentOperation = getCurrentOperation();
if (currentOperation == null)
throw new IllegalStateException("Can't call next on a finished workflow");
for (Iterator<WorkflowOperationInstance> opIter = operations.iterator(); opIter.hasNext();) {
WorkflowOperationInstance op = opIter.next();
if (op.equals(currentOperation) && opIter.hasNext()) {
currentOperation.setState(OperationState.SKIPPED);
currentOperation = opIter.next();
return currentOperation;
}
}
return null;
}
/**
* {@inheritDoc}
*
* @see org.opencastproject.workflow.api.WorkflowInstance#hasNext()
*/
@Override
public boolean hasNext() {
if (!initialized)
init();
if (WorkflowState.FAILED.equals(state) || WorkflowState.FAILING.equals(state)
|| WorkflowState.STOPPED.equals(state) || WorkflowState.SUCCEEDED.equals(state))
return false;
if (operations == null || operations.size() == 0)
throw new IllegalStateException("operations list must contain operations");
WorkflowOperationInstance currentOperation = getCurrentOperation();
if (currentOperation == null)
return true;
return operations.lastIndexOf(currentOperation) < operations.size() - 1;
}
/**
* {@inheritDoc}
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "Workflow {" + id + "}";
}
/**
* {@inheritDoc}
*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return Long.valueOf(id).hashCode();
}
/**
* {@inheritDoc}
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (obj instanceof WorkflowInstance) {
WorkflowInstance other = (WorkflowInstance) obj;
return id == other.getId();
}
return false;
}
/**
* Allows JAXB handling of {@link WorkflowInstance} interfaces.
*/
static class Adapter extends XmlAdapter<WorkflowInstanceImpl, WorkflowInstance> {
@Override
public WorkflowInstanceImpl marshal(WorkflowInstance instance) throws Exception {
return (WorkflowInstanceImpl) instance;
}
@Override
public WorkflowInstance unmarshal(WorkflowInstanceImpl instance) throws Exception {
instance.init();
return instance;
}
}
/**
* Allows JAXB handling of {@link Organization} interfaces.
*/
static class OrganizationAdapter extends XmlAdapter<JaxbOrganization, Organization> {
@Override
public JaxbOrganization marshal(Organization org) throws Exception {
if (org == null)
return null;
if (org instanceof JaxbOrganization)
return (JaxbOrganization) org;
return JaxbOrganization.fromOrganization(org);
}
@Override
public Organization unmarshal(JaxbOrganization org) throws Exception {
return org;
}
}
/**
* Allows JAXB handling of {@link Organization} interfaces.
*/
static class UserAdapter extends XmlAdapter<JaxbUser, User> {
@Override
public JaxbUser marshal(User user) throws Exception {
if (user == null)
return null;
if (user instanceof JaxbUser)
return (JaxbUser) user;
return JaxbUser.fromUser(user);
}
@Override
public User unmarshal(JaxbUser user) throws Exception {
return user;
}
}
/**
* @return the template
*/
@Override
public String getTemplate() {
return template;
}
/**
* @param template
* the template to set
*/
public void setTemplate(String template) {
this.template = template;
}
/**
* {@inheritDoc}
*
* @see org.opencastproject.workflow.api.WorkflowInstance#extend(org.opencastproject.workflow.api.WorkflowDefinition)
*/
@Override
public void extend(WorkflowDefinition workflowDefinition) {
if (!workflowDefinition.getOperations().isEmpty()) {
setOperations($(operations).append($(workflowDefinition.getOperations()).map(mkInstanceFn)).toList());
setTemplate(workflowDefinition.getId());
}
}
@Override
public void insert(WorkflowDefinition workflowDefinition, WorkflowOperationInstance after) {
if (!workflowDefinition.getOperations().isEmpty() && after.getPosition() >= 0) {
setOperations($(operations).take(after.getPosition() + 1)
.append($(workflowDefinition.getOperations()).map(mkInstanceFn))
.append($(operations).drop(after.getPosition() + 1)).toList());
}
}
private final Fn<WorkflowOperationDefinition, WorkflowOperationInstance> mkInstanceFn = new Fn<WorkflowOperationDefinition, WorkflowOperationInstance>() {
@Override
public WorkflowOperationInstance apply(WorkflowOperationDefinition wod) {
return new WorkflowOperationInstanceImpl(wod, -1);
}
};
}