package eu.europeana.cloud.service.mcs.rest; import eu.europeana.cloud.client.uis.rest.CloudException; import eu.europeana.cloud.common.model.CloudId; import eu.europeana.cloud.common.model.File; import eu.europeana.cloud.common.model.Representation; import eu.europeana.cloud.common.selectors.LatestPersistentRepresentationVersionSelector; import eu.europeana.cloud.common.selectors.RepresentationSelector; import eu.europeana.cloud.service.mcs.RecordService; import eu.europeana.cloud.service.mcs.UISClientHandler; import eu.europeana.cloud.service.mcs.exception.FileNotExistsException; import eu.europeana.cloud.service.mcs.exception.ProviderNotExistsException; import eu.europeana.cloud.service.mcs.exception.RecordNotExistsException; import eu.europeana.cloud.service.mcs.exception.RepresentationNotExistsException; import eu.europeana.cloud.service.mcs.exception.WrongContentRangeException; import eu.europeana.cloud.service.mcs.rest.exceptionmappers.UnitedExceptionMapper; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.PermissionEvaluator; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import javax.ws.rs.GET; import javax.ws.rs.HEAD; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import javax.ws.rs.core.StreamingOutput; import javax.ws.rs.core.UriInfo; import java.io.IOException; import java.io.OutputStream; import java.util.List; import static eu.europeana.cloud.common.web.ParamConstants.*; /** * Gives (read) access to files stored in ecloud in simplified (friendly) way. <br/> * The latest persistent version of representation is picked up. */ @Path("/data-providers/{" + P_PROVIDER + "}/records/{" + P_LOCALID + ":.+}/representations/{" + P_REPRESENTATIONNAME + "}/{" + P_FILENAME + ":.+}") @Scope("request") @Component public class SimplifiedFileAccessResource { private static final Logger LOGGER = LoggerFactory.getLogger(SimplifiedFileAccessResource.class); @Autowired private RecordService recordService; @Autowired private UISClientHandler uisClientHandler; @Autowired private PermissionEvaluator permissionEvaluator; /** * Returns file content from <b>latest persistent version</b> of specified representation. * * @param providerId providerId * @param localId localId * @param representationName representationName * @param fileName fileName * @return Requested file context * @throws RepresentationNotExistsException * @throws FileNotExistsException * @throws CloudException * @throws RecordNotExistsException * @summary Get file content using simplified url * @statuscode 204 object has been updated. */ @GET public Response getFile(@Context UriInfo uriInfo, @PathParam(P_PROVIDER) final String providerId, @PathParam(P_LOCALID) final String localId, @PathParam(P_REPRESENTATIONNAME) final String representationName, @PathParam(P_FILENAME) final String fileName) throws RepresentationNotExistsException, FileNotExistsException, CloudException, RecordNotExistsException, ProviderNotExistsException { LOGGER.info("Reading file in friendly way for: provider: {}, localId: {}, represenatation: {}, fileName: {}", providerId, localId, representationName, fileName); final String cloudId = findCloudIdFor(providerId, localId); final Representation representation = selectRepresentationVersion(cloudId, representationName); if (representation == null) { throw new RepresentationNotExistsException(); } if (userHasRightsToReadFile(cloudId, representation.getRepresentationName(), representation.getVersion())) { final File requestedFile = readFile(cloudId, representationName, representation.getVersion(), fileName); String md5 = requestedFile.getMd5(); String fileMimeType = null; if (StringUtils.isNotBlank(requestedFile.getMimeType())) { fileMimeType = requestedFile.getMimeType(); } EnrichUriUtil.enrich(uriInfo, representation, requestedFile); StreamingOutput output = new StreamingOutput() { @Override public void write(OutputStream output) throws IOException, WebApplicationException { try { final FileResource.ContentRange contentRange = new FileResource.ContentRange(-1L, -1L); recordService.getContent(cloudId, representationName, representation.getVersion(), fileName, contentRange.getStart(), contentRange.getEnd(), output); } catch (RepresentationNotExistsException ex) { throw new WebApplicationException(new UnitedExceptionMapper().toResponse(ex)); } catch (FileNotExistsException ex) { throw new WebApplicationException(new UnitedExceptionMapper().toResponse(ex)); } catch (WrongContentRangeException ex) { throw new WebApplicationException(new UnitedExceptionMapper().toResponse(ex)); } } }; return Response.status(Response.Status.OK).entity(output).location(requestedFile.getContentUri()).type(fileMimeType).tag(md5).build(); } else { throw new AccessDeniedException("Access is denied"); } } /** * * Returns file headers from <b>latest persistent version</b> of specified representation. * * @param uriInfo * @param providerId providerId * @param localId localId * @param representationName representationName * @param fileName fileNAme * * @return Requested file headers (together with full file path in 'Location' header) * * @summary Get file headers using simplified url * @throws RepresentationNotExistsException * @throws FileNotExistsException * @throws CloudException * @throws RecordNotExistsException * @throws ProviderNotExistsException */ @HEAD public Response getFileHeaders(@Context UriInfo uriInfo, @PathParam(P_PROVIDER) final String providerId, @PathParam(P_LOCALID) final String localId, @PathParam(P_REPRESENTATIONNAME) final String representationName, @PathParam(P_FILENAME) final String fileName) throws RepresentationNotExistsException, FileNotExistsException, CloudException, RecordNotExistsException, ProviderNotExistsException { LOGGER.info("Reading file headers in friendly way for: provider: {}, localId: {}, represenatation: {}, fileName: {}", providerId, localId, representationName, fileName); final String cloudId = findCloudIdFor(providerId, localId); final Representation representation = selectRepresentationVersion(cloudId, representationName); if (representation == null) { throw new RepresentationNotExistsException(); } if (userHasRightsToReadFile(cloudId, representation.getRepresentationName(), representation.getVersion())) { final File requestedFile = readFile(cloudId, representationName, representation.getVersion(), fileName); String md5 = requestedFile.getMd5(); String fileMimeType = null; if (StringUtils.isNotBlank(requestedFile.getMimeType())) { fileMimeType = requestedFile.getMimeType(); } EnrichUriUtil.enrich(uriInfo, representation, requestedFile); return Response.status(Response.Status.OK).type(fileMimeType).location(requestedFile.getContentUri()).tag(md5).build(); } else { throw new AccessDeniedException("Access is denied"); } } private boolean userHasRightsToReadFile(String cloudId, String representationName, String representationVersion) { SecurityContext ctx = SecurityContextHolder.getContext(); Authentication authentication = ctx.getAuthentication(); // String targetId = cloudId + "/" + representationName + "/" + representationVersion; boolean hasAccess = permissionEvaluator.hasPermission(authentication, targetId, Representation.class.getName(), "read"); return hasAccess; } private String findCloudIdFor(String providerID, String localId) throws CloudException, ProviderNotExistsException, RecordNotExistsException { CloudId foundCloudId = uisClientHandler.getCloudIdFromProviderAndLocalId(providerID, localId); return foundCloudId.getId(); } private Representation selectRepresentationVersion(String cloudId, String representationName) throws RepresentationNotExistsException, RecordNotExistsException { List<Representation> representations = recordService.listRepresentationVersions(cloudId, representationName); RepresentationSelector representationSelector = new LatestPersistentRepresentationVersionSelector(); return representationSelector.select(representations); } private File readFile(String cloudId, String representationName, String version, String fileName) throws RepresentationNotExistsException, FileNotExistsException { return recordService.getFile(cloudId, representationName, version, fileName); } }