/** * 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.distribution.acl; import static org.opencastproject.util.RequireUtil.notNull; import org.opencastproject.distribution.api.AbstractDistributionService; import org.opencastproject.distribution.api.DistributionException; import org.opencastproject.distribution.api.DistributionService; import org.opencastproject.job.api.Job; 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.MediaPackageParser; import org.opencastproject.serviceregistry.api.ServiceRegistryException; import org.opencastproject.util.FileSupport; import org.opencastproject.util.LoadUtil; import org.opencastproject.util.NotFoundException; import org.opencastproject.util.OsgiUtil; import org.opencastproject.util.PathSupport; import org.apache.commons.io.FileUtils; import org.osgi.service.cm.ConfigurationException; import org.osgi.service.cm.ManagedService; import org.osgi.service.component.ComponentContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.Dictionary; import java.util.List; /** * Distributes an access control list to control media to the local media delivery directory. */ public class AclDistributionService extends AbstractDistributionService implements DistributionService, ManagedService { /** Logging facility */ private static final Logger logger = LoggerFactory.getLogger(AclDistributionService.class); /** List of available operations on jobs */ private enum Operation { Distribute, Retract }; /** Receipt type */ public static final String JOB_TYPE = "org.opencastproject.distribution.acl"; /** The load on the system introduced by creating a distribute job */ public static final float DEFAULT_DISTRIBUTE_JOB_LOAD = 0.1f; /** The load on the system introduced by creating a retract job */ public static final float DEFAULT_RETRACT_JOB_LOAD = 1.0f; /** The key to look for in the service configuration file to override the {@link DEFAULT_DISTRIBUTE_JOB_LOAD} */ public static final String DISTRIBUTE_JOB_LOAD_KEY = "job.load.acl.distribute"; /** The key to look for in the service configuration file to override the {@link DEFAULT_RETRACT_JOB_LOAD} */ public static final String RETRACT_JOB_LOAD_KEY = "job.load.acl.retract"; /** The load on the system introduced by creating a distribute job */ private float distributeJobLoad = DEFAULT_DISTRIBUTE_JOB_LOAD; /** The load on the system introduced by creating a retract job */ private float retractJobLoad = DEFAULT_RETRACT_JOB_LOAD; /** * Creates a new instance of the download distribution service. */ public AclDistributionService() { super(JOB_TYPE); } /** * Activate method for this OSGi service implementation. * * @param cc * the OSGi component context */ @Override public void activate(ComponentContext cc) { super.activate(cc); serviceUrl = cc.getBundleContext().getProperty("org.opencastproject.download.url"); if (serviceUrl == null) throw new IllegalStateException("Download url must be set (org.opencastproject.download.url)"); String ccDistributionDirectory = cc.getBundleContext().getProperty("org.opencastproject.download.directory"); if (ccDistributionDirectory == null) throw new IllegalStateException("Distribution directory must be set (org.opencastproject.download.directory)"); this.distributionDirectory = new File(ccDistributionDirectory); logger.info("Download distribution directory is {}", distributionDirectory); this.distributionChannel = OsgiUtil.getComponentContextProperty(cc, CONFIG_KEY_STORE_TYPE); } public String getDistributionType() { return this.distributionChannel; } @Override public Job distribute(String channelId, MediaPackage mediapackage, String elementId) throws DistributionException, MediaPackageException { notNull(mediapackage, "mediapackage"); notNull(elementId, "elementId"); notNull(channelId, "channelId"); try { return serviceRegistry.createJob(JOB_TYPE, Operation.Distribute.toString(), Arrays.asList(channelId, MediaPackageParser.getAsXml(mediapackage), elementId), distributeJobLoad); } catch (ServiceRegistryException e) { throw new DistributionException("Unable to create a job", e); } } protected MediaPackageElement distributeElement(String channelId, MediaPackage mediaPackage, String elementId) throws DistributionException, MediaPackageException { try { File source = getAclXmlAttachmentFile(mediaPackage); if (source == null) { throw new DistributionException("No available acl for mediapackage " + mediaPackage); } File destination = new File(PathSupport.concat(new String[] { distributionDirectory.getAbsolutePath(), mediaPackage.getIdentifier().compact(), elementId, elementId + ".acl" })); // Create the parent directory if it doesn't exist. try { FileUtils.forceMkdir(destination.getParentFile()); } catch (IOException e) { throw new DistributionException("Unable to create " + destination.getParentFile(), e); } logger.info("Distributing {} to {}", elementId, destination); // Copy the acl from source to destination. try { FileSupport.copy(source, destination); } catch (IOException e) { throw new DistributionException("Unable to copy " + source + " to " + destination, e); } return (MediaPackageElement)getAclXmlAttachment(mediaPackage); } catch (Exception e) { logger.warn("Could not distribute acl XML with " + elementId + " from " + mediaPackage.getIdentifier().compact(), e); return null; } } /** * @param mediaPackage The media package to find the attachment that has an ACL. * @return Returns the attachment if possible or null. */ private Attachment getAclXmlAttachment(MediaPackage mediaPackage) { Attachment[] aclAttachments = mediaPackage.getAttachments(new MediaPackageElementFlavor("text", "acl")); if (aclAttachments.length > 0) { return aclAttachments[0]; } else { return null; } } /** * @param mediaPackage * The media package to find the acl xml. * @return Returns a File object representing the attachment acl xml * @throws DistributionException * Thrown if it is unable to access the acl xml. */ private File getAclXmlAttachmentFile(MediaPackage mediaPackage) throws DistributionException { File aclSourceFile = null; Attachment acl = getAclXmlAttachment(mediaPackage); if (acl != null) { try { aclSourceFile = workspace.get(acl.getURI()); } catch (NotFoundException e) { throw new DistributionException("Unable to find " + acl.getURI() + " in the workspace", e); } catch (IOException e) { throw new DistributionException("Error loading " + acl.getURI() + " from the workspace", e); } } return aclSourceFile; } @Override public Job retract(String channelId, MediaPackage mediapackage, String elementId) throws DistributionException { notNull(mediapackage, "mediapackage"); notNull(elementId, "elementId"); notNull(channelId, "channelId"); try { return serviceRegistry.createJob(JOB_TYPE, Operation.Retract.toString(), Arrays.asList(channelId, MediaPackageParser.getAsXml(mediapackage), elementId), retractJobLoad); } catch (ServiceRegistryException e) { throw new DistributionException("Unable to create a job", e); } } /** * Retracts the mediapackage with the given identifier from the distribution channel. * * @param job * the associated job * @param mediapackage * the mediapackage * @param elementId * the element identifier * @return the retracted element or <code>null</code> if the element was not retracted */ protected MediaPackageElement retract(Job job, MediaPackage mediapackage, String elementId) throws DistributionException { return null; } /** * {@inheritDoc} * * @see org.opencastproject.job.api.AbstractJobProducer#process(org.opencastproject.job.api.Job) */ @Override protected String process(Job job) throws Exception { Operation op = null; String operation = job.getOperation(); List<String> arguments = job.getArguments(); try { op = Operation.valueOf(operation); String channelId = arguments.get(0); MediaPackage mediapackage = MediaPackageParser.getFromXml(arguments.get(1)); String elementId = arguments.get(2); switch (op) { case Distribute: MediaPackageElement distributedElement = distributeElement(channelId, mediapackage, elementId); return (distributedElement != null) ? MediaPackageElementParser.getAsXml(distributedElement) : null; case Retract: MediaPackageElement retractedElement = retract(job, mediapackage, elementId); return (retractedElement != null) ? MediaPackageElementParser.getAsXml(retractedElement) : null; default: throw new IllegalStateException("Don't know how to handle operation '" + operation + "'"); } } catch (IllegalArgumentException e) { throw new ServiceRegistryException("This service can't handle operations of type '" + op + "'", e); } catch (IndexOutOfBoundsException e) { throw new ServiceRegistryException("This argument list for operation '" + op + "' does not meet expectations", e); } catch (Exception e) { throw new ServiceRegistryException("Error handling operation '" + op + "'", e); } } @Override public void updated(@SuppressWarnings("rawtypes") Dictionary properties) throws ConfigurationException { distributeJobLoad = LoadUtil.getConfiguredLoadValue(properties, DISTRIBUTE_JOB_LOAD_KEY, DEFAULT_DISTRIBUTE_JOB_LOAD, serviceRegistry); retractJobLoad = LoadUtil.getConfiguredLoadValue(properties, RETRACT_JOB_LOAD_KEY, DEFAULT_RETRACT_JOB_LOAD, serviceRegistry); } }