ImageConvertWorkflowOperationHandler.java
/*
* 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.handler.composer;
import org.opencastproject.composer.api.ComposerService;
import org.opencastproject.composer.api.EncodingProfile;
import org.opencastproject.job.api.Job;
import org.opencastproject.job.api.JobContext;
import org.opencastproject.mediapackage.Attachment;
import org.opencastproject.mediapackage.MediaPackage;
import org.opencastproject.mediapackage.MediaPackageElementFlavor;
import org.opencastproject.mediapackage.MediaPackageElementParser;
import org.opencastproject.mediapackage.MediaPackageException;
import org.opencastproject.mediapackage.selector.AttachmentSelector;
import org.opencastproject.serviceregistry.api.ServiceRegistry;
import org.opencastproject.util.NotFoundException;
import org.opencastproject.workflow.api.AbstractWorkflowOperationHandler;
import org.opencastproject.workflow.api.ConfiguredTagsAndFlavors;
import org.opencastproject.workflow.api.WorkflowInstance;
import org.opencastproject.workflow.api.WorkflowOperationException;
import org.opencastproject.workflow.api.WorkflowOperationHandler;
import org.opencastproject.workflow.api.WorkflowOperationInstance;
import org.opencastproject.workflow.api.WorkflowOperationResult;
import org.opencastproject.workspace.api.Workspace;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
@Component(
immediate = true,
service = WorkflowOperationHandler.class,
property = {
"service.description=Image Convert Workflow Operation Handler",
"workflow.operation=image-convert"
}
)
public class ImageConvertWorkflowOperationHandler extends AbstractWorkflowOperationHandler {
private static final Logger logger = LoggerFactory.getLogger(ImageConvertWorkflowOperationHandler.class);
/** Configuration key for encoding profile */
private static final String CONFIG_KEY_ENCODING_PROFILE = "encoding-profile";
/** Configuration key for encoding profile. The value of this configuration will be used,
* if encoding-profile isn't set */
private static final String CONFIG_KEY_ENCODING_PROFILES = "encoding-profiles";
/** Boolean configuration key for value, wether to use flavors and tags for selection of the source
* attachments (set to true) or flavors or tags (set to false) */
private static final String CONFIG_KEY_TAGS_AND_FLAVORS = "tags-and-flavors";
/** The composer service */
private ComposerService composerService = null;
/** The workspace */
private Workspace workspace = null;
/**
* Callback for the OSGi declarative services configuration.
*
* @param composerService
* the composer service
*/
@Reference
protected void setComposerService(ComposerService composerService) {
this.composerService = composerService;
}
/**
* Callback for declarative services configuration that will introduce us to the local workspace service.
*
* @param workspace
* an instance of the workspace
*/
@Reference
public void setWorkspace(Workspace workspace) {
this.workspace = workspace;
}
@Override
public WorkflowOperationResult start(WorkflowInstance workflowInstance, JobContext context) throws WorkflowOperationException {
WorkflowOperationInstance operation = workflowInstance.getCurrentOperation();
// Check which tags have been configured
ConfiguredTagsAndFlavors tagsAndFlavors = getTagsAndFlavors(workflowInstance,
Configuration.many, Configuration.many, Configuration.many, Configuration.many);
List<MediaPackageElementFlavor> sourceFlavorsOption = tagsAndFlavors.getSrcFlavors();
List<String> sourceTagsOption = tagsAndFlavors.getSrcTags();
List<MediaPackageElementFlavor> targetFlavorsOption = tagsAndFlavors.getTargetFlavors();
ConfiguredTagsAndFlavors.TargetTags targetTagsOption = tagsAndFlavors.getTargetTags();
String encodingProfileOption = StringUtils.trimToNull(operation.getConfiguration(CONFIG_KEY_ENCODING_PROFILE));
if (encodingProfileOption == null)
encodingProfileOption = StringUtils.trimToNull(operation.getConfiguration(CONFIG_KEY_ENCODING_PROFILES));
String tagsAndFlavorsOption = StringUtils.trimToNull(operation.getConfiguration(CONFIG_KEY_TAGS_AND_FLAVORS));
boolean tagsAndFlavorsBool = BooleanUtils.toBoolean(tagsAndFlavorsOption);
MediaPackage mediaPackage = workflowInstance.getMediaPackage();
// Make sure either one of tags or flavors are provided
if (sourceFlavorsOption.isEmpty() && sourceTagsOption.isEmpty()) {
logger.info("No source tags or flavors have been specified, not matching anything");
return createResult(mediaPackage, WorkflowOperationResult.Action.CONTINUE);
}
// Target flavor
MediaPackageElementFlavor targetFlavor = null;
if (!targetFlavorsOption.isEmpty()) {
targetFlavor = targetFlavorsOption.get(0);
} else {
throw new WorkflowOperationException("No target flavor specified");
}
List<String> profiles = new ArrayList<>();
for (String encodingProfileId : asList(encodingProfileOption)) {
EncodingProfile profile = composerService.getProfile(encodingProfileId);
if (profile == null)
throw new WorkflowOperationException("Encoding profile '" + encodingProfileId + "' was not found");
// just test if the profile exists, we only need the profile id for further work
profiles.add(encodingProfileId);
}
// Make sure there is at least one profile
if (profiles.isEmpty())
throw new WorkflowOperationException("No encoding profile was specified");
AttachmentSelector attachmentSelector = new AttachmentSelector();
for (MediaPackageElementFlavor sourceFlavor : sourceFlavorsOption) {
attachmentSelector.addFlavor(sourceFlavor);
}
for (String sourceTag : sourceTagsOption) {
attachmentSelector.addTag(sourceTag);
}
// Look for elements matching the tag
Collection<Attachment> sourceElements = attachmentSelector.select(mediaPackage, tagsAndFlavorsBool);
Map<Job, Attachment> jobs = new Hashtable<>();
try {
for (Attachment sourceElement : sourceElements) {
Job job = composerService.convertImage(sourceElement, profiles.toArray(new String[profiles.size()]));
jobs.put(job, sourceElement);
}
if (!waitForStatus(jobs.keySet().toArray(new Job[jobs.size()])).isSuccess()) {
throw new WorkflowOperationException("At least one image conversation job did not succeed.");
}
for (Map.Entry<Job, Attachment> jobEntry : jobs.entrySet()) {
Job job = jobEntry.getKey();
Attachment sourceElement = jobEntry.getValue();
List<Attachment> targetElements =
(List<Attachment>) MediaPackageElementParser.getArrayFromXml(job.getPayload());
for (Attachment targetElement : targetElements) {
String targetFileName = FilenameUtils.getName(targetElement.getURI().getPath());
URI newTargetElementUri = workspace.moveTo(targetElement.getURI(), mediaPackage.getIdentifier().toString(),
targetElement.getIdentifier(), targetFileName);
targetElement.setURI(newTargetElementUri);
targetElement.setChecksum(null);
// set flavor on target element
if (targetFlavor != null) {
targetElement.setFlavor(targetFlavor);
if (StringUtils.equalsAny("*", targetFlavor.getType())) {
targetElement.setFlavor(MediaPackageElementFlavor.flavor(
sourceElement.getFlavor().getType(), targetElement.getFlavor().getSubtype()));
}
if (StringUtils.equalsAny("*", targetFlavor.getSubtype())) {
targetElement.setFlavor(MediaPackageElementFlavor.flavor(
targetElement.getFlavor().getType(), sourceElement.getFlavor().getSubtype()));
}
}
// set tags on target element
applyTargetTagsToElement(targetTagsOption, targetElement);
mediaPackage.addDerived(targetElement, sourceElement);
}
}
return createResult(mediaPackage, WorkflowOperationResult.Action.CONTINUE);
} catch (WorkflowOperationException ex) {
throw ex;
} catch (Throwable t) {
throw new WorkflowOperationException("Convert image operation failed", t);
} finally {
cleanupWorkspace(jobs.keySet());
}
}
private void cleanupWorkspace(Collection<Job> jobs) {
for (Job job : jobs) {
try {
List<Attachment> targetElements =
(List<Attachment>) MediaPackageElementParser.getArrayFromXml(job.getPayload());
for (Attachment targetElement : targetElements) {
try {
workspace.delete(targetElement.getURI());
} catch (NotFoundException ex) {
logger.trace("The image file {} not found", targetElement, ex);
} catch (IOException ex) {
logger.warn("Unable to delete image file {} from workspace", targetElement, ex);
}
}
} catch (MediaPackageException ex) {
logger.debug("Unable to parse job payload from job {}", job.getId(), ex);
}
}
}
@Reference
@Override
public void setServiceRegistry(ServiceRegistry serviceRegistry) {
super.setServiceRegistry(serviceRegistry);
}
}