package ca.intelliware.ihtsdo.mlds.web.rest;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Paths;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders;
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.apache.http.impl.client.HttpClients;
import org.ihtsdo.otf.dao.s3.S3Client;
import org.ihtsdo.otf.dao.s3.helper.FileHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.StreamUtils;
@Service
public class UriDownloader {
@Resource
S3Client s3Client;
private static final String HEADER_CONTENT_DISPOSITION = "Content-Disposition";
// TODO These should be owned by S3ClientHelper and configured at runtime, but don't want to break SRS
private static final String S3_PROTOCOL = "s3://";
private static final String S3_BASE_LOCATION = ".s3.amazonaws.com";
private final Logger log = LoggerFactory.getLogger(UriDownloader.class);
public int download(String downloadUrl, HttpServletRequest clientRequest, HttpServletResponse clientResponse) throws IOException {
// Are we dealing with an HTTP request or S3?
// Can we determine an S3 Bucket?
S3Location s3Location = determineS3Location(downloadUrl);
if (s3Location != null) {
return downloadS3(s3Location, downloadUrl, clientRequest, clientResponse);
} else {
return downloadHTTP(downloadUrl, clientRequest, clientResponse);
}
}
// TODO Can also move to S3ClientHelper, once configured variables are available there.
S3Location determineS3Location(String url) {
S3Location location = null;
// Does the URL start with the S3 protocol?
if (url.startsWith(S3_PROTOCOL)) {
// eg s3://ire.published.release.ihtsdo/international/ bucket is up to first single slash
int nextSlash = url.indexOf('/', S3_PROTOCOL.length());
String bucket = url.substring(S3_PROTOCOL.length(), nextSlash);
String filePath = url.substring(nextSlash + 1);
location = new S3Location(bucket, filePath);
} else if (url.contains(S3_BASE_LOCATION)) {
// eg https://ire.published.release.ihtsdo.s3.amazonaws.com/international/SnomedCT_Release_INT_20140131.zip
// In this case the bucket is the host name up to the base location
// Remove optional protocol
int protocolEnd = url.indexOf("//");
if (protocolEnd != -1) {
url = url.substring(protocolEnd + 2);
}
int baseLocationStart = url.indexOf(S3_BASE_LOCATION);
String bucket = url.substring(0, baseLocationStart);
String filePath = url.substring(baseLocationStart + S3_BASE_LOCATION.length() + 1);
location = new S3Location(bucket, filePath);
}
return location;
}
public int downloadS3(S3Location s3Location, String downloadUrl, HttpServletRequest clientRequest, HttpServletResponse clientResponse)
throws IOException {
log.debug("Attempting to download {} from S3", s3Location.toString());
FileHelper s3Helper = new FileHelper(s3Location.bucket, s3Client);
InputStream fileContents = s3Helper.getFileStream(s3Location.filePath);
if (fileContents != null) {
// Recover the filename from the path
Path p = Paths.get(s3Location.filePath);
String fileName = p.getFileName().toString();
clientResponse.setHeader("Content-Disposition", "attachment;filename=" + fileName);
StreamUtils.copy(fileContents, clientResponse.getOutputStream());
} else {
clientResponse.sendError(HttpStatus.SC_NOT_FOUND);
}
return clientResponse.getStatus();
}
public int downloadHTTP(String downloadUrl, HttpServletRequest clientRequest, HttpServletResponse clientResponse) {
log.debug("Attempting to download {} via HTTP", downloadUrl);
try {
CloseableHttpClient httpClient = createHttpClient();
try {
HttpGet hostingRequest = new HttpGet(downloadUrl);
copyClientHeadersToHostingRequest(clientRequest, hostingRequest);
CloseableHttpResponse hostingResponse = httpClient.execute(hostingRequest);
try {
int statusCode = hostingResponse.getStatusLine().getStatusCode();
if (statusCode >= 200 && statusCode < 300 || statusCode == HttpStatus.SC_NOT_MODIFIED) {
copyHostingHeadersToClientResponse(hostingResponse, clientResponse);
setContentDispositionIfMissing(hostingResponse, clientResponse, downloadUrl);
HttpEntity hostingEntity = hostingResponse.getEntity();
hostingEntity.writeTo(clientResponse.getOutputStream());
} else {
clientResponse.sendError(statusCode);
}
return statusCode;
} finally {
hostingResponse.close();
}
} finally {
httpClient.close();
}
} catch (IOException ex) {
throw new RuntimeException("Error while downloading file", ex);
}
}
private CloseableHttpClient createHttpClient() {
return HttpClients.createDefault();
}
private void setContentDispositionIfMissing(CloseableHttpResponse hostingResponse, HttpServletResponse clientResponse, String downloadUrl) {
if (hostingResponse.getFirstHeader(HEADER_CONTENT_DISPOSITION) != null) {
return;
}
try {
URI uri = new URI(downloadUrl);
String value = "attachment";
String path = uri.getPath();
if (path != null) {
int lastIndex = path.lastIndexOf('/');
if (lastIndex != -1) {
path = path.substring(lastIndex+1);
}
value += "; filename=\""+path+"\"";
}
clientResponse.addHeader(HEADER_CONTENT_DISPOSITION, value);
} catch (URISyntaxException e) {
log.error("Failed to generate content-disposition due to error parsing uri");
}
}
private void copyClientHeadersToHostingRequest(HttpServletRequest clientRequest, HttpGet hostingRequest) {
copyClientHeaderToHostingRequest(HttpHeaders.IF_MODIFIED_SINCE, clientRequest, hostingRequest);
copyClientHeaderToHostingRequest(HttpHeaders.IF_NONE_MATCH, clientRequest, hostingRequest);
copyClientHeaderToHostingRequest(HttpHeaders.IF_UNMODIFIED_SINCE, clientRequest, hostingRequest);
}
private void copyClientHeaderToHostingRequest(String key,
HttpServletRequest clientRequest, HttpGet hostingRequest) {
String value = clientRequest.getHeader(key);
if (value != null) {
hostingRequest.addHeader(key, value);
}
}
private void copyHostingHeadersToClientResponse(CloseableHttpResponse hostingResponse, HttpServletResponse clientResponse) {
copyHostingHeaderToClientResponse(HttpHeaders.CACHE_CONTROL, hostingResponse, clientResponse);
copyHostingHeaderToClientResponse(HEADER_CONTENT_DISPOSITION, hostingResponse, clientResponse);
copyHostingHeaderToClientResponse(HttpHeaders.CONTENT_TYPE, hostingResponse, clientResponse);
copyHostingHeaderToClientResponse(HttpHeaders.CONTENT_LENGTH, hostingResponse, clientResponse);
copyHostingHeaderToClientResponse(HttpHeaders.DATE, hostingResponse, clientResponse);
copyHostingHeaderToClientResponse(HttpHeaders.ETAG, hostingResponse, clientResponse);
copyHostingHeaderToClientResponse(HttpHeaders.EXPIRES, hostingResponse, clientResponse);
copyHostingHeaderToClientResponse(HttpHeaders.LAST_MODIFIED, hostingResponse, clientResponse);
}
private void copyHostingHeaderToClientResponse(String key, CloseableHttpResponse hostingResponse, HttpServletResponse clientResponse) {
Header header = hostingResponse.getFirstHeader(key);
if (header != null) {
clientResponse.setHeader(key, header.getValue());
}
}
class S3Location {
String bucket;
String filePath;
public S3Location(String bucket, String filePath) {
this.bucket = bucket;
this.filePath = filePath;
}
public String getBucket() {
return bucket;
}
public void setBucket(String bucket) {
this.bucket = bucket;
}
public String getFilePath() {
return filePath;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
public String toString() {
return bucket + ":" + filePath;
}
}
}