diff --git a/src/main/java/org/gbif/ipt/action/BaseAction.java b/src/main/java/org/gbif/ipt/action/BaseAction.java index cef74075e1..656c156983 100644 --- a/src/main/java/org/gbif/ipt/action/BaseAction.java +++ b/src/main/java/org/gbif/ipt/action/BaseAction.java @@ -16,6 +16,7 @@ import java.util.ResourceBundle; import javax.annotation.Nullable; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import com.google.common.base.Strings; import com.google.inject.Inject; @@ -25,13 +26,14 @@ import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; import org.apache.struts2.interceptor.ServletRequestAware; +import org.apache.struts2.interceptor.ServletResponseAware; import org.apache.struts2.interceptor.SessionAware; /** * The base of all IPT actions. This handles conditions such as menu items, a custom text provider, sessions, currently * logged in user, and hosting organization information. */ -public class BaseAction extends ActionSupport implements SessionAware, Preparable, ServletRequestAware { +public class BaseAction extends ActionSupport implements SessionAware, Preparable, ServletRequestAware , ServletResponseAware { // logging private static final Logger LOG = Logger.getLogger(BaseAction.class); @@ -48,6 +50,7 @@ public class BaseAction extends ActionSupport implements SessionAware, Preparabl protected List warnings = new ArrayList(); protected Map session; protected HttpServletRequest req; + protected HttpServletResponse res; // a generic identifier for loading an object BEFORE the param interceptor sets values protected String id; @@ -264,6 +267,10 @@ public void setServletRequest(HttpServletRequest req) { this.req = req; } + public void setServletResponse(HttpServletResponse res) { + this.res = res; + } + public void setSession(Map session) { this.session = session; // always keep sth in the session otherwise the session is not maintained and e.g. the message redirect interceptor diff --git a/src/main/java/org/gbif/ipt/action/portal/ResourceFileAction.java b/src/main/java/org/gbif/ipt/action/portal/ResourceFileAction.java index efa791af70..67e2d8dd9c 100644 --- a/src/main/java/org/gbif/ipt/action/portal/ResourceFileAction.java +++ b/src/main/java/org/gbif/ipt/action/portal/ResourceFileAction.java @@ -1,5 +1,14 @@ package org.gbif.ipt.action.portal; +import com.google.common.base.Strings; +import com.google.inject.Inject; +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; +import org.apache.commons.compress.archivers.zip.ZipFile; +import org.apache.commons.compress.compressors.deflate.DeflateCompressorInputStream; +import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream; +import org.apache.commons.compress.utils.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.log4j.Logger; import org.gbif.ipt.config.AppConfig; import org.gbif.ipt.config.Constants; import org.gbif.ipt.config.DataDir; @@ -13,11 +22,10 @@ import java.io.FileNotFoundException; import java.io.InputStream; import java.math.BigDecimal; +import java.util.Enumeration; +import java.util.zip.GZIPOutputStream; -import com.google.common.base.Strings; -import com.google.inject.Inject; -import org.apache.commons.lang3.StringUtils; -import org.apache.log4j.Logger; +import javax.servlet.ServletOutputStream; /** * The Action responsible for serving datadir resource files. @@ -42,6 +50,91 @@ public ResourceFileAction(SimpleTextProvider textProvider, AppConfig cfg, Regist this.dataDir = dataDir; } + /** + * Handles DwC-A internal file download request. + * + * @return Struts2 result string + */ + public String dwcaIn() { + if (resource == null) { + return NOT_FOUND; + } + String internalFilename = StringUtils.trimToNull(req.getParameter(Constants.REQ_PARAM_FILE)); + + if(internalFilename == null || internalFilename.trim().isEmpty()) { + return NOT_FOUND; + } + + // if no specific version is requested, use the latest published version + if (version == null) { + BigDecimal latestVersion = resource.getLastPublishedVersionsVersion(); + if (latestVersion == null) { + return NOT_FOUND; + } else { + version = latestVersion; + } + } + + boolean gz = internalFilename.endsWith(".gz"); + if(gz) { + internalFilename = internalFilename.replace(".gz",""); + } + boolean compress = gz || (req.getHeader("Accept-Encoding") != null && req.getHeader("Accept-Encoding").contains("gzip")); + + // serve file + File dwcaFile = dataDir.resourceDwcaFile(resource.getShortname(), version); + try { + ZipFile dwcaZip = new ZipFile(dwcaFile); + ZipArchiveEntry entry = dwcaZip.getEntry(internalFilename); + if(entry != null) { + inputStream = dwcaZip.getInputStream(entry); + } else { + return NOT_FOUND; + } + }catch(Exception e) { + LOG.warn("failed to get internal file", e); + return ERROR; + } + + // construct download filename + StringBuilder sb = new StringBuilder(); + sb.append("dwca-" + resource.getShortname()); + if (version != null) { + sb.append("-v" + version.toPlainString()); + } + sb.append("-"+internalFilename); + if(gz) sb.append(".gz"); + filename = sb.toString(); + + if(compress){ + try { + if(gz) { + res.setContentType("application/x-gzip"); + } else { + res.setContentType("application/octet-stream"); + res.addHeader("Content-Encoding", "gzip"); + + } + res.addHeader("Content-Disposition", "attachment; filename=\""+filename+"\""); + + ServletOutputStream sos = res.getOutputStream(); + GZIPOutputStream zos = new GZIPOutputStream(sos); + IOUtils.copy(inputStream, zos); + zos.flush(); + inputStream.close(); + zos.close(); + sos.close(); + } catch (Exception e){ + LOG.warn("error sending gzip",e); + } + return NONE; + } else { + mimeType = "application/octet-stream"; + return SUCCESS; + + } + } + /** * Handles DwC-A file download request. The method checks if the request is a conditional get with If-Modified-Since * header. If the If-Modified-Since date is greater than the last published date, the NOT_MODIFIED string is returned. diff --git a/src/main/java/org/gbif/ipt/config/Constants.java b/src/main/java/org/gbif/ipt/config/Constants.java index ba39acde69..0d7231632e 100644 --- a/src/main/java/org/gbif/ipt/config/Constants.java +++ b/src/main/java/org/gbif/ipt/config/Constants.java @@ -19,6 +19,7 @@ public final class Constants { public static final String REQ_PATH_DWCA = "archive.do"; public static final String REQ_PATH_LOGO = "logo.do"; public static final String REQ_PARAM_RESOURCE = "r"; + public static final String REQ_PARAM_FILE = "f"; public static final String REQ_PARAM_ID = "id"; public static final String REQ_PARAM_SOURCE = "s"; public static final String REQ_PARAM_VERSION = "v"; diff --git a/src/main/resources/struts-portal.xml b/src/main/resources/struts-portal.xml index 99fe3de2fd..b40cde4e44 100644 --- a/src/main/resources/struts-portal.xml +++ b/src/main/resources/struts-portal.xml @@ -88,6 +88,14 @@ 1024 + + + ${mimeType} + inputStream + filename="${filename}" + 1024 + + true