/** * Copyright 2008 The University of North Carolina at Chapel Hill * * Licensed under the Apache 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.apache.org/licenses/LICENSE-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 edu.unc.lib.dl.ui.service; import java.io.IOException; import java.io.OutputStream; import java.util.Arrays; import java.util.List; import javax.servlet.http.HttpServletResponse; import org.apache.http.HttpStatus; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import edu.unc.lib.dl.acl.util.AccessGroupSet; import edu.unc.lib.dl.acl.util.GroupsThreadStore; import edu.unc.lib.dl.fedora.AccessClient; import edu.unc.lib.dl.fedora.AuthorizationException; import edu.unc.lib.dl.fedora.FedoraException; import edu.unc.lib.dl.fedora.PID; import edu.unc.lib.dl.httpclient.HttpClientUtil; import edu.unc.lib.dl.search.solr.model.BriefObjectMetadataBean; import edu.unc.lib.dl.search.solr.model.Datastream; import edu.unc.lib.dl.search.solr.model.SimpleIdRequest; import edu.unc.lib.dl.search.solr.util.SearchFieldKeys; import edu.unc.lib.dl.search.solr.util.SearchSettings; import edu.unc.lib.dl.ui.exception.ClientAbortException; import edu.unc.lib.dl.ui.exception.InvalidRecordRequestException; import edu.unc.lib.dl.ui.exception.ResourceNotFoundException; import edu.unc.lib.dl.ui.util.AccessUtil; import edu.unc.lib.dl.ui.util.AnalyticsTrackerUtil; import edu.unc.lib.dl.ui.util.AnalyticsTrackerUtil.AnalyticsUserData; import edu.unc.lib.dl.ui.util.FedoraUtil; import edu.unc.lib.dl.ui.util.FileIOUtil; import edu.unc.lib.dl.ui.util.StringFormatUtil; import edu.unc.lib.dl.util.ContentModelHelper; import edu.unc.lib.dl.util.ContentModelHelper.DatastreamCategory; /** * Connects to and streams datastreams from Fedora. * * @author bbpennel */ public class FedoraContentService { private static final Logger LOG = LoggerFactory.getLogger(FedoraContentService.class); private AccessClient accessClient; private FedoraUtil fedoraUtil; private String fedoraHost; @Autowired private SearchSettings searchSettings; @Autowired protected SolrQueryLayerService queryLayer; @Autowired(required = false) protected AnalyticsTrackerUtil analyticsTracker; private final int numberOfRetries = 1; private static List<String> resultFields = Arrays.asList(SearchFieldKeys.ID.name(), SearchFieldKeys.DATASTREAM.name(), SearchFieldKeys.LABEL.name(), SearchFieldKeys.RESOURCE_TYPE.name(), SearchFieldKeys.ROLE_GROUP.name(), SearchFieldKeys.PARENT_COLLECTION.name(), SearchFieldKeys.ANCESTOR_PATH.name(), SearchFieldKeys.TITLE.name()); public void streamData(String pid, String datastream, boolean download, AnalyticsUserData userData, HttpServletResponse response) { AccessGroupSet accessGroups = GroupsThreadStore.getGroups(); // Default datastream is DATA_FILE if (datastream == null) { datastream = ContentModelHelper.Datastream.DATA_FILE.toString(); } // Use solr to check if the user is allowed to view this item. SimpleIdRequest idRequest = new SimpleIdRequest(pid, resultFields, accessGroups); BriefObjectMetadataBean briefObject = queryLayer.getObjectById(idRequest); // If the record isn't accessible then invalid record exception. if (briefObject == null) { throw new InvalidRecordRequestException(); } // Block access to thumbnails for non-containers, if (AccessUtil.hasListAccessOnly(accessGroups, briefObject) && (searchSettings.resourceTypeFile.equals(briefObject.getResourceType()) || searchSettings.resourceTypeAggregate .equals(briefObject.getResourceType()))) throw new InvalidRecordRequestException(); // If a label is available, use it for the filename String filename = briefObject.getLabel(); try { edu.unc.lib.dl.search.solr.model.Datastream datastreamResult = briefObject.getDatastreamObject(datastream); if (datastreamResult == null) throw new ResourceNotFoundException("Datastream " + datastream + " was not found on object " + pid); // Track the download event if the request is for the original content if (analyticsTracker != null && datastreamResult.getDatastreamCategory() != null && datastreamResult.getDatastreamCategory().equals(DatastreamCategory.ORIGINAL)) { analyticsTracker.trackEvent(userData, briefObject.getParentCollection() == null ? "(no collection)" : briefObject.getParentCollectionName(), "download", briefObject.getTitle() + "|" + pid, null); } this.streamData(pid, datastreamResult, filename, response, download, numberOfRetries); } catch (AuthorizationException e) { throw new InvalidRecordRequestException(e); } catch (ResourceNotFoundException e) { LOG.info("Resource not found while attempting to stream datastream", e); throw e; } catch (Exception e) { LOG.error("Failed to retrieve content for " + pid + " datastream: " + datastream, e); throw new ResourceNotFoundException(); } } private void streamData(String simplepid, Datastream datastream, String filename, HttpServletResponse response, boolean asAttachment, int retryServerError) throws FedoraException, IOException { OutputStream outStream = response.getOutputStream(); String dataUrl = fedoraUtil.getFedoraUrl() + "/objects/" + simplepid + "/datastreams/" + datastream.getName() + "/content"; CloseableHttpClient client = HttpClientUtil .getAuthenticatedClient(fedoraHost, accessClient.getUsername(), accessClient.getPassword()); HttpGet method = new HttpGet(dataUrl); method.addHeader(HttpClientUtil.FORWARDED_GROUPS_HEADER, GroupsThreadStore.getGroupString()); try (CloseableHttpResponse httpResp = client.execute(method)) { if (httpResp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { if (response != null) { PID pid = new PID(simplepid); // Adjusting content related headers // Use the content length from Fedora it is not provided or negative, in which case use solr's long contentLength; try { String contentLengthString = httpResp.getFirstHeader("content-length").getValue(); contentLength = Long.parseLong(contentLengthString); } catch (Exception e) { // If the content length wasn't provided or wasn't a number, set it to -1 contentLength = -1L; } if (contentLength < 0L) { contentLength = datastream.getFilesize(); } response.setHeader("Content-Length", Long.toString(contentLength)); // Use Fedora's content type unless it is unset or octet-stream String mimeType; try { mimeType = httpResp.getFirstHeader("content-type").getValue(); if (mimeType == null || "application/octet-stream".equals(mimeType)) { if ("mp3".equals(datastream.getExtension())) { mimeType = "audio/mpeg"; } else { mimeType = datastream.getMimetype(); } } } catch (Exception e) { mimeType = datastream.getMimetype(); } response.setHeader("Content-Type", mimeType); // Setting the filename header for the response if (filename != null) { filename = StringFormatUtil.makeToken(filename, "_"); } else { filename = pid.getPid(); } // For metadata types files, append the datastream name if (datastream.getDatastreamCategory().equals(DatastreamCategory.METADATA) || datastream.getDatastreamCategory().equals(DatastreamCategory.ADMINISTRATIVE)) { filename += "_" + datastream.getName(); } // Add the file extension unless its already in there. if (datastream.getExtension() != null && datastream.getExtension().length() > 0 && !filename.toLowerCase().endsWith("." + datastream.getExtension()) && !"unknown".equals(datastream.getExtension())) { filename += "." + datastream.getExtension(); } if (asAttachment) { response.setHeader("content-disposition", "attachment; filename=\"" + filename + "\""); } else { response.setHeader("content-disposition", "inline; filename=\"" + filename + "\""); } } // Stream the content FileIOUtil.stream(outStream, httpResp); } else if (httpResp.getStatusLine().getStatusCode() == HttpStatus.SC_FORBIDDEN) { throw new AuthorizationException( "User does not have sufficient permissions to retrieve the specified object"); } else { // Retry server errors if (httpResp.getStatusLine().getStatusCode() == 500 && retryServerError > 0) { LOG.warn("Failed to retrieve " + dataUrl + ", retrying."); this.streamData(simplepid, datastream, filename, response, asAttachment, retryServerError - 1); } else { throw new ResourceNotFoundException("Failure to stream fedora content due to response of: " + httpResp.getStatusLine().toString() + "\nPath was: " + dataUrl); } } } catch (ClientAbortException e) { if (LOG.isDebugEnabled()) LOG.debug("User client aborted request to stream Fedora content for " + simplepid, e); } catch (IOException e) { LOG.warn("Problem retrieving " + dataUrl + " for " + simplepid, e); } } public void setAccessClient(edu.unc.lib.dl.fedora.AccessClient accessClient) { this.accessClient = accessClient; } public void setFedoraUtil(FedoraUtil fedoraUtil) { this.fedoraUtil = fedoraUtil; } public void setSearchSettings(SearchSettings searchSettings) { this.searchSettings = searchSettings; } public void setQueryLayer(SolrQueryLayerService queryLayer) { this.queryLayer = queryLayer; } public void setAnalyticsTracker(AnalyticsTrackerUtil analyticsTracker) { this.analyticsTracker = analyticsTracker; } public String getFedoraHost() { return fedoraHost; } public void setFedoraHost(String fedoraHost) { this.fedoraHost = fedoraHost; } }