View Javadoc
1   /*
2    * Licensed to The Apereo Foundation under one or more contributor license
3    * agreements. See the NOTICE file distributed with this work for additional
4    * information regarding copyright ownership.
5    *
6    *
7    * The Apereo Foundation licenses this file to you under the Educational
8    * Community License, Version 2.0 (the "License"); you may not use this file
9    * except in compliance with the License. You may obtain a copy of the License
10   * at:
11   *
12   *   http://opensource.org/licenses/ecl2.txt
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
17   * License for the specific language governing permissions and limitations under
18   * the License.
19   *
20   */
21  
22  package org.opencastproject.ingestdownloadservice.impl;
23  
24  import org.opencastproject.ingestdownloadservice.api.IngestDownloadService;
25  import org.opencastproject.job.api.AbstractJobProducer;
26  import org.opencastproject.job.api.Job;
27  import org.opencastproject.mediapackage.MediaPackage;
28  import org.opencastproject.mediapackage.MediaPackageElement;
29  import org.opencastproject.mediapackage.MediaPackageException;
30  import org.opencastproject.mediapackage.MediaPackageParser;
31  import org.opencastproject.mediapackage.selector.AbstractMediaPackageElementSelector;
32  import org.opencastproject.mediapackage.selector.SimpleElementSelector;
33  import org.opencastproject.security.api.OrganizationDirectoryService;
34  import org.opencastproject.security.api.SecurityService;
35  import org.opencastproject.security.api.TrustedHttpClient;
36  import org.opencastproject.security.api.TrustedHttpClientException;
37  import org.opencastproject.security.api.UserDirectoryService;
38  import org.opencastproject.serviceregistry.api.ServiceRegistration;
39  import org.opencastproject.serviceregistry.api.ServiceRegistry;
40  import org.opencastproject.serviceregistry.api.ServiceRegistryException;
41  import org.opencastproject.util.NotFoundException;
42  import org.opencastproject.util.UrlSupport;
43  import org.opencastproject.workingfilerepository.api.WorkingFileRepository;
44  import org.opencastproject.workspace.api.Workspace;
45  
46  import org.apache.commons.io.FilenameUtils;
47  import org.apache.commons.lang3.StringUtils;
48  import org.apache.http.HttpResponse;
49  import org.apache.http.HttpStatus;
50  import org.apache.http.client.methods.HttpDelete;
51  import org.osgi.service.component.annotations.Component;
52  import org.osgi.service.component.annotations.Reference;
53  import org.slf4j.Logger;
54  import org.slf4j.LoggerFactory;
55  
56  import java.io.File;
57  import java.io.FileInputStream;
58  import java.io.IOException;
59  import java.io.InputStream;
60  import java.net.URI;
61  import java.util.ArrayList;
62  import java.util.List;
63  import java.util.Optional;
64  
65  /**
66   * A simple tutorial class to learn about Opencast Services
67   */
68  @Component(
69      immediate = true,
70      service = IngestDownloadService.class,
71      property = {
72          "service.description=Ingest download service",
73          "service.pid=org.opencastproject.ingestdownloadservice.impl.IngestDownloadServiceImpl"
74      }
75  )
76  public class IngestDownloadServiceImpl extends AbstractJobProducer implements IngestDownloadService {
77  
78    public enum Operation {
79      Download
80    }
81  
82    /**
83     * The module specific logger
84     */
85    private static final Logger logger = LoggerFactory.getLogger(IngestDownloadServiceImpl.class);
86  
87    /**
88     * Reference to the receipt service
89     */
90    private ServiceRegistry serviceRegistry = null;
91  
92    /**
93     * The security service
94     */
95    private SecurityService securityService = null;
96  
97    /**
98     * The user directory service
99     */
100   private UserDirectoryService userDirectoryService = null;
101 
102   /**
103    * The organization directory service
104    */
105   private OrganizationDirectoryService organizationDirectoryService = null;
106 
107   /**
108    * The workspace service
109    */
110   private Workspace workspace;
111 
112   /**
113    * The http client to use when connecting to remote servers
114    */
115   private TrustedHttpClient client = null;
116 
117   /**
118    * Creates a new abstract job producer for jobs of the given type.
119    *
120    */
121   public IngestDownloadServiceImpl() {
122     super(JOB_TYPE);
123   }
124 
125   /**
126    * Sets the workspace to use.
127    *
128    * @param workspace the workspace
129    */
130   @Reference
131   public void setWorkspace(Workspace workspace) {
132     this.workspace = workspace;
133   }
134 
135   /**
136    * Sets the receipt service
137    *
138    * @param serviceRegistry the service registry
139    */
140   @Reference
141   public void setServiceRegistry(ServiceRegistry serviceRegistry) {
142     this.serviceRegistry = serviceRegistry;
143   }
144 
145   /**
146    * {@inheritDoc}
147    *
148    * @see org.opencastproject.job.api.AbstractJobProducer#getServiceRegistry()
149    */
150   @Override
151   protected ServiceRegistry getServiceRegistry() {
152     return serviceRegistry;
153   }
154 
155   /**
156    * {@inheritDoc}
157    *
158    * @see org.opencastproject.job.api.AbstractJobProducer#getSecurityService()
159    */
160   @Override
161   protected SecurityService getSecurityService() {
162     return securityService;
163   }
164 
165   /**
166    * Callback for setting the security service.
167    *
168    * @param securityService the securityService to set
169    */
170   @Reference
171   public void setSecurityService(SecurityService securityService) {
172     this.securityService = securityService;
173   }
174 
175   /**
176    * Callback for setting the user directory service.
177    *
178    * @param userDirectoryService the userDirectoryService to set
179    */
180   @Reference
181   public void setUserDirectoryService(UserDirectoryService userDirectoryService) {
182     this.userDirectoryService = userDirectoryService;
183   }
184 
185   /**
186    * {@inheritDoc}
187    *
188    * @see org.opencastproject.job.api.AbstractJobProducer#getUserDirectoryService()
189    */
190   @Override
191   protected UserDirectoryService getUserDirectoryService() {
192     return userDirectoryService;
193   }
194 
195   /**
196    * {@inheritDoc}
197    *
198    * @see org.opencastproject.job.api.AbstractJobProducer#getOrganizationDirectoryService()
199    */
200   @Override
201   protected OrganizationDirectoryService getOrganizationDirectoryService() {
202     return organizationDirectoryService;
203   }
204 
205   /**
206    * Sets a reference to the organization directory service.
207    *
208    * @param organizationDirectory the organization directory
209    */
210   @Reference
211   public void setOrganizationDirectoryService(OrganizationDirectoryService organizationDirectory) {
212     this.organizationDirectoryService = organizationDirectory;
213   }
214 
215   @Override
216   public Job ingestDownload(MediaPackage mediaPackage, String sourceFlavors, String sourceTags, boolean deleteExternal,
217           boolean tagsAndFlavor) throws ServiceRegistryException {
218 
219     final List<String> paramList = new ArrayList<>(5);
220     paramList.add(MediaPackageParser.getAsXml(mediaPackage));
221     paramList.add(sourceFlavors);
222     paramList.add(sourceTags);
223     paramList.add(Boolean.toString(deleteExternal));
224     paramList.add(Boolean.toString(tagsAndFlavor));
225 
226     return serviceRegistry.createJob(JOB_TYPE, Operation.Download.toString(), paramList);
227 
228   }
229 
230   @Override
231   protected String process(Job job) throws MediaPackageException, IOException {
232     final List<String> arguments = new ArrayList<>(job.getArguments());
233 
234     final MediaPackage mediaPackage = MediaPackageParser.getFromXml(arguments.get(0));
235     final String sourceFlavors = arguments.get(1);
236     final String sourceTags = arguments.get(2);
237     final boolean deleteExternal = Boolean.parseBoolean(arguments.get(3));
238     final boolean tagsAndFlavor = Boolean.parseBoolean(arguments.get(4));
239 
240     // building elementSelector with tags and flavors
241     AbstractMediaPackageElementSelector<MediaPackageElement> elementSelector = new SimpleElementSelector();
242     for (String tag : StringUtils.split(sourceTags, ", ")) {
243       elementSelector.addTag(tag);
244     }
245     for (String flavor : StringUtils.split(sourceFlavors, ", ")) {
246       elementSelector.addFlavor(flavor);
247     }
248 
249     final String baseUrl = workspace.getBaseUri().toString();
250 
251     List<URI> externalUris = new ArrayList<>();
252     for (MediaPackageElement element : elementSelector.select(mediaPackage, tagsAndFlavor)) {
253       if (element.getURI() == null) {
254         continue;
255       }
256 
257       if (element.getElementType() == MediaPackageElement.Type.Publication) {
258         logger.debug("Skipping publication {} from media package {}", element.getIdentifier(),
259                      mediaPackage.getIdentifier());
260         continue;
261       }
262 
263       if (element.getURI().toString().startsWith(baseUrl)) {
264         logger.info("Skipping already existing element {}", element.getURI());
265         continue;
266       }
267 
268       // Download the external URI
269       File file;
270       try {
271         file = workspace.get(element.getURI());
272       } catch (NotFoundException e) {
273         logger.warn("Unable to download the external element {}", element.getURI());
274         continue;
275       }
276 
277       // Put to working file repository and rewrite URI on element
278       final URI originalUri = element.getURI();
279       try (InputStream in = new FileInputStream(file)) {
280         final String filename = FilenameUtils.getName(element.getURI().getPath());
281         final URI uri = workspace.put(mediaPackage.getIdentifier().toString(), element.getIdentifier(), filename, in);
282         element.setURI(uri);
283       } finally {
284         try {
285           workspace.delete(originalUri);
286         } catch (Exception e) {
287           logger.warn("Unable to delete ingest-downloaded element {}", element.getURI(), e);
288         }
289       }
290 
291       logger.info("Downloaded the external element {}", originalUri);
292 
293       // Store original URI for deletion
294       externalUris.add(originalUri);
295     }
296 
297     if (!deleteExternal || externalUris.size() == 0)
298       return MediaPackageParser.getAsXml(mediaPackage);
299 
300     // Find all external working file repository base Urls
301     logger.debug("Assembling list of external working file repositories");
302     List<String> externalWfrBaseUrls = new ArrayList<>();
303     try {
304       final String wfrServiceType = WorkingFileRepository.SERVICE_TYPE;
305       for (ServiceRegistration reg : serviceRegistry.getServiceRegistrationsByType(wfrServiceType)) {
306         if (baseUrl.startsWith(reg.getHost())) {
307           logger.trace("Skipping local working file repository");
308           continue;
309         }
310         externalWfrBaseUrls.add(UrlSupport.concat(reg.getHost(), reg.getPath()));
311       }
312       logger.debug("{} external working file repositories found", externalWfrBaseUrls.size());
313     } catch (ServiceRegistryException e) {
314       logger.error("Unable to load WFR services from service registry", e);
315     }
316 
317     // try deleting files from external working file reposities
318     for (URI uri : externalUris) {
319 
320       String elementUri = uri.toString();
321 
322       // Delete external working file repository URI's
323       Optional<String> wfrBaseUrl = externalWfrBaseUrls.parallelStream().filter(elementUri::startsWith).findAny();
324 
325       if (!wfrBaseUrl.isPresent()) {
326         logger.debug("Unable to delete {}, no working file repository found for this URI", elementUri);
327         continue;
328       }
329 
330       final String deleteUrl;
331       if (uri.getPath().startsWith(WorkingFileRepository.MEDIAPACKAGE_PATH_PREFIX)) {
332         deleteUrl = elementUri.substring(0, elementUri.lastIndexOf("/"));
333       } else if (uri.getPath().startsWith(WorkingFileRepository.COLLECTION_PATH_PREFIX)) {
334         deleteUrl = elementUri;
335       } else {
336         logger.info("Unable to handle working file repository URI {}", elementUri);
337         continue;
338       }
339       HttpDelete delete = new HttpDelete(deleteUrl);
340 
341       HttpResponse response = null;
342       try {
343         response = client.execute(delete);
344         final int statusCode = response.getStatusLine().getStatusCode();
345         if (statusCode == HttpStatus.SC_NO_CONTENT || statusCode == HttpStatus.SC_OK) {
346           logger.info("Successfully deleted external URI {}", delete.getURI());
347         } else if (statusCode == HttpStatus.SC_NOT_FOUND) {
348           logger.debug("External URI {} has already been deleted", delete.getURI());
349         } else {
350           logger.warn("Unable to delete external URI {}, status code '{}' returned", delete.getURI(), statusCode);
351         }
352       } catch (TrustedHttpClientException e) {
353         logger.warn("Unable to execute DELETE request on external URI {}", delete.getURI());
354       } finally {
355         client.close(response);
356       }
357     }
358 
359     return MediaPackageParser.getAsXml(mediaPackage);
360 
361   }
362 }
363