/** * The contents of this file are subject to the license and copyright * detailed in the LICENSE file at the root of the source * tree and available online at * * https://github.com/keeps/roda */ package org.roda.wui.api.v1.utils; import java.io.IOException; import java.io.OutputStream; import java.net.URI; import java.net.URISyntaxException; import java.util.Date; import java.util.List; import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.CacheControl; import javax.ws.rs.core.EntityTag; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.StreamingOutput; import javax.xml.transform.TransformerException; import org.apache.commons.lang3.StringUtils; import org.roda.core.RodaCoreFactory; import org.roda.core.common.ConsumesOutputStream; import org.roda.core.common.DownloadUtils; import org.roda.core.common.EntityResponse; import org.roda.core.common.StreamResponse; import org.roda.core.data.common.RodaConstants; import org.roda.core.data.exceptions.AuthorizationDeniedException; import org.roda.core.data.exceptions.GenericException; import org.roda.core.data.exceptions.NotFoundException; import org.roda.core.data.exceptions.RODAException; import org.roda.core.data.exceptions.RequestNotValidException; import org.roda.core.data.v2.IsModelObject; import org.roda.core.data.v2.common.Pair; import org.roda.core.data.v2.common.RODAObjectList; import org.roda.core.data.v2.formats.Format; import org.roda.core.data.v2.index.IndexResult; import org.roda.core.data.v2.index.IsIndexed; import org.roda.core.data.v2.ip.AIP; import org.roda.core.data.v2.ip.AIPs; import org.roda.core.data.v2.ip.DIP; import org.roda.core.data.v2.ip.DIPFile; import org.roda.core.data.v2.ip.IndexedAIP; import org.roda.core.data.v2.ip.IndexedDIP; import org.roda.core.data.v2.ip.IndexedFile; import org.roda.core.data.v2.ip.IndexedRepresentation; import org.roda.core.data.v2.ip.Representations; import org.roda.core.data.v2.ip.StoragePath; import org.roda.core.data.v2.ip.TransferredResource; import org.roda.core.data.v2.jobs.IndexedReport; import org.roda.core.data.v2.jobs.Report; import org.roda.core.data.v2.log.LogEntry; import org.roda.core.data.v2.notifications.Notification; import org.roda.core.data.v2.risks.IndexedRisk; import org.roda.core.data.v2.risks.Risk; import org.roda.core.data.v2.risks.RiskIncidence; import org.roda.core.data.v2.user.RODAMember; import org.roda.core.model.utils.ModelUtils; import org.roda.core.storage.Directory; import org.roda.core.storage.Resource; import org.roda.core.storage.StorageService; /** * API Utils * * @author Hélder Silva <hsilva@keep.pt> */ public class ApiUtils { private static final String CONTENT_DISPOSITION_FILENAME_ARGUMENT = "filename="; private static final String CONTENT_DISPOSITION_INLINE = "inline; "; private static final String CONTENT_DISPOSITION_ATTACHMENT = "attachment; "; private ApiUtils() { // do nothing } /** * Get media type * * @param acceptFormat * String with required format * @param request * http request * @return media type */ public static String getMediaType(String acceptFormat, HttpServletRequest request) { return getMediaType(acceptFormat, request.getHeader(RodaConstants.API_HTTP_HEADER_ACCEPT)); } /** * Get media type * * @param acceptFormat * String with required format * @param acceptHeaders * String with request headers * @return media type */ public static String getMediaType(final String acceptFormat, final String acceptHeaders) { final String applicationJs = "application/javascript; charset=UTF-8"; String mediaType = MediaType.APPLICATION_JSON + "; charset=UTF-8"; if (StringUtils.isNotBlank(acceptFormat)) { if ("XML".equalsIgnoreCase(acceptFormat)) { mediaType = MediaType.APPLICATION_XML; } else if ("JSONP".equalsIgnoreCase(acceptFormat)) { mediaType = applicationJs; } else if ("bin".equalsIgnoreCase(acceptFormat)) { mediaType = MediaType.APPLICATION_OCTET_STREAM; } else if ("html".equalsIgnoreCase(acceptFormat)) { mediaType = MediaType.TEXT_HTML; } } else if (StringUtils.isNotBlank(acceptHeaders)) { if (acceptHeaders.contains(MediaType.APPLICATION_XML)) { mediaType = MediaType.APPLICATION_XML; } else if (acceptHeaders.contains(applicationJs) || acceptHeaders.contains(ExtraMediaType.APPLICATION_JAVASCRIPT)) { mediaType = applicationJs; } else if (acceptHeaders.contains(ExtraMediaType.TEXT_CSV)) { mediaType = ExtraMediaType.TEXT_CSV; } } return mediaType; } /** * Returns valid start (pair first element) and limit (pair second element) * paging parameters defaulting to start = 0 and limit = 100 if none or * invalid values are provided. */ public static Pair<Integer, Integer> processPagingParams(String start, String limit) { Integer startInteger; Integer limitInteger; try { startInteger = Integer.parseInt(start); if (startInteger < 0) { startInteger = 0; } } catch (NumberFormatException e) { startInteger = 0; } try { limitInteger = Integer.parseInt(limit); if (limitInteger < 0) { limitInteger = 100; } } catch (NumberFormatException e) { limitInteger = 100; } return Pair.of(startInteger, limitInteger); } public static Response okResponse(StreamResponse streamResponse, CacheControl cacheControl, Date lastModifiedDate) { return okResponse(streamResponse, cacheControl, lastModifiedDate, false); } public static Response okResponse(StreamResponse streamResponse, CacheControl cacheControl, Date lastModifiedDate, boolean inline) { StreamingOutput so = new StreamingOutput() { @Override public void write(OutputStream output) throws IOException, WebApplicationException { streamResponse.getStream().consumeOutputStream(output); } }; return Response.ok(so, streamResponse.getMediaType()) .header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition(inline) + CONTENT_DISPOSITION_FILENAME_ARGUMENT + "\"" + streamResponse.getFilename() + "\"") .cacheControl(cacheControl).lastModified(lastModifiedDate).build(); } public static Response okResponse(StreamResponse streamResponse, CacheControl cacheControl, EntityTag tag) { return okResponse(streamResponse, cacheControl, tag, false); } public static Response okResponse(StreamResponse streamResponse, CacheControl cacheControl, EntityTag tag, boolean inline) { StreamingOutput so = new StreamingOutput() { @Override public void write(OutputStream output) throws IOException, WebApplicationException { streamResponse.getStream().consumeOutputStream(output); } }; return Response.ok(so, streamResponse.getMediaType()) .header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition(inline) + CONTENT_DISPOSITION_FILENAME_ARGUMENT + "\"" + streamResponse.getFilename() + "\"") .cacheControl(cacheControl).tag(tag).build(); } public static Response okResponse(StreamResponse streamResponse) { return okResponse(streamResponse, false); } public static Response okResponse(StreamResponse streamResponse, boolean inline) { StreamingOutput so = new StreamingOutput() { @Override public void write(OutputStream output) throws IOException, WebApplicationException { streamResponse.getStream().consumeOutputStream(output); } }; return Response.ok(so, streamResponse.getMediaType()) .header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition(inline) + CONTENT_DISPOSITION_FILENAME_ARGUMENT + "\"" + streamResponse.getFilename() + "\"") .build(); } private static String contentDisposition(boolean inline) { return inline ? CONTENT_DISPOSITION_INLINE : CONTENT_DISPOSITION_ATTACHMENT; } public static Response errorResponse(TransformerException e) { String message; if (e.getCause() != null) { message = e.getCause().getMessage(); } else { message = e.getMessage(); } return Response.serverError().entity(new ApiResponseMessage(ApiResponseMessage.ERROR, message)).build(); } public static URI getUriFromRequest(HttpServletRequest request) throws RODAException { try { return new URI(request.getRequestURI()); } catch (URISyntaxException e) { throw new GenericException("Error creating URI from String: " + e.getMessage()); } } @SuppressWarnings("unchecked") public static <T extends IsIndexed, R extends IsModelObject> RODAObjectList<R> indexedResultToRODAObjectList( Class<T> objectClass, IndexResult<T> result) throws RequestNotValidException, NotFoundException, GenericException, AuthorizationDeniedException { RODAObjectList<? extends IsModelObject> ret; if (objectClass.equals(IndexedAIP.class)) { AIPs aips = new AIPs(); for (T object : result.getResults()) { IndexedAIP aip = (IndexedAIP) object; aips.addObject(RodaCoreFactory.getModelService().retrieveAIP(aip.getId())); } ret = aips; } else if (objectClass.equals(IndexedRepresentation.class)) { Representations representations = new Representations(); for (T object : result.getResults()) { IndexedRepresentation representation = (IndexedRepresentation) object; representations.addObject( RodaCoreFactory.getModelService().retrieveRepresentation(representation.getAipId(), representation.getId())); } ret = representations; } else if (objectClass.equals(IndexedFile.class)) { org.roda.core.data.v2.ip.Files files = new org.roda.core.data.v2.ip.Files(); for (T object : result.getResults()) { IndexedFile file = (IndexedFile) object; files.addObject(RodaCoreFactory.getModelService().retrieveFile(file.getAipId(), file.getRepresentationId(), file.getPath(), file.getId())); } ret = files; } else if (objectClass.equals(IndexedRisk.class)) { List<Risk> risks = result.getResults().stream().map(risk -> (Risk) risk).collect(Collectors.toList()); ret = new org.roda.core.data.v2.risks.Risks(risks); } else if (objectClass.equals(TransferredResource.class)) { ret = new org.roda.core.data.v2.ip.TransferredResources((List<TransferredResource>) result.getResults()); } else if (objectClass.equals(Format.class)) { ret = new org.roda.core.data.v2.formats.Formats((List<Format>) result.getResults()); } else if (objectClass.equals(Notification.class)) { ret = new org.roda.core.data.v2.notifications.Notifications((List<Notification>) result.getResults()); } else if (objectClass.equals(LogEntry.class)) { ret = new org.roda.core.data.v2.log.LogEntries((List<LogEntry>) result.getResults()); } else if (objectClass.equals(RiskIncidence.class)) { ret = new org.roda.core.data.v2.risks.RiskIncidences((List<RiskIncidence>) result.getResults()); } else if (objectClass.equals(RODAMember.class)) { ret = new org.roda.core.data.v2.user.RODAMembers((List<RODAMember>) result.getResults()); } else if (objectClass.equals(IndexedDIP.class)) { List<DIP> dips = result.getResults().stream().map(dip -> (DIP) dip).collect(Collectors.toList()); ret = new org.roda.core.data.v2.ip.DIPs(dips); } else if (objectClass.equals(DIPFile.class)) { ret = new org.roda.core.data.v2.ip.DIPFiles((List<DIPFile>) result.getResults()); } else if (objectClass.equals(IndexedReport.class)) { List<Report> reports = result.getResults().stream().map(report -> (Report) report).collect(Collectors.toList()); ret = new org.roda.core.data.v2.jobs.Reports(reports); } else { throw new GenericException("Unsupported object class: " + objectClass); } return (RODAObjectList<R>) ret; } public static StreamResponse download(Resource resource) { return download(resource, null); } public static StreamResponse download(Resource resource, String fileName) { ConsumesOutputStream download = DownloadUtils.download(RodaCoreFactory.getStorageService(), resource, fileName); return new StreamResponse(download.getFileName(), download.getMediaType(), download); } public static <T extends IsIndexed> Response okResponse(T indexed, String acceptFormat, String mediaType) throws RequestNotValidException, NotFoundException, GenericException, AuthorizationDeniedException { EntityResponse representation; if (indexed instanceof IndexedAIP) { IndexedAIP indexedAIP = (IndexedAIP) indexed; if (RodaConstants.API_QUERY_VALUE_ACCEPT_FORMAT_ZIP.equals(acceptFormat)) { StoragePath storagePath = ModelUtils.getAIPStoragePath(indexedAIP.getId()); StorageService storage = RodaCoreFactory.getStorageService(); Directory directory = storage.getDirectory(storagePath); representation = download(directory, indexedAIP.getTitle()); } else if (RodaConstants.API_QUERY_VALUE_ACCEPT_FORMAT_JSON.equals(acceptFormat) || RodaConstants.API_QUERY_VALUE_ACCEPT_FORMAT_XML.equals(acceptFormat)) { AIP aip = RodaCoreFactory.getModelService().retrieveAIP(indexedAIP.getId()); representation = new ObjectResponse<AIP>(acceptFormat, aip); } else { throw new GenericException("Unsupported class: " + acceptFormat); } if (representation instanceof ObjectResponse) { ObjectResponse<AIP> aip = (ObjectResponse<AIP>) representation; return Response.ok(aip.getObject(), mediaType).build(); } else { return ApiUtils.okResponse((StreamResponse) representation); } } else { throw new GenericException("Unsupported accept format: " + acceptFormat); } } }