/** * 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 java.lang.String.format; import static org.opencastproject.util.data.Option.option; import static org.opencastproject.util.data.functions.Misc.chuck; import org.opencastproject.job.api.Job; import org.opencastproject.job.api.JobBarrier; import org.opencastproject.job.api.JobContext; import org.opencastproject.mediapackage.MediaPackage; import org.opencastproject.mediapackage.MediaPackageElement; import org.opencastproject.serviceregistry.api.ServiceRegistry; import org.opencastproject.util.data.Function; import org.opencastproject.util.data.Function0; import org.opencastproject.util.data.Option; import org.opencastproject.workflow.api.WorkflowOperationResult.Action; import com.entwinemedia.fn.data.Opt; import com.entwinemedia.fn.fns.Strings; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; import org.osgi.framework.Constants; import org.osgi.service.component.ComponentContext; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; /** * Abstract base implementation for an operation handler, which implements a simple start operation that returns a * {@link WorkflowOperationResult} with the current mediapackage and {@link Action#CONTINUE}. */ public abstract class AbstractWorkflowOperationHandler implements WorkflowOperationHandler { /** The ID of this operation handler */ protected String id = null; /** The description of what this handler actually does */ protected String description = null; /** The configuration options for this operation handler */ protected SortedMap<String, String> options = new TreeMap<String, String>(); /** Optional service registry */ protected ServiceRegistry serviceRegistry = null; /** The JobBarrier polling interval */ private long jobBarrierPollingInterval = JobBarrier.DEFAULT_POLLING_INTERVAL; /** * Activates this component with its properties once all of the collaborating services have been set * * @param cc * The component's context, containing the properties used for configuration */ protected void activate(ComponentContext cc) { this.id = (String) cc.getProperties().get(WorkflowService.WORKFLOW_OPERATION_PROPERTY); this.description = (String) cc.getProperties().get(Constants.SERVICE_DESCRIPTION); } /** * {@inheritDoc} * * @see org.opencastproject.workflow.api.WorkflowOperationHandler#start(org.opencastproject.workflow.api.WorkflowInstance, * JobContext) */ @Override public abstract WorkflowOperationResult start(WorkflowInstance workflowInstance, JobContext context) throws WorkflowOperationException; /** * {@inheritDoc} * * @see org.opencastproject.workflow.api.WorkflowOperationHandler#skip(org.opencastproject.workflow.api.WorkflowInstance, * JobContext) */ @Override public WorkflowOperationResult skip(WorkflowInstance workflowInstance, JobContext context) throws WorkflowOperationException { return createResult(Action.SKIP); } /** * {@inheritDoc} * * @see org.opencastproject.workflow.api.WorkflowOperationHandler#destroy(org.opencastproject.workflow.api.WorkflowInstance, * JobContext) */ @Override public void destroy(WorkflowInstance workflowInstance, JobContext context) throws WorkflowOperationException { } /** * {@inheritDoc} * * @see org.opencastproject.workflow.api.WorkflowOperationHandler#getConfigurationOptions() */ @Override public SortedMap<String, String> getConfigurationOptions() { return options; } /** * Adds a configuration option to the list of possible configuration options. * * @param name * the option name * @param description * the option description */ public void addConfigurationOption(String name, String description) { options.put(name, description); } /** * Removes the configuration option from the list of possible configuration options. * * @param name * the option name */ public void removeConfigurationOption(String name) { options.remove(name); } /** * Converts a comma separated string into a set of values. Useful for converting operation configuration strings into * multi-valued sets. * * @param elements * The comma space separated string * @return the set of values */ protected List<String> asList(String elements) { elements = StringUtils.trimToNull(elements); List<String> list = new ArrayList<String>(); if (elements != null) { for (String s : StringUtils.split(elements, ",")) { if (StringUtils.trimToNull(s) != null) { list.add(s.trim()); } } } return list; } /** {@link #asList(String)} as a function. */ protected Function<String, List<String>> asList = new Function<String, List<String>>() { @Override public List<String> apply(String s) { return asList(s); } }; /** * Generates a filename using the base name of a source element and the extension of a derived element. * * @param source * the source media package element * @param derived * the derived media package element * @return the filename */ protected String getFileNameFromElements(MediaPackageElement source, MediaPackageElement derived) { String fileName = FilenameUtils.getBaseName(source.getURI().getPath()); String fileExtension = FilenameUtils.getExtension(derived.getURI().getPath()); return fileName + "." + fileExtension; } /** * {@inheritDoc} * * @see org.opencastproject.workflow.api.WorkflowOperationHandler#getId() */ @Override public String getId() { return id; } /** * {@inheritDoc} * * @see org.opencastproject.workflow.api.WorkflowOperationHandler#getDescription() */ @Override public String getDescription() { return description; } /** * Creates a result for the execution of this workflow operation handler. * * @param action * the action to take * @return the result */ protected WorkflowOperationResult createResult(Action action) { return createResult(null, null, action, 0); } /** * Creates a result for the execution of this workflow operation handler. * <p> * Since there is no way for the workflow service to determine the queuing time (e. g. waiting on services), it needs * to be provided by the handler. * * @param action * the action to take * @param timeInQueue * the amount of time this handle spent waiting for services * @return the result */ protected WorkflowOperationResult createResult(Action action, long timeInQueue) { return createResult(null, null, action, timeInQueue); } /** * Creates a result for the execution of this workflow operation handler. * * @param mediaPackage * the modified mediapackage * @param action * the action to take * @return the result */ protected WorkflowOperationResult createResult(MediaPackage mediaPackage, Action action) { return createResult(mediaPackage, null, action, 0); } /** * Creates a result for the execution of this workflow operation handler. * <p> * Since there is no way for the workflow service to determine the queuing time (e. g. waiting on services), it needs * to be provided by the handler. * * @param mediaPackage * the modified mediapackage * @param action * the action to take * @param timeInQueue * the amount of time this handle spent waiting for services * @return the result */ protected WorkflowOperationResult createResult(MediaPackage mediaPackage, Action action, long timeInQueue) { return createResult(mediaPackage, null, action, timeInQueue); } /** * Creates a result for the execution of this workflow operation handler. * <p> * Since there is no way for the workflow service to determine the queuing time (e. g. waiting on services), it needs * to be provided by the handler. * * @param mediaPackage * the modified mediapackage * @param properties * the properties to add to the workflow instance * @param action * the action to take * @param timeInQueue * the amount of time this handle spent waiting for services * @return the result */ protected WorkflowOperationResult createResult(MediaPackage mediaPackage, Map<String, String> properties, Action action, long timeInQueue) { return new WorkflowOperationResultImpl(mediaPackage, properties, action, timeInQueue); } /** * Sets the service registry. This method is here as a convenience for developers that need the registry to do job * waiting. * * @param serviceRegistry * the service registry */ public void setServiceRegistry(ServiceRegistry serviceRegistry) { this.serviceRegistry = serviceRegistry; } /** * Waits until all of the jobs have reached either one of these statuses: * <ul> * <li>{@link Job.Status#FINISHED}</li> * <li>{@link Job.Status#FAILED}</li> * <li>{@link Job.Status#DELETED}</li> * </ul> * After that, the method returns with the actual outcomes of the jobs. * * @param jobs * the jobs * @return the jobs and their outcomes * @throws IllegalStateException * if the service registry has not been set * @throws IllegalArgumentException * if the jobs collecion is either <code>null</code> or empty */ protected JobBarrier.Result waitForStatus(Job... jobs) throws IllegalStateException, IllegalArgumentException { return waitForStatus(0, jobs); } /** * Waits until all of the jobs have reached either one of these statuses: * <ul> * <li>{@link Job.Status#FINISHED}</li> * <li>{@link Job.Status#FAILED}</li> * <li>{@link Job.Status#DELETED}</li> * </ul> * After that, the method returns with the actual outcomes of the jobs. * * @param timeout * the maximum amount of time in milliseconds to wait * @param jobs * the jobs * @return the jobs and their outcomes * @throws IllegalStateException * if the service registry has not been set * @throws IllegalArgumentException * if the jobs collection is either <code>null</code> or empty */ protected JobBarrier.Result waitForStatus(long timeout, Job... jobs) throws IllegalStateException, IllegalArgumentException { if (serviceRegistry == null) { throw new IllegalStateException("Can't wait for job status without providing a service registry first"); } JobBarrier barrier = new JobBarrier(null, serviceRegistry, jobBarrierPollingInterval, jobs); return barrier.waitForJobs(timeout); } /** * Get a configuration option. * * @deprecated use {@link #getConfig(WorkflowInstance, String)} or {@link #getOptConfig(org.opencastproject.workflow.api.WorkflowInstance, String)} */ protected Option<String> getCfg(WorkflowInstance wi, String key) { return option(wi.getCurrentOperation().getConfiguration(key)); } /** * Get a mandatory configuration key. Values are returned trimmed. * * @throws WorkflowOperationException * if the configuration key is either missing or empty */ protected String getConfig(WorkflowInstance wi, String key) throws WorkflowOperationException { return getConfig(wi.getCurrentOperation(), key); } /** * Get a configuration key. Values are returned trimmed. * * @param w * WorkflowInstance with current operation * @param key * Configuration key to check for * @param defaultValue * Value to return if key does not exists */ protected String getConfig(WorkflowInstance w, String key, String defaultValue) { for (final String cfg : getOptConfig(w.getCurrentOperation(), key)) { return cfg; } return defaultValue; } /** * Get a mandatory configuration key. Values are returned trimmed. * * @throws WorkflowOperationException * if the configuration key is either missing or empty */ protected String getConfig(WorkflowOperationInstance woi, String key) throws WorkflowOperationException { for (final String cfg : getOptConfig(woi, key)) { return cfg; } throw new WorkflowOperationException(format("Configuration key '%s' is either missing or empty", key)); } /** * Get an optional configuration key. Values are returned trimmed. */ protected Opt<String> getOptConfig(WorkflowInstance wi, String key) { return getOptConfig(wi.getCurrentOperation(), key); } /** * Get an optional configuration key. Values are returned trimmed. */ protected Opt<String> getOptConfig(WorkflowOperationInstance woi, String key) { return Opt.nul(woi.getConfiguration(key)).flatMap(Strings.trimToNone); } /** * Create an error function. * <p> * Example usage: <code>getCfg(wi, "key").getOrElse(this.<String>cfgKeyMissing("key"))</code> * * @see #getCfg(WorkflowInstance, String) * @deprecated see {@link #getCfg(WorkflowInstance, String)} for details */ protected <A> Function0<A> cfgKeyMissing(final String key) { return new Function0<A>() { @Override public A apply() { return chuck(new WorkflowOperationException(key + " is missing or malformed")); } }; } /** * Set the @link org.opencastproject.job.api.JobBarrier polling interval. * <p> * While waiting for other jobs to finish, the barrier will poll the status of these jobs until they are finished. To * reduce load on the system, the polling is done only every x milliseconds. This interval defines the sleep time * between these polls. * <p> * If most cases you want to leave this at its default value. It will make sense, though, to adjust this time if you * know that your job will be exceptionally short. An example of this might be the unit tests where other jobs are * usually mocked. But this setting is not limited to tests and may be a sensible options for other jobs as well. * * @param interval the time in miliseconds between two polling operations * * @see org.opencastproject.job.api.JobBarrier#DEFAULT_POLLING_INTERVAL */ public void setJobBarrierPollingInterval(long interval) { this.jobBarrierPollingInterval = interval; } /** * {@inheritDoc} * * @see java.lang.Object#hashCode() */ @Override public int hashCode() { return id != null ? id.hashCode() : super.hashCode(); } /** * {@inheritDoc} * * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (obj instanceof WorkflowOperationHandler) { if (id != null) return id.equals(((WorkflowOperationHandler) obj).getId()); else return this == obj; } return false; } /** * {@inheritDoc} * * @see java.lang.Object#toString() */ @Override public String toString() { return getId(); } }