/**
* 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.distribution;
import static org.opencastproject.util.data.Option.some;
import static org.opencastproject.workflow.handler.distribution.EngagePublicationChannel.CHANNEL_ID;
import org.opencastproject.distribution.api.DistributionException;
import org.opencastproject.distribution.api.DistributionService;
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.MediaPackageElement;
import org.opencastproject.mediapackage.MediaPackageElementFlavor;
import org.opencastproject.mediapackage.MediaPackageElementParser;
import org.opencastproject.mediapackage.MediaPackageException;
import org.opencastproject.mediapackage.MediaPackageReference;
import org.opencastproject.mediapackage.selector.AbstractMediaPackageElementSelector;
import org.opencastproject.mediapackage.selector.SimpleElementSelector;
import org.opencastproject.mediapackage.selector.SimpleFlavorPrioritySelector;
import org.opencastproject.security.api.AclScope;
import org.opencastproject.security.api.AuthorizationService;
import org.opencastproject.workflow.api.AbstractWorkflowOperationHandler;
import org.opencastproject.workflow.api.WorkflowInstance;
import org.opencastproject.workflow.api.WorkflowOperationException;
import org.opencastproject.workflow.api.WorkflowOperationResult;
import org.opencastproject.workflow.api.WorkflowOperationResult.Action;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* The workflow definition for handling "distribute" operations
*/
public class DistributeWorkflowOperationHandler extends AbstractWorkflowOperationHandler {
/** The logging facility */
private static final Logger logger = LoggerFactory.getLogger(DistributeWorkflowOperationHandler.class);
/** The distribution service */
private DistributionService distributionService = null;
/** The authorization service */
private AuthorizationService authorizationService = null;
/**
* Callback for the OSGi declarative services configuration.
*
* @param distributionService
* the distribution service
*/
protected void setDistributionService(DistributionService distributionService) {
this.distributionService = distributionService;
}
/**
* Callback for the OSGi declarative services configuration.
*
* @param authorizationService
* the authorization service
*/
protected void setAuthorizationService(AuthorizationService authorizationService) {
this.authorizationService = authorizationService;
}
/** The configuration options for this handler */
private static final SortedMap<String, String> CONFIG_OPTIONS;
static {
CONFIG_OPTIONS = new TreeMap<String, String>();
CONFIG_OPTIONS.put("source-tags",
"Distribute any mediapackage elements with one of these (comma separated) tags. If a source-tag "
+ "starts with a '-', mediapackage elements with this tag will be excluded from distribution.");
CONFIG_OPTIONS.put("target-tags",
"Apply these (comma separated) tags to any mediapackage elements produced as a result of distribution");
CONFIG_OPTIONS.put("source-flavors",
"Apply these (comma separated) flavors to any mediapackage elements produced as a result of distribution");
CONFIG_OPTIONS
.put("source-priority-flavors",
"Apply these (comma separated) priority flavors to any mediapackage elements produced as a result of distribution");
}
/**
* {@inheritDoc}
*
* @see org.opencastproject.workflow.api.WorkflowOperationHandler#getConfigurationOptions()
*/
@Override
public SortedMap<String, String> getConfigurationOptions() {
return CONFIG_OPTIONS;
}
/**
* {@inheritDoc}
*
* @see org.opencastproject.workflow.api.WorkflowOperationHandler#start(org.opencastproject.workflow.api.WorkflowInstance,
* JobContext)
*/
@Override
public WorkflowOperationResult start(final WorkflowInstance workflowInstance, JobContext context)
throws WorkflowOperationException {
logger.debug("Running distribution workflow operation");
MediaPackage mediaPackage = workflowInstance.getMediaPackage();
// Check which tags have been configured
String sourceTags = StringUtils.trimToNull(workflowInstance.getCurrentOperation().getConfiguration("source-tags"));
String targetTags = StringUtils.trimToNull(workflowInstance.getCurrentOperation().getConfiguration("target-tags"));
String sourceFlavors = StringUtils.trimToNull(workflowInstance.getCurrentOperation().getConfiguration(
"source-flavors"));
String sourcePriorityFlavors = StringUtils.trimToNull(workflowInstance.getCurrentOperation().getConfiguration(
"source-priority-flavors"));
String storeType = StringUtils.trimToNull(workflowInstance.getCurrentOperation().getConfiguration("store-type"));
AbstractMediaPackageElementSelector<MediaPackageElement> elementSelector;
if (sourcePriorityFlavors != null) {
if (sourceFlavors != null || sourceTags != null)
throw new IllegalArgumentException(
"Source-flavors or source-tags can not be used in case of source-priority-flavors property");
elementSelector = new SimpleFlavorPrioritySelector();
for (String flavor : asList(sourcePriorityFlavors)) {
elementSelector.addFlavor(MediaPackageElementFlavor.parseFlavor(flavor));
}
} else {
if (sourceTags == null && sourceFlavors == null) {
logger.warn("No tags or flavors have been specified");
return createResult(mediaPackage, Action.CONTINUE);
}
elementSelector = new SimpleElementSelector();
if (sourceFlavors != null) {
for (String flavor : asList(sourceFlavors)) {
elementSelector.addFlavor(MediaPackageElementFlavor.parseFlavor(flavor));
}
}
if (sourceTags != null) {
for (String tag : asList(sourceTags)) {
elementSelector.addTag(tag);
}
}
}
try {
Set<String> elementIds = new HashSet<String>();
// Look for elements matching the tag
Collection<MediaPackageElement> elements = elementSelector.select(mediaPackage, false);
for (MediaPackageElement elem : elements) {
elementIds.add(elem.getIdentifier());
}
// Also distribute the security configuration
// -----
// This was removed in the meantime by a fix for MH-8515, but could now be used again.
// -----
List<Attachment> securityAttachments = new ArrayList<Attachment>();
securityAttachments.addAll(authorizationService.getAclAttachments(mediaPackage, some(AclScope.Episode)));
securityAttachments.addAll(authorizationService.getAclAttachments(mediaPackage, some(AclScope.Series)));
for (Attachment a : securityAttachments) {
elementIds.add(a.getIdentifier());
}
// Finally, push the elements to the distribution channel
List<String> targetTagList = asList(targetTags);
Map<String, Job> jobs = new HashMap<String, Job>(elementIds.size());
try {
for (String elementId : elementIds) {
Job job = distributionService.distribute(CHANNEL_ID, mediaPackage, elementId);
if (job == null)
continue;
jobs.put(elementId, job);
}
} catch (DistributionException e) {
throw new WorkflowOperationException(e);
}
if (jobs.size() < 1) {
logger.info("No mediapackage element was found for distribution");
return createResult(mediaPackage, Action.CONTINUE);
}
// Wait until all distribution jobs have returned
if (!waitForStatus(jobs.values().toArray(new Job[jobs.size()])).isSuccess()) {
throw new WorkflowOperationException("One of the distribution jobs did not complete successfully");
}
// All the jobs have passed, let's update the mediapackage with references to the distributed elements
for (Map.Entry<String, Job> entry : jobs.entrySet()) {
String elementId = entry.getKey();
Job job = serviceRegistry.getJob(entry.getValue().getId());
MediaPackageElement element = mediaPackage.getElementById(elementId);
// If there is no payload, then the item has not been distributed.
if (job.getPayload() == null) {
continue;
}
MediaPackageElement newElement = null;
try {
newElement = MediaPackageElementParser.getFromXml(job.getPayload());
} catch (MediaPackageException e) {
throw new WorkflowOperationException(e);
}
// If the job finished successfully, but returned no new element, the channel simply doesn't support this
// kind of element. So we just keep on looping.
if (newElement == null) {
continue;
}
newElement.setIdentifier(null);
MediaPackageReference ref = element.getReference();
if (ref != null && mediaPackage.getElementByReference(ref) != null) {
newElement.setReference((MediaPackageReference) ref.clone());
mediaPackage.add(newElement);
} else {
mediaPackage.addDerived(newElement, element);
if (ref != null) {
Map<String, String> props = ref.getProperties();
newElement.getReference().getProperties().putAll(props);
}
}
for (String tag : targetTagList) {
if (StringUtils.trimToNull(tag) == null)
continue;
newElement.addTag(tag);
}
}
logger.debug("Distribute operation completed");
} catch (Exception e) {
if (e instanceof WorkflowOperationException) {
throw (WorkflowOperationException) e;
} else {
throw new WorkflowOperationException(e);
}
}
return createResult(mediaPackage, Action.CONTINUE);
}
}