TimelinePreviewsWorkflowOperationHandler.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.timelinepreviews;
import org.opencastproject.job.api.Job;
import org.opencastproject.job.api.JobContext;
import org.opencastproject.mediapackage.MediaPackage;
import org.opencastproject.mediapackage.MediaPackageElement;
import org.opencastproject.mediapackage.MediaPackageElementFlavor;
import org.opencastproject.mediapackage.MediaPackageElementParser;
import org.opencastproject.mediapackage.MediaPackageException;
import org.opencastproject.mediapackage.Track;
import org.opencastproject.mediapackage.selector.TrackSelector;
import org.opencastproject.serviceregistry.api.ServiceRegistry;
import org.opencastproject.timelinepreviews.api.TimelinePreviewsException;
import org.opencastproject.timelinepreviews.api.TimelinePreviewsService;
import org.opencastproject.util.IoSupport;
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.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.ComponentContext;
import org.osgi.service.component.annotations.Activate;
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.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* Workflow operation for the timeline previews service.
*/
@Component(
immediate = true,
service = WorkflowOperationHandler.class,
property = {
"service.description=Timeline Preview Images Workflow Operation Handler",
"workflow.operation=timelinepreviews"
}
)
public class TimelinePreviewsWorkflowOperationHandler extends AbstractWorkflowOperationHandler {
/** The logging facility */
private static final Logger logger = LoggerFactory.getLogger(TimelinePreviewsWorkflowOperationHandler.class);
/** Source flavor configuration property name. */
private static final String SOURCE_FLAVOR_PROPERTY = "source-flavor";
/** Source tags configuration property name. */
private static final String SOURCE_TAGS_PROPERTY = "source-tags";
/** Target flavor configuration property name. */
private static final String TARGET_FLAVOR_PROPERTY = "target-flavor";
/** Target tags configuration property name. */
private static final String TARGET_TAGS_PROPERTY = "target-tags";
/** Process first match only */
private static final String PROCCESS_FIRST_MATCH = "process-first-match-only";
/** Image size configuration property name. */
private static final String IMAGE_SIZE_PROPERTY = "image-count";
/** Default value for image size. */
private static final int DEFAULT_IMAGE_SIZE = 10;
/** The timeline previews service. */
private TimelinePreviewsService timelinePreviewsService = null;
/** The workspace service. */
private Workspace workspace = null;
@Override
@Activate
public void activate(ComponentContext cc) {
super.activate(cc);
logger.info("Registering timeline previews workflow operation handler");
}
@Override
public WorkflowOperationResult start(WorkflowInstance workflowInstance, JobContext context)
throws WorkflowOperationException {
MediaPackage mediaPackage = workflowInstance.getMediaPackage();
logger.info("Start timeline previews workflow operation for mediapackage {}",
mediaPackage.getIdentifier().toString());
ConfiguredTagsAndFlavors tagsAndFlavors = getTagsAndFlavors(workflowInstance,
Configuration.many, Configuration.many, Configuration.many, Configuration.one);
List<MediaPackageElementFlavor> sourceFlavorProperty = tagsAndFlavors.getSrcFlavors();
List<String> sourceTagsProperty = tagsAndFlavors.getSrcTags();
if (sourceFlavorProperty.isEmpty() && sourceTagsProperty.isEmpty()) {
throw new WorkflowOperationException(String.format("Required property %s or %s not set",
SOURCE_FLAVOR_PROPERTY, SOURCE_TAGS_PROPERTY));
}
MediaPackageElementFlavor targetFlavor = tagsAndFlavors.getSingleTargetFlavor();
ConfiguredTagsAndFlavors.TargetTags targetTagsProperty = tagsAndFlavors.getTargetTags();
String imageSizeArg = StringUtils.trimToNull(
workflowInstance.getCurrentOperation().getConfiguration(IMAGE_SIZE_PROPERTY));
int imageSize;
if (imageSizeArg != null) {
try {
imageSize = Integer.parseInt(imageSizeArg);
} catch (NumberFormatException e) {
imageSize = DEFAULT_IMAGE_SIZE;
logger.info("No valid integer given for property {}, using default value: {}",
IMAGE_SIZE_PROPERTY, DEFAULT_IMAGE_SIZE);
}
} else {
imageSize = DEFAULT_IMAGE_SIZE;
logger.info("Property {} not set, using default value: {}", IMAGE_SIZE_PROPERTY, DEFAULT_IMAGE_SIZE);
}
boolean processOnlyOne = BooleanUtils.toBoolean(StringUtils.trimToNull(
workflowInstance.getCurrentOperation().getConfiguration(PROCCESS_FIRST_MATCH)));
TrackSelector trackSelector = new TrackSelector();
for (MediaPackageElementFlavor flavor : sourceFlavorProperty) {
trackSelector.addFlavor(flavor);
}
for (String tag : sourceTagsProperty) {
trackSelector.addTag(tag);
}
Collection<Track> sourceTracks = trackSelector.select(mediaPackage, true);
if (sourceTracks.isEmpty()) {
logger.info("No tracks found in mediapackage {} with specified {} {}", mediaPackage.getIdentifier().toString(),
SOURCE_FLAVOR_PROPERTY,
sourceFlavorProperty);
createResult(mediaPackage, WorkflowOperationResult.Action.SKIP);
}
List<Job> timelinepreviewsJobs = new ArrayList<Job>(sourceTracks.size());
for (Track sourceTrack : sourceTracks) {
try {
// generate timeline preview images
logger.info("Create timeline previews job for track '{}' in mediapackage '{}'",
sourceTrack.getIdentifier(), mediaPackage.getIdentifier().toString());
Job timelinepreviewsJob = timelinePreviewsService.createTimelinePreviewImages(sourceTrack, imageSize);
timelinepreviewsJobs.add(timelinepreviewsJob);
if (processOnlyOne) {
break;
}
} catch (MediaPackageException | TimelinePreviewsException ex) {
logger.error("Creating timeline previews job for track '{}' in media package '{}' failed with error {}",
sourceTrack.getIdentifier(), mediaPackage.getIdentifier().toString(), ex.getMessage());
}
}
logger.info("Wait for timeline previews jobs for media package {}", mediaPackage.getIdentifier().toString());
if (!waitForStatus(timelinepreviewsJobs.toArray(new Job[timelinepreviewsJobs.size()])).isSuccess()) {
cleanupWorkspace(timelinepreviewsJobs);
throw new WorkflowOperationException(
String.format("Timeline previews jobs for media package '%s' have not completed successfully",
mediaPackage.getIdentifier().toString()));
}
try {
// copy timeline previews attachments into workspace and add them to the media package
for (Job job : timelinepreviewsJobs) {
String jobPayload = job.getPayload();
if (StringUtils.isNotEmpty(jobPayload)) {
MediaPackageElement timelinePreviewsMpe = null;
File timelinePreviewsFile = null;
try {
timelinePreviewsMpe = MediaPackageElementParser.getFromXml(jobPayload);
timelinePreviewsFile = workspace.get(timelinePreviewsMpe.getURI());
} catch (MediaPackageException ex) {
// unexpected job payload
throw new WorkflowOperationException("Can't parse timeline previews attachment from job " + job.getId());
} catch (NotFoundException ex) {
throw new WorkflowOperationException("Timeline preview images file '" + timelinePreviewsMpe.getURI()
+ "' not found", ex);
} catch (IOException ex) {
throw new WorkflowOperationException("Can't get workflow image file '" + timelinePreviewsMpe.getURI()
+ "' from workspace");
}
FileInputStream timelinePreviewsInputStream = null;
logger.info("Put timeline preview images file {} from media package {} to the media package work space",
timelinePreviewsMpe.getURI(), mediaPackage.getIdentifier().toString());
try {
timelinePreviewsInputStream = new FileInputStream(timelinePreviewsFile);
String fileName = FilenameUtils.getName(timelinePreviewsMpe.getURI().getPath());
URI timelinePreviewsWfrUri = workspace.put(mediaPackage.getIdentifier().toString(),
timelinePreviewsMpe.getIdentifier(), fileName, timelinePreviewsInputStream);
timelinePreviewsMpe.setURI(timelinePreviewsWfrUri);
} catch (FileNotFoundException ex) {
throw new WorkflowOperationException("Timeline preview images file " + timelinePreviewsFile.getPath()
+ " not found", ex);
} catch (IOException ex) {
throw new WorkflowOperationException("Can't read just created timeline preview images file "
+ timelinePreviewsFile.getPath(), ex);
} catch (IllegalArgumentException ex) {
throw new WorkflowOperationException(ex);
} finally {
IoSupport.closeQuietly(timelinePreviewsInputStream);
}
// set the timeline previews attachment flavor and add it to the mediapackage
if ("*".equals(targetFlavor.getType())) {
targetFlavor = new MediaPackageElementFlavor(
timelinePreviewsMpe.getFlavor().getType(), targetFlavor.getSubtype());
}
if ("*".equals(targetFlavor.getSubtype())) {
targetFlavor = new MediaPackageElementFlavor(
targetFlavor.getType(), timelinePreviewsMpe.getFlavor().getSubtype());
}
timelinePreviewsMpe.setFlavor(targetFlavor);
applyTargetTagsToElement(targetTagsProperty, timelinePreviewsMpe);
mediaPackage.add(timelinePreviewsMpe);
}
}
} finally {
cleanupWorkspace(timelinepreviewsJobs);
}
logger.info("Timeline previews workflow operation for mediapackage {} completed",
mediaPackage.getIdentifier().toString());
return createResult(mediaPackage, WorkflowOperationResult.Action.CONTINUE);
}
/**
* Remove all files created by the given jobs
* @param jobs
*/
private void cleanupWorkspace(List<Job> jobs) {
for (Job job : jobs) {
String jobPayload = job.getPayload();
if (StringUtils.isNotEmpty(jobPayload)) {
try {
MediaPackageElement timelinepreviewsMpe = MediaPackageElementParser.getFromXml(jobPayload);
URI timelinepreviewsUri = timelinepreviewsMpe.getURI();
workspace.delete(timelinepreviewsUri);
} catch (MediaPackageException ex) {
// unexpected job payload
logger.error("Can't parse timeline previews attachment from job {}", job.getId());
} catch (NotFoundException ex) {
// this is ok, because we want delete the file
} catch (IOException ex) {
logger.warn("Deleting timeline previews image file from workspace failed: {}", ex.getMessage());
// this is ok, because workspace cleaner will remove old files if they exist
}
}
}
}
@Reference
public void setTimelinePreviewsService(TimelinePreviewsService timelinePreviewsService) {
this.timelinePreviewsService = timelinePreviewsService;
}
@Reference
public void setWorkspace(Workspace workspace) {
this.workspace = workspace;
}
@Reference
@Override
public void setServiceRegistry(ServiceRegistry serviceRegistry) {
super.setServiceRegistry(serviceRegistry);
}
}