Java tutorial
/** * Copyright 2009, 2010 The Regents of the University of California * Licensed 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://www.osedu.org/licenses/ECL-2.0 * * 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.download; import org.opencastproject.distribution.api.DistributionException; import org.opencastproject.distribution.api.DistributionService; import org.opencastproject.job.api.AbstractJobProducer; import org.opencastproject.job.api.Job; import org.opencastproject.mediapackage.MediaPackage; import org.opencastproject.mediapackage.MediaPackageElement; import org.opencastproject.mediapackage.MediaPackageElementParser; import org.opencastproject.mediapackage.MediaPackageException; import org.opencastproject.mediapackage.MediaPackageParser; import org.opencastproject.security.api.OrganizationDirectoryService; import org.opencastproject.security.api.SecurityService; import org.opencastproject.security.api.TrustedHttpClient; import org.opencastproject.security.api.UserDirectoryService; import org.opencastproject.serviceregistry.api.ServiceRegistry; import org.opencastproject.serviceregistry.api.ServiceRegistryException; import org.opencastproject.util.FileSupport; import org.opencastproject.util.NotFoundException; import org.opencastproject.util.PathSupport; import org.opencastproject.util.UrlSupport; import org.opencastproject.workspace.api.Workspace; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpHead; import org.osgi.service.component.ComponentContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javax.servlet.http.HttpServletResponse; /** * Distributes media to the local media delivery directory. */ public class DownloadDistributionService extends AbstractJobProducer implements DistributionService { /** Logging facility */ private static final Logger logger = LoggerFactory.getLogger(DownloadDistributionService.class); /** List of available operations on jobs */ private enum Operation { Distribute, Retract } /** Receipt type */ public static final String JOB_TYPE = "org.opencastproject.distribution.download"; /** Default distribution directory */ public static final String DEFAULT_DISTRIBUTION_DIR = "opencast" + File.separator + "static"; /** Timeout in millis for checking distributed file request */ private static final long TIMEOUT = 10000L; /** Interval time in millis for checking distributed file request */ private static final long INTERVAL = 300L; /** Path to the distribution directory */ protected File distributionDirectory = null; /** this media download service's base URL */ protected String serviceUrl = null; /** The remote service registry */ protected ServiceRegistry serviceRegistry = null; /** The workspace reference */ protected Workspace workspace = null; /** The security service */ protected SecurityService securityService = null; /** The user directory service */ protected UserDirectoryService userDirectoryService = null; /** The organization directory service */ protected OrganizationDirectoryService organizationDirectoryService = null; /** The trusted HTTP client */ private TrustedHttpClient trustedHttpClient; /** * Creates a new instance of the download distribution service. */ public DownloadDistributionService() { super(JOB_TYPE); } /** * Activate method for this OSGi service implementation. * * @param cc * the OSGi component context */ protected void activate(ComponentContext 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); } @Override public Job distribute(MediaPackage mediapackage, String elementId) throws DistributionException, MediaPackageException { if (mediapackage == null) throw new MediaPackageException("Mediapackage must be specified"); if (elementId == null) throw new MediaPackageException("Element ID must be specified"); try { return serviceRegistry.createJob(JOB_TYPE, Operation.Distribute.toString(), Arrays.asList(MediaPackageParser.getAsXml(mediapackage), elementId)); } catch (ServiceRegistryException e) { throw new DistributionException("Unable to create a job", e); } } /** * Distributes the mediapackage's element to the location that is returned by the concrete implementation. In * addition, a representation of the distributed element is added to the mediapackage. * * @see org.opencastproject.distribution.api.DistributionService#distribute(org.opencastproject.mediapackage.MediaPackage, * String) * @throws org.opencastproject.distribution.api.DistributionException * in case of an error */ protected MediaPackageElement distribute(Job job, MediaPackage mediapackage, String elementId) throws DistributionException { return distributeElement(mediapackage, elementId); } /** * Distribute a Mediapackage element to the download distribution service. * * @param mediapackage * The media package that contains the element to distribute. * @param elementId * The id of the element that should be distributed contained within the media package. * @return A reference to the MediaPackageElement that has been distributed. * @throws DistributionException * Thrown if the parent directory of the MediaPackageElement cannot be created, if the MediaPackageElement * cannot be copied or another unexpected exception occurs. */ public MediaPackageElement distributeElement(MediaPackage mediapackage, String elementId) throws DistributionException { if (mediapackage == null) throw new IllegalArgumentException("Mediapackage must be specified"); if (elementId == null) throw new IllegalArgumentException("Element ID must be specified"); String mediaPackageId = mediapackage.getIdentifier().compact(); MediaPackageElement element = mediapackage.getElementById(elementId); // Make sure the element exists if (mediapackage.getElementById(elementId) == null) throw new IllegalStateException("No element " + elementId + " found in mediapackage"); try { File source; try { source = workspace.get(element.getURI()); } catch (NotFoundException e) { throw new DistributionException("Unable to find " + element.getURI() + " in the workspace", e); } catch (IOException e) { throw new DistributionException("Error loading " + element.getURI() + " from the workspace", e); } File destination = getDistributionFile(mediapackage, element); // Put the file in place try { FileUtils.forceMkdir(destination.getParentFile()); } catch (IOException e) { throw new DistributionException("Unable to create " + destination.getParentFile(), e); } logger.info("Distributing {} to {}", elementId, destination); try { FileSupport.link(source, destination, true); } catch (IOException e) { throw new DistributionException("Unable to copy " + source + " to " + destination, e); } // Create a representation of the distributed file in the mediapackage MediaPackageElement distributedElement = (MediaPackageElement) element.clone(); try { distributedElement.setURI(getDistributionUri(mediaPackageId, element)); } catch (URISyntaxException e) { throw new DistributionException("Distributed element produces an invalid URI", e); } distributedElement.setIdentifier(null); logger.info("Finished distribution of {}", element); URI uri = distributedElement.getURI(); long now = 0L; while (true) { HttpResponse response = trustedHttpClient.execute(new HttpHead(uri)); if (response.getStatusLine().getStatusCode() == HttpServletResponse.SC_OK) break; if (now < TIMEOUT) { try { Thread.sleep(INTERVAL); now += INTERVAL; continue; } catch (Exception e) { throw new RuntimeException(e); } } logger.warn("Status code of distributed file {}: {}", uri, response.getStatusLine().getStatusCode()); throw new DistributionException("Unable to load distributed file " + uri.toString()); } return distributedElement; } catch (Exception e) { logger.warn("Error distributing " + element, e); if (e instanceof DistributionException) { throw (DistributionException) e; } else { throw new DistributionException(e); } } } @Override public Job retract(MediaPackage mediaPackage, String elementId) throws DistributionException { if (mediaPackage == null) throw new IllegalArgumentException("Mediapackage must be specified"); if (elementId == null) throw new IllegalArgumentException("Element ID must be specified"); try { List<String> arguments = new ArrayList<String>(); arguments.add(MediaPackageParser.getAsXml(mediaPackage)); arguments.add(elementId); return serviceRegistry.createJob(JOB_TYPE, Operation.Retract.toString(), arguments); } catch (ServiceRegistryException e) { throw new DistributionException("Unable to create a job", e); } } /** * Retract a media package element from the distribution channel. The retracted element must not necessarily be the * one given as parameter <code>elementId</code>. Instead, the element's distribution URI will be calculated and then * in turn be matched against each element of the package. This way you are able to retract elements by providing the * "original" element here. * * @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 * @throws org.opencastproject.distribution.api.DistributionException * in case of an error */ protected MediaPackageElement retract(Job job, MediaPackage mediapackage, String elementId) throws DistributionException { if (mediapackage == null) throw new IllegalArgumentException("Mediapackage must be specified"); if (elementId == null) throw new IllegalArgumentException("Element ID must be specified"); // Make sure the element exists MediaPackageElement element = mediapackage.getElementById(elementId); if (element == null) throw new IllegalStateException("No element " + elementId + " found in mediapackage"); // Find the element that has been created as part of the distribution process String mediaPackageId = mediapackage.getIdentifier().compact(); URI distributedURI = null; MediaPackageElement distributedElement = null; try { distributedURI = getDistributionUri(mediaPackageId, element); for (MediaPackageElement e : mediapackage.getElements()) { if (distributedURI.equals(e.getURI())) { distributedElement = e; break; } } } catch (URISyntaxException e) { throw new DistributionException("Retracted element produces an invalid URI", e); } // Has this element been distributed? if (distributedElement == null) return null; String mediapackageId = mediapackage.getIdentifier().compact(); try { File mediapackageDir = getMediaPackageDirectory(mediapackageId); File elementDir = getDistributionFile(mediapackage, element); logger.info("Retracting element {} from {}", distributedElement, elementDir); // Does the file exist? If not, the current element has not been distributed to this channel // or has been removed otherwise if (!elementDir.exists()) { logger.warn("Unable to delete element from {}", elementDir); return distributedElement; } // Try to remove the file and - if possible - the parent folder FileUtils.forceDelete(elementDir.getParentFile()); if (mediapackageDir.list().length == 0) { FileSupport.delete(mediapackageDir); } logger.info("Finished rectracting element {} of media package {}", elementId, mediapackageId); return distributedElement; } catch (Exception e) { logger.warn("Error retracting element " + elementId + " of mediapackage " + mediapackageId, e); if (e instanceof DistributionException) { throw (DistributionException) e; } else { throw new DistributionException(e); } } } /** * {@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); MediaPackage mediapackage = MediaPackageParser.getFromXml(arguments.get(0)); String elementId = arguments.get(1); switch (op) { case Distribute: MediaPackageElement distributedElement = distribute(job, 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); } } /** * Gets the destination file to copy the contents of a mediapackage element. * * @param mediaPackage * the media package * @param element * The mediapackage element being distributed * @return The file to copy the content to */ protected File getDistributionFile(MediaPackage mediaPackage, MediaPackageElement element) { String elementId = element.getIdentifier(); String fileName = FilenameUtils.getName(element.getURI().toString()); String directoryName = distributionDirectory.getAbsolutePath(); String destinationFileName = PathSupport.concat( new String[] { directoryName, mediaPackage.getIdentifier().compact(), elementId, fileName }); return new File(destinationFileName); } /** * Gets the URI for the element to be distributed. * * @param mediaPackageId * the mediapackage identifier * @param element * The mediapackage element being distributed * @return The resulting URI after distribution * @throws URISyntaxException * if the concrete implementation tries to create a malformed uri */ protected URI getDistributionUri(String mediaPackageId, MediaPackageElement element) throws URISyntaxException { String elementId = element.getIdentifier(); String fileName = FilenameUtils.getName(element.getURI().toString()); String destinationURI = UrlSupport.concat(serviceUrl, mediaPackageId, elementId, fileName); return new URI(destinationURI); } /** * Gets the directory containing the distributed files for this mediapackage. * * @param mediaPackageId * the mediapackage ID * @return the filesystem directory */ protected File getMediaPackageDirectory(String mediaPackageId) { return new File(distributionDirectory, mediaPackageId); } /** * Callback for the OSGi environment to set the workspace reference. * * @param workspace * the workspace */ protected void setWorkspace(Workspace workspace) { this.workspace = workspace; } /** * Callback for the OSGi environment to set the service registry reference. * * @param serviceRegistry * the service registry */ protected void setServiceRegistry(ServiceRegistry serviceRegistry) { this.serviceRegistry = serviceRegistry; } /** * {@inheritDoc} * * @see org.opencastproject.job.api.AbstractJobProducer#getServiceRegistry() */ @Override protected ServiceRegistry getServiceRegistry() { return serviceRegistry; } /** * Callback for setting the security service. * * @param securityService * the securityService to set */ public void setSecurityService(SecurityService securityService) { this.securityService = securityService; } /** * Callback for setting the trusted HTTP client. * * @param trustedHttpClient * the trusted HTTP client to set */ public void setTrustedHttpClient(TrustedHttpClient trustedHttpClient) { this.trustedHttpClient = trustedHttpClient; } /** * Callback for setting the user directory service. * * @param userDirectoryService * the userDirectoryService to set */ public void setUserDirectoryService(UserDirectoryService userDirectoryService) { this.userDirectoryService = userDirectoryService; } /** * Sets a reference to the organization directory service. * * @param organizationDirectory * the organization directory */ public void setOrganizationDirectoryService(OrganizationDirectoryService organizationDirectory) { this.organizationDirectoryService = organizationDirectory; } /** * {@inheritDoc} * * @see org.opencastproject.job.api.AbstractJobProducer#getSecurityService() */ @Override protected SecurityService getSecurityService() { return securityService; } /** * {@inheritDoc} * * @see org.opencastproject.job.api.AbstractJobProducer#getUserDirectoryService() */ @Override protected UserDirectoryService getUserDirectoryService() { return userDirectoryService; } /** * {@inheritDoc} * * @see org.opencastproject.job.api.AbstractJobProducer#getOrganizationDirectoryService() */ @Override protected OrganizationDirectoryService getOrganizationDirectoryService() { return organizationDirectoryService; } }