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.workingfilerepository.impl;
23  
24  import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
25  import static javax.servlet.http.HttpServletResponse.SC_NOT_MODIFIED;
26  import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
27  import static javax.servlet.http.HttpServletResponse.SC_OK;
28  import static org.opencastproject.util.MimeTypes.getMimeType;
29  import static org.opencastproject.util.RestUtil.R.ok;
30  import static org.opencastproject.util.RestUtil.fileResponse;
31  import static org.opencastproject.util.RestUtil.partialFileResponse;
32  import static org.opencastproject.util.data.Option.none;
33  import static org.opencastproject.util.data.Option.some;
34  import static org.opencastproject.util.doc.rest.RestParameter.Type.FILE;
35  import static org.opencastproject.util.doc.rest.RestParameter.Type.STRING;
36  
37  import org.opencastproject.security.api.SecurityService;
38  import org.opencastproject.serviceregistry.api.ServiceRegistry;
39  import org.opencastproject.util.MimeTypes;
40  import org.opencastproject.util.NotFoundException;
41  import org.opencastproject.util.UnknownFileTypeException;
42  import org.opencastproject.util.doc.rest.RestParameter;
43  import org.opencastproject.util.doc.rest.RestQuery;
44  import org.opencastproject.util.doc.rest.RestResponse;
45  import org.opencastproject.util.doc.rest.RestService;
46  import org.opencastproject.workingfilerepository.api.PathMappable;
47  import org.opencastproject.workingfilerepository.api.WorkingFileRepository;
48  
49  import org.apache.commons.fileupload.FileItemIterator;
50  import org.apache.commons.fileupload.FileItemStream;
51  import org.apache.commons.fileupload.servlet.ServletFileUpload;
52  import org.apache.commons.io.IOUtils;
53  import org.apache.commons.lang3.StringUtils;
54  import org.apache.http.HttpStatus;
55  import org.json.simple.JSONArray;
56  import org.json.simple.JSONObject;
57  import org.osgi.service.component.ComponentContext;
58  import org.osgi.service.component.annotations.Activate;
59  import org.osgi.service.component.annotations.Component;
60  import org.osgi.service.component.annotations.Deactivate;
61  import org.osgi.service.component.annotations.Reference;
62  import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource;
63  import org.slf4j.Logger;
64  import org.slf4j.LoggerFactory;
65  
66  import java.io.File;
67  import java.io.IOException;
68  import java.net.URI;
69  
70  import javax.servlet.http.HttpServletRequest;
71  import javax.ws.rs.DELETE;
72  import javax.ws.rs.FormParam;
73  import javax.ws.rs.GET;
74  import javax.ws.rs.HeaderParam;
75  import javax.ws.rs.POST;
76  import javax.ws.rs.Path;
77  import javax.ws.rs.PathParam;
78  import javax.ws.rs.Produces;
79  import javax.ws.rs.core.Context;
80  import javax.ws.rs.core.MediaType;
81  import javax.ws.rs.core.Response;
82  
83  @Path("/files")
84  @RestService(name = "filerepo", title = "Working File Repository", abstractText = "Stores and retrieves files for use during media processing.", notes = {
85          "All paths above are relative to the REST endpoint base (something like http://your.server/files)",
86          "If the service is down or not working it will return a status 503, this means the the underlying service is "
87                  + "not working and is either restarting or has failed",
88          "A status code 500 means a general failure has occurred which is not recoverable and was not anticipated. In "
89                  + "other words, there is a bug! You should file an error report with your server logs from the time when the "
90                  + "error occurred: <a href=\"https://github.com/opencast/opencast/issues\">Opencast Issue Tracker</a>" })
91  @Component(
92    name = "org.opencastproject.workingfilerepository.impl.WorkingFileRepository",
93    property = {
94      "service.description=Working File Repository REST Endpoint",
95      "opencast.service.type=org.opencastproject.files",
96      "opencast.service.path=/files"
97    },
98    immediate = true,
99    service = { WorkingFileRepositoryRestEndpoint.class, WorkingFileRepository.class, PathMappable.class }
100 )
101 @JaxrsResource
102 public class WorkingFileRepositoryRestEndpoint extends WorkingFileRepositoryImpl {
103 
104   private static final Logger logger = LoggerFactory.getLogger(WorkingFileRepositoryRestEndpoint.class);
105 
106   /**
107    * Callback from OSGi that is called when this service is activated.
108    *
109    * @param cc
110    *          OSGi component context
111    */
112   @Override
113   @Activate
114   public void activate(ComponentContext cc) throws IOException {
115     super.activate(cc);
116   }
117 
118   /**
119    * Callback from OSGi that is called when this service is deactivated.
120    */
121   @Override
122   @Deactivate
123   public void deactivate() {
124     super.deactivate();
125   }
126 
127   /**
128    * Sets the remote service manager.
129    *
130    * @param remoteServiceManager
131    */
132   @Override
133   @Reference
134   public void setRemoteServiceManager(ServiceRegistry remoteServiceManager) {
135     super.setRemoteServiceManager(remoteServiceManager);
136   }
137 
138   @Override
139   @Reference
140   public void setSecurityService(SecurityService securityService) {
141     super.setSecurityService(securityService);
142   }
143 
144   @POST
145   @Produces(MediaType.TEXT_HTML)
146   @Path(WorkingFileRepository.MEDIAPACKAGE_PATH_PREFIX + "{mediaPackageID}/{mediaPackageElementID}")
147   @RestQuery(name = "put", description = "Store a file in working repository under ./mediaPackageID/mediaPackageElementID", returnDescription = "The URL to access the stored file", pathParameters = {
148           @RestParameter(name = "mediaPackageID", description = "the mediapackage identifier", isRequired = true, type = STRING),
149           @RestParameter(name = "mediaPackageElementID", description = "the mediapackage element identifier", isRequired = true, type = STRING) }, responses = { @RestResponse(responseCode = SC_OK, description = "OK, file stored") }, restParameters = { @RestParameter(name = "file", description = "the filename", isRequired = true, type = FILE) })
150   public Response restPut(@PathParam("mediaPackageID") String mediaPackageID,
151           @PathParam("mediaPackageElementID") String mediaPackageElementID, @Context HttpServletRequest request)
152           throws Exception {
153     if (ServletFileUpload.isMultipartContent(request)) {
154       for (FileItemIterator iter = new ServletFileUpload().getItemIterator(request); iter.hasNext();) {
155         FileItemStream item = iter.next();
156         if (item.isFormField()) {
157           continue;
158 
159         }
160         URI url = this.put(mediaPackageID, mediaPackageElementID, item.getName(), item.openStream());
161         return Response.ok(url.toString()).build();
162       }
163     }
164     return Response.serverError().status(400).build();
165   }
166 
167   @POST
168   @Produces(MediaType.TEXT_HTML)
169   @Path(WorkingFileRepository.MEDIAPACKAGE_PATH_PREFIX + "{mediaPackageID}/{mediaPackageElementID}/{filename}")
170   @RestQuery(name = "putWithFilename", description = "Store a file in working repository under ./mediaPackageID/mediaPackageElementID/filename", returnDescription = "The URL to access the stored file", pathParameters = {
171           @RestParameter(name = "mediaPackageID", description = "the mediapackage identifier", isRequired = true, type = STRING),
172           @RestParameter(name = "mediaPackageElementID", description = "the mediapackage element identifier", isRequired = true, type = STRING),
173           @RestParameter(name = "filename", description = "the filename", isRequired = true, type = FILE) }, responses = { @RestResponse(responseCode = SC_OK, description = "OK, file stored") })
174   public Response restPutURLEncoded(@Context HttpServletRequest request,
175           @PathParam("mediaPackageID") String mediaPackageID,
176           @PathParam("mediaPackageElementID") String mediaPackageElementID, @PathParam("filename") String filename,
177           @FormParam("content") String content) throws Exception {
178     String encoding = request.getCharacterEncoding();
179     if (encoding == null)
180       encoding = "utf-8";
181 
182     URI url = this.put(mediaPackageID, mediaPackageElementID, filename, IOUtils.toInputStream(content, encoding));
183     return Response.ok(url.toString()).build();
184   }
185 
186   @POST
187   @Produces(MediaType.TEXT_HTML)
188   @Path(WorkingFileRepository.COLLECTION_PATH_PREFIX + "{collectionId}")
189   @RestQuery(name = "putInCollection", description = "Store a file in working repository under ./collectionId/filename", returnDescription = "The URL to access the stored file", pathParameters = { @RestParameter(name = "collectionId", description = "the colection identifier", isRequired = true, type = STRING) }, restParameters = { @RestParameter(name = "file", description = "the filename", isRequired = true, type = FILE) }, responses = { @RestResponse(responseCode = SC_OK, description = "OK, file stored") })
190   public Response restPutInCollection(@PathParam("collectionId") String collectionId,
191           @Context HttpServletRequest request) throws Exception {
192     if (ServletFileUpload.isMultipartContent(request)) {
193       for (FileItemIterator iter = new ServletFileUpload().getItemIterator(request); iter.hasNext();) {
194         FileItemStream item = iter.next();
195         if (item.isFormField()) {
196           continue;
197 
198         }
199         URI url = this.putInCollection(collectionId, item.getName(), item.openStream());
200         return Response.ok(url.toString()).build();
201       }
202     }
203     return Response.serverError().status(400).build();
204   }
205 
206   @DELETE
207   @Path(WorkingFileRepository.MEDIAPACKAGE_PATH_PREFIX + "{mediaPackageID}/{mediaPackageElementID}")
208   @RestQuery(name = "delete", description = "Remove the file from the working repository under /mediaPackageID/mediaPackageElementID", returnDescription = "No content", pathParameters = {
209           @RestParameter(name = "mediaPackageID", description = "the mediapackage identifier", isRequired = true, type = STRING),
210           @RestParameter(name = "mediaPackageElementID", description = "the mediapackage element identifier", isRequired = true, type = STRING) }, responses = {
211           @RestResponse(responseCode = SC_OK, description = "File deleted"),
212           @RestResponse(responseCode = SC_NOT_FOUND, description = "File did not exist") })
213   public Response restDelete(@PathParam("mediaPackageID") String mediaPackageID,
214           @PathParam("mediaPackageElementID") String mediaPackageElementID) {
215     try {
216       if (delete(mediaPackageID, mediaPackageElementID))
217         return Response.ok().build();
218       else
219         return Response.status(HttpStatus.SC_NOT_FOUND).build();
220     } catch (Exception e) {
221       logger.error("Unable to delete element '{}' from mediapackage '{}'", mediaPackageElementID,
222               mediaPackageID, e);
223       return Response.serverError().entity(e.getMessage()).build();
224     }
225   }
226 
227   @DELETE
228   @Path(WorkingFileRepository.COLLECTION_PATH_PREFIX + "{collectionId}/{fileName}")
229   @RestQuery(name = "deleteFromCollection", description = "Remove the file from the working repository under /collectionId/filename", returnDescription = "No content", pathParameters = {
230           @RestParameter(name = "collectionId", description = "the collection identifier", isRequired = true, type = STRING),
231           @RestParameter(name = "fileName", description = "the file name", isRequired = true, type = STRING) }, responses = {
232           @RestResponse(responseCode = SC_NO_CONTENT, description = "File deleted"),
233           @RestResponse(responseCode = SC_NOT_FOUND, description = "Collection or file not found") })
234   public Response restDeleteFromCollection(@PathParam("collectionId") String collectionId,
235           @PathParam("fileName") String fileName) {
236     try {
237       if (this.deleteFromCollection(collectionId, fileName))
238         return Response.noContent().build();
239       else
240         return Response.status(SC_NOT_FOUND).build();
241     } catch (Exception e) {
242       logger.error("Unable to delete element '{}' from collection '{}'", fileName, collectionId, e);
243       return Response.serverError().entity(e.getMessage()).build();
244     }
245   }
246 
247   @DELETE
248   @Path(WorkingFileRepository.COLLECTION_PATH_PREFIX + "cleanup/{collectionId}/{numberOfDays}")
249   @RestQuery(name = "cleanupOldFilesFromCollection", description = "Remove the files from the working repository under /collectionId that are older than N days", returnDescription = "No content", pathParameters = {
250           @RestParameter(name = "collectionId", description = "the collection identifier", isRequired = true, type = STRING),
251           @RestParameter(name = "numberOfDays", description = "files older than this number of days will be deleted", isRequired = true, type = STRING) }, responses = {
252                   @RestResponse(responseCode = SC_NO_CONTENT, description = "Files deleted"),
253                   @RestResponse(responseCode = SC_NOT_FOUND, description = "Collection not found") })
254   public Response restCleanupOldFilesFromCollection(@PathParam("collectionId") String collectionId,
255           @PathParam("numberOfDays") long days) {
256     try {
257       if (this.cleanupOldFilesFromCollection(collectionId, days))
258         return Response.noContent().build();
259       else
260         return Response.status(SC_NOT_FOUND).build();
261     } catch (Exception e) {
262       logger.error("Unable to delete files older than '{}' days from collection '{}'",
263               days, collectionId, e);
264       return Response.serverError().entity(e.getMessage()).build();
265     }
266   }
267 
268   @DELETE
269   @Path(WorkingFileRepository.COLLECTION_PATH_PREFIX + "cleanup/mediapackage/{numberOfDays}")
270   @RestQuery(name = "cleanupOldFilesFromMediaPackage", description = "Remove files and directories from the working file repository under /mediapackage that are older than N days", returnDescription = "No content", pathParameters = {
271           @RestParameter(name = "numberOfDays", description = "files older than this number of days will be deleted", isRequired = true, type = STRING) }, responses = {
272           @RestResponse(responseCode = SC_NO_CONTENT, description = "Files deleted")})
273   public Response restCleanupOldFilesFromMediaPackage(@PathParam("numberOfDays") long days) {
274     try {
275       this.cleanupOldFilesFromMediaPackage(days);
276       return Response.noContent().build();
277     } catch (Exception e) {
278       return Response.serverError().entity(e.getMessage()).build();
279     }
280   }
281 
282   @GET
283   @Path(WorkingFileRepository.MEDIAPACKAGE_PATH_PREFIX + "{mediaPackageID}/{mediaPackageElementID}")
284   @RestQuery(name = "get", description = "Gets the file from the working repository under /mediaPackageID/mediaPackageElementID", returnDescription = "The file", pathParameters = {
285           @RestParameter(name = "mediaPackageID", description = "the mediapackage identifier", isRequired = true, type = STRING),
286           @RestParameter(name = "mediaPackageElementID", description = "the mediapackage element identifier", isRequired = true, type = STRING) }, responses = {
287           @RestResponse(responseCode = SC_OK, description = "File returned"),
288           @RestResponse(responseCode = SC_NOT_MODIFIED, description = "If file not modified"),
289           @RestResponse(responseCode = SC_NOT_FOUND, description = "Not found") })
290   public Response restGet(@PathParam("mediaPackageID") final String mediaPackageID,
291           @PathParam("mediaPackageElementID") final String mediaPackageElementID,
292           @HeaderParam("If-None-Match") String ifNoneMatch) throws NotFoundException {
293     // Check the If-None-Match header first
294     String md5 = null;
295     try {
296       md5 = getMediaPackageElementDigest(mediaPackageID, mediaPackageElementID);
297       if (StringUtils.isNotBlank(ifNoneMatch) && md5.equals(ifNoneMatch)) {
298         return Response.notModified(md5).build();
299       }
300     } catch (IOException e) {
301       logger.warn("Error reading digest of {}/{}", mediaPackageElementID, mediaPackageElementID);
302     }
303     try {
304       String contentType;
305       File file = getFile(mediaPackageID, mediaPackageElementID);
306       try {
307         contentType = MimeTypes.fromString(file.getPath()).toString();
308       } catch (UnknownFileTypeException e) {
309         contentType = "application/octet-stream";
310       }
311       try {
312         return ok(get(mediaPackageID, mediaPackageElementID), contentType, some(file.length()), none(""));
313       } catch (IOException e) {
314         throw new NotFoundException();
315       }
316     } catch (IllegalStateException e) {
317       logger.error("Unable to provide element '{}' from mediapackage '{}'", mediaPackageElementID,
318               mediaPackageID, e);
319       return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
320     }
321   }
322 
323   @GET
324   @Path(WorkingFileRepository.MEDIAPACKAGE_PATH_PREFIX + "{mediaPackageID}/{mediaPackageElementID}/{fileName}")
325   @RestQuery(name = "getWithFilename", description = "Gets the file from the working repository under /mediaPackageID/mediaPackageElementID/filename", returnDescription = "The file", pathParameters = {
326           @RestParameter(name = "mediaPackageID", description = "the mediapackage identifier", isRequired = true, type = STRING),
327           @RestParameter(name = "mediaPackageElementID", description = "the mediapackage element identifier", isRequired = true, type = STRING),
328           @RestParameter(name = "fileName", description = "the file name", isRequired = true, type = STRING) }, responses = {
329           @RestResponse(responseCode = SC_OK, description = "File returned"),
330           @RestResponse(responseCode = SC_NOT_FOUND, description = "Not found") })
331   public Response restGet(@PathParam("mediaPackageID") String mediaPackageID,
332           @PathParam("mediaPackageElementID") String mediaPackageElementID, @PathParam("fileName") String fileName,
333           @HeaderParam("If-None-Match") String ifNoneMatch, @HeaderParam("Range") String range)
334           throws NotFoundException {
335     String md5 = null;
336     // Check the If-None-Match header first
337     try {
338       md5 = getMediaPackageElementDigest(mediaPackageID, mediaPackageElementID);
339       if (StringUtils.isNotBlank(ifNoneMatch) && md5.equals(ifNoneMatch)) {
340         return Response.notModified(md5).build();
341       }
342     } catch (IOException e) {
343       logger.warn("Error reading digest of {}/{}/{}", mediaPackageElementID, mediaPackageElementID,
344               fileName);
345     }
346 
347     try {
348       if (StringUtils.isNotBlank(range)) {
349         logger.debug("trying to retrieve range: {}", range);
350         return partialFileResponse(getFile(mediaPackageID, mediaPackageElementID), getMimeType(fileName),
351                 some(fileName), range).tag(md5).build();
352 
353       } else {
354         // No If-Non-Match header provided, or the file changed in the meantime
355         return fileResponse(getFile(mediaPackageID, mediaPackageElementID), getMimeType(fileName),
356                 some(fileName)).tag(md5).build();
357       }
358     } catch (Exception e) {
359       logger.error("Unable to provide element '{}' from mediapackage '{}'", mediaPackageElementID,
360               mediaPackageID, e);
361       return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
362     }
363   }
364 
365   @GET
366   @Path(WorkingFileRepository.COLLECTION_PATH_PREFIX + "{collectionId}/{fileName}")
367   @RestQuery(name = "getFromCollection", description = "Gets the file from the working repository under /collectionId/filename", returnDescription = "The file", pathParameters = {
368           @RestParameter(name = "collectionId", description = "the collection identifier", isRequired = true, type = STRING),
369           @RestParameter(name = "fileName", description = "the file name", isRequired = true, type = STRING) }, responses = {
370           @RestResponse(responseCode = SC_OK, description = "File returned"),
371           @RestResponse(responseCode = SC_NOT_FOUND, description = "Not found") })
372   public Response restGetFromCollection(@PathParam("collectionId") String collectionId,
373           @PathParam("fileName") String fileName) throws NotFoundException {
374     return fileResponse(getFileFromCollection(collectionId, fileName), getMimeType(fileName), some(fileName))
375             .build();
376   }
377 
378   @GET
379   @Path("/collectionuri/{collectionID}/{fileName}")
380   @RestQuery(name = "getUriFromCollection", description = "Gets the URL for a file to be stored in the working repository under /collectionId/filename", returnDescription = "The url to this file", pathParameters = {
381           @RestParameter(name = "collectionID", description = "the collection identifier", isRequired = true, type = STRING),
382           @RestParameter(name = "fileName", description = "the file name", isRequired = true, type = STRING) }, responses = { @RestResponse(responseCode = SC_OK, description = "URL returned") })
383   public Response restGetCollectionUri(@PathParam("collectionID") String collectionId,
384           @PathParam("fileName") String fileName) {
385     URI uri = this.getCollectionURI(collectionId, fileName);
386     return Response.ok(uri.toString()).build();
387   }
388 
389   @GET
390   @Path("/uri/{mediaPackageID}/{mediaPackageElementID}")
391   @RestQuery(name = "getUri", description = "Gets the URL for a file to be stored in the working repository under /mediaPackageID", returnDescription = "The url to this file", pathParameters = {
392           @RestParameter(name = "mediaPackageID", description = "the mediaPackage identifier", isRequired = true, type = STRING),
393           @RestParameter(name = "mediaPackageElementID", description = "the mediaPackage element identifier", isRequired = true, type = STRING) }, responses = { @RestResponse(responseCode = SC_OK, description = "URL returned") })
394   public Response restGetUri(@PathParam("mediaPackageID") String mediaPackageID,
395           @PathParam("mediaPackageElementID") String mediaPackageElementID) {
396     URI uri = this.getURI(mediaPackageID, mediaPackageElementID);
397     return Response.ok(uri.toString()).build();
398   }
399 
400   @GET
401   @Path("/uri/{mediaPackageID}/{mediaPackageElementID}/{fileName}")
402   @RestQuery(name = "getUriWithFilename", description = "Gets the URL for a file to be stored in the working repository under /mediaPackageID", returnDescription = "The url to this file", pathParameters = {
403           @RestParameter(name = "mediaPackageID", description = "the mediaPackage identifier", isRequired = true, type = STRING),
404           @RestParameter(name = "mediaPackageElementID", description = "the mediaPackage element identifier", isRequired = true, type = STRING),
405           @RestParameter(name = "fileName", description = "the filename", isRequired = true, type = STRING) }, responses = { @RestResponse(responseCode = SC_OK, description = "URL returned") })
406   public Response restGetUri(@PathParam("mediaPackageID") String mediaPackageID,
407           @PathParam("mediaPackageElementID") String mediaPackageElementID, @PathParam("fileName") String fileName) {
408     URI uri = this.getURI(mediaPackageID, mediaPackageElementID, fileName);
409     return Response.ok(uri.toString()).build();
410   }
411 
412   @SuppressWarnings("unchecked")
413   @GET
414   @Produces(MediaType.APPLICATION_JSON)
415   @Path("/list/{collectionId}.json")
416   @RestQuery(name = "filesInCollection", description = "Lists files in a collection", returnDescription = "Links to the URLs in a collection", pathParameters = { @RestParameter(name = "collectionId", description = "the collection identifier", isRequired = true, type = STRING) }, responses = {
417           @RestResponse(responseCode = SC_OK, description = "URLs returned"),
418           @RestResponse(responseCode = SC_NOT_FOUND, description = "Collection not found") })
419   public Response restGetCollectionContents(@PathParam("collectionId") String collectionId) throws NotFoundException {
420     URI[] uris = super.getCollectionContents(collectionId);
421     JSONArray jsonArray = new JSONArray();
422     for (URI uri : uris) {
423       jsonArray.add(uri.toString());
424     }
425     return Response.ok(jsonArray.toJSONString()).build();
426   }
427 
428   @POST
429   @Path("/copy/{fromCollection}/{fromFileName}/{toMediaPackage}/{toMediaPackageElement}/{toFileName}")
430   @RestQuery(name = "copy", description = "Copies a file from a collection to a mediapackage", returnDescription = "A URL to the copied file", pathParameters = {
431           @RestParameter(name = "fromCollection", description = "the collection identifier hosting the source", isRequired = true, type = STRING),
432           @RestParameter(name = "fromFileName", description = "the source file name", isRequired = true, type = STRING),
433           @RestParameter(name = "toMediaPackage", description = "the destination mediapackage identifier", isRequired = true, type = STRING),
434           @RestParameter(name = "toMediaPackageElement", description = "the destination mediapackage element identifier", isRequired = true, type = STRING),
435           @RestParameter(name = "toFileName", description = "the destination file name", isRequired = true, type = STRING) }, responses = {
436           @RestResponse(responseCode = SC_OK, description = "URL returned"),
437           @RestResponse(responseCode = SC_NOT_FOUND, description = "File to copy not found") })
438   public Response restCopyTo(@PathParam("fromCollection") String fromCollection,
439           @PathParam("fromFileName") String fromFileName, @PathParam("toMediaPackage") String toMediaPackage,
440           @PathParam("toMediaPackageElement") String toMediaPackageElement, @PathParam("toFileName") String toFileName)
441           throws NotFoundException {
442     try {
443       URI uri = super.copyTo(fromCollection, fromFileName, toMediaPackage, toMediaPackageElement, toFileName);
444       return Response.ok().entity(uri.toString()).build();
445     } catch (IOException e) {
446       logger.error("Unable to copy file '{}' from collection '{}' to mediapackage {}/{}",
447               fromFileName, fromCollection, toMediaPackage, toMediaPackageElement, e);
448       return Response.serverError().entity(e.getMessage()).build();
449     }
450   }
451 
452   @POST
453   @Path("/move/{fromCollection}/{fromFileName}/{toMediaPackage}/{toMediaPackageElement}/{toFileName}")
454   @RestQuery(name = "move", description = "Moves a file from a collection to a mediapackage", returnDescription = "A URL to the moved file", pathParameters = {
455           @RestParameter(name = "fromCollection", description = "the collection identifier hosting the source", isRequired = true, type = STRING),
456           @RestParameter(name = "fromFileName", description = "the source file name", isRequired = true, type = STRING),
457           @RestParameter(name = "toMediaPackage", description = "the destination mediapackage identifier", isRequired = true, type = STRING),
458           @RestParameter(name = "toMediaPackageElement", description = "the destination mediapackage element identifier", isRequired = true, type = STRING),
459           @RestParameter(name = "toFileName", description = "the destination file name", isRequired = true, type = STRING) }, responses = {
460           @RestResponse(responseCode = SC_OK, description = "URL returned"),
461           @RestResponse(responseCode = SC_NOT_FOUND, description = "File to move not found") })
462   public Response restMoveTo(@PathParam("fromCollection") String fromCollection,
463           @PathParam("fromFileName") String fromFileName, @PathParam("toMediaPackage") String toMediaPackage,
464           @PathParam("toMediaPackageElement") String toMediaPackageElement, @PathParam("toFileName") String toFileName)
465           throws NotFoundException {
466     try {
467       URI uri = super.moveTo(fromCollection, fromFileName, toMediaPackage, toMediaPackageElement, toFileName);
468       return Response.ok().entity(uri.toString()).build();
469     } catch (IOException e) {
470       logger.error("Unable to move file '{}' from collection '{}' to mediapackage {}/{}",
471               fromFileName, fromCollection, toMediaPackage, toMediaPackageElement, e);
472       return Response.serverError().entity(e.getMessage()).build();
473     }
474   }
475 
476   @SuppressWarnings("unchecked")
477   @GET
478   @Produces(MediaType.APPLICATION_JSON)
479   @Path("storage")
480   @RestQuery(name = "storage", description = "Returns a report on the disk usage and availability", returnDescription = "Plain text containing the report", responses = { @RestResponse(responseCode = SC_OK, description = "Report returned") })
481   public Response restGetTotalStorage() {
482     long total = this.getTotalSpace().get();
483     long usable = this.getUsableSpace().get();
484     long used = this.getUsedSpace().get();
485     String summary = this.getDiskSpace();
486     JSONObject json = new JSONObject();
487     json.put("size", total);
488     json.put("usable", usable);
489     json.put("used", used);
490     json.put("summary", summary);
491     return Response.ok(json.toJSONString()).build();
492   }
493 
494   @GET
495   @Produces(MediaType.TEXT_PLAIN)
496   @Path("/baseUri")
497   @RestQuery(name = "baseuri", description = "Returns a base URI for this repository", returnDescription = "Plain text containing the base URI", responses = { @RestResponse(responseCode = SC_OK, description = "Base URI returned") })
498   public Response restGetBaseUri() {
499     return Response.ok(super.getBaseUri().toString()).build();
500   }
501 }