diff --git a/dataset_download/.classpath b/dataset_download/.classpath index af9bc418829c75485f6a300cd1f5e2fcc6e0578d..2127245367c1793141f044667becb513bfba1846 100644 --- a/dataset_download/.classpath +++ b/dataset_download/.classpath @@ -17,5 +17,6 @@ <classpathentry kind="lib" path="/libraries/jetty/jetty-util.jar" sourcepath="/libraries/jetty/src/jetty-util.zip"/> <classpathentry combineaccessrules="false" kind="src" path="/lims_base"/> <classpathentry kind="lib" path="/libraries/commons-io/commons-io.jar" sourcepath="/libraries/commons-io/src.zip"/> + <classpathentry kind="lib" path="/libraries/commons-lang/commons-lang.jar" sourcepath="/libraries/commons-lang/src.zip"/> <classpathentry kind="output" path="targets/classes"/> </classpath> diff --git a/dataset_download/source/java/ch/systemsx/cisd/openbis/datasetdownload/ConfigParameters.java b/dataset_download/source/java/ch/systemsx/cisd/openbis/datasetdownload/ConfigParameters.java index 8bd22c32a737152790e4ec2b6c1b3c303c7b8389..812e41c37124fe477e6ec75c108112fee3e41a7d 100644 --- a/dataset_download/source/java/ch/systemsx/cisd/openbis/datasetdownload/ConfigParameters.java +++ b/dataset_download/source/java/ch/systemsx/cisd/openbis/datasetdownload/ConfigParameters.java @@ -25,6 +25,12 @@ import java.util.Properties; */ class ConfigParameters { + static final String PASSWORD_KEY = "password"; + static final String USERNAME_KEY = "username"; + static final String SERVER_URL_KEY = "server-url"; + static final String PORT_KEY = "port"; + static final String STOREROOT_DIR_KEY = "storeroot-dir"; + private final String storePath; private final int port; @@ -37,11 +43,11 @@ class ConfigParameters public ConfigParameters(Properties properties) { - storePath = properties.getProperty("storeroot-dir"); - port = Integer.parseInt(properties.getProperty("port")); - serverURL = properties.getProperty("server-url"); - userName = properties.getProperty("username"); - password = properties.getProperty("password"); + storePath = properties.getProperty(STOREROOT_DIR_KEY); + port = Integer.parseInt(properties.getProperty(PORT_KEY)); + serverURL = properties.getProperty(SERVER_URL_KEY); + userName = properties.getProperty(USERNAME_KEY); + password = properties.getProperty(PASSWORD_KEY); } public final String getStorePath() diff --git a/dataset_download/source/java/ch/systemsx/cisd/openbis/datasetdownload/DatasetDownloadService.java b/dataset_download/source/java/ch/systemsx/cisd/openbis/datasetdownload/DatasetDownloadService.java index f90b26fcb834797a828c3155643a5b149f393076..132d961e5d1d05f69ca301ee3b3f4e458c4281ef 100644 --- a/dataset_download/source/java/ch/systemsx/cisd/openbis/datasetdownload/DatasetDownloadService.java +++ b/dataset_download/source/java/ch/systemsx/cisd/openbis/datasetdownload/DatasetDownloadService.java @@ -60,7 +60,7 @@ public class DatasetDownloadService Server server = new Server(applicationContext.getConfigParameters().getPort()); Context context = new Context(server, "/", Context.SESSIONS); context.setAttribute(APPLICATION_CONTEXT_KEY, applicationContext); - context.addServlet(DatasetDownloadServlet.class, "/dataset-download"); + context.addServlet(DatasetDownloadServlet.class, "/dataset-download/*"); server.start(); selfTest(applicationContext); diff --git a/dataset_download/source/java/ch/systemsx/cisd/openbis/datasetdownload/DatasetDownloadServlet.java b/dataset_download/source/java/ch/systemsx/cisd/openbis/datasetdownload/DatasetDownloadServlet.java index a57ebe73ad237fcc2f1bdf074c545da832e71f9d..07dc928146c6eeacd44d9d2853fb446038319a88 100644 --- a/dataset_download/source/java/ch/systemsx/cisd/openbis/datasetdownload/DatasetDownloadServlet.java +++ b/dataset_download/source/java/ch/systemsx/cisd/openbis/datasetdownload/DatasetDownloadServlet.java @@ -18,27 +18,40 @@ package ch.systemsx.cisd.openbis.datasetdownload; import static ch.systemsx.cisd.openbis.datasetdownload.DatasetDownloadService.APPLICATION_CONTEXT_KEY; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; +import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException; import ch.systemsx.cisd.common.logging.LogCategory; import ch.systemsx.cisd.common.logging.LogFactory; +import ch.systemsx.cisd.common.utilities.FileUtilities; import ch.systemsx.cisd.lims.base.ExternalData; +import ch.systemsx.cisd.lims.base.LocatorType; /** * @author Franz-Josef Elmer */ public class DatasetDownloadServlet extends HttpServlet { + static final String DATA_SET_ROOT_DIR_KEY = "data-set-root-dir"; + + static final String DATA_SET_KEY = "data-set"; + static final String DATASET_CODE_KEY = "dataSetCode"; static final String SESSION_ID_KEY = "sessionID"; @@ -53,6 +66,15 @@ public class DatasetDownloadServlet extends HttpServlet private ApplicationContext applicationContext; + public DatasetDownloadServlet() + { + } + + DatasetDownloadServlet(ApplicationContext applicationContext) + { + this.applicationContext = applicationContext; + } + @Override public final void init(final ServletConfig servletConfig) throws ServletException { @@ -73,12 +95,144 @@ public class DatasetDownloadServlet extends HttpServlet protected final void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { - final String dataSetCode = request.getParameter(DATASET_CODE_KEY); - final String sessionID = request.getParameter(SESSION_ID_KEY); - ExternalData dataSet = applicationContext.getDataSetService().getDataSet(sessionID, dataSetCode); - final PrintWriter writer = response.getWriter(); - writer.write("<html><body>Download dataset " + dataSetCode + " (sessionID:" + sessionID + "):" + dataSet + "</body></html>"); + try + { + obtainDataSetFromServer(request); + + HttpSession session = request.getSession(false); + if (session == null) + { + printSessionExpired(response); + } else + { + ExternalData dataSet = (ExternalData) session.getAttribute(DATA_SET_KEY); + File rootDir = (File) session.getAttribute(DATA_SET_ROOT_DIR_KEY); + String pathInfo = request.getPathInfo(); + if (pathInfo != null && pathInfo.startsWith("/")) + { + pathInfo = pathInfo.substring(1); + } + String requestURI = request.getRequestURI(); + renderPage(response, dataSet, rootDir, requestURI, pathInfo); + } + + } catch (Exception e) + { + PrintWriter writer = response.getWriter(); + writer.println("<html><body><h1>Error</h1>"); + String message = e.getMessage(); + writer.println(StringUtils.isBlank(message) ? e.toString() : message); + writer.println("</body></html>"); + writer.flush(); + writer.close(); + } + } + + private void renderPage(final HttpServletResponse response, ExternalData dataSet, File rootDir, + String requestURI, String relativePathOrNull) throws IOException + { + File file = rootDir; + String urlPrefix = requestURI; + String relativeParentPath = null; + if (relativePathOrNull != null && relativePathOrNull.length() > 0) + { + file = new File(rootDir, relativePathOrNull); + urlPrefix = requestURI.substring(0, requestURI.length() - relativePathOrNull.length()); + relativeParentPath = FileUtilities.getRelativeFile(rootDir, file.getParentFile()); + if (relativeParentPath == null) + { + relativeParentPath = ""; + } + } + if (file.exists() == false) + { + throw new EnvironmentFailureException("File '" + file.getName() + "' does not exist."); + } + if (file.isDirectory()) + { + IDirectoryRenderer directoryRenderer = new HTMLDirectoryRenderer(urlPrefix, relativePathOrNull); + response.setContentType(directoryRenderer.getContentType()); + PrintWriter writer = response.getWriter(); + directoryRenderer.setWriter(writer); + directoryRenderer.printHeader(dataSet); + if (relativeParentPath != null) + { + directoryRenderer.printLinkToParentDirectory(relativeParentPath); + } + File[] children = file.listFiles(); + for (File child : children) + { + String name = child.getName(); + String relativePath = FileUtilities.getRelativeFile(rootDir, child); + String normalizedRelativePath = relativePath.replace('\\', '/'); + if (child.isDirectory()) + { + directoryRenderer.printDirectory(name, normalizedRelativePath); + } else + { + directoryRenderer.printFile(name, normalizedRelativePath, child.length()); + } + } + directoryRenderer.printFooter(); + writer.flush(); + writer.close(); + } else + { + long size = file.length(); + response.setContentLength((int) size); + response.setHeader("Content-Disposition", "inline; filename=" + file.getName()); + ServletOutputStream outputStream = null; + FileInputStream fileInputStream = null; + try + { + outputStream = response.getOutputStream(); + fileInputStream = new FileInputStream(file); + IOUtils.copy(fileInputStream, outputStream); + + } finally + { + IOUtils.closeQuietly(fileInputStream); + IOUtils.closeQuietly(outputStream); + } + } + } + + private void printSessionExpired(final HttpServletResponse response) throws IOException + { + PrintWriter writer = response.getWriter(); + writer.write("<html><body>Download session expired.</body></html>"); writer.flush(); writer.close(); } + + private void obtainDataSetFromServer(final HttpServletRequest request) + { + final String dataSetCode = request.getParameter(DATASET_CODE_KEY); + final String sessionID = request.getParameter(SESSION_ID_KEY); + if (dataSetCode != null && sessionID != null) + { + ExternalData dataSet = applicationContext.getDataSetService().getDataSet(sessionID, dataSetCode); + File dataSetRootDirectory = new File(createDataSetPath(dataSet)); + if (dataSetRootDirectory.exists() == false) + { + throw new EnvironmentFailureException("Data set '" + dataSetCode + + "' not found in store at '" + dataSetRootDirectory.getAbsolutePath() + + "'."); + } + HttpSession session = request.getSession(true); + session.setAttribute(DATA_SET_KEY, dataSet); + session.setAttribute(DATA_SET_ROOT_DIR_KEY, dataSetRootDirectory); + } + } + + private String createDataSetPath(ExternalData dataSet) + { + String location = dataSet.getLocation(); + LocatorType locatorType = dataSet.getLocatorType(); + if (locatorType.getCode().equals(LocatorType.DEFAULT_LOCATOR_TYPE_CODE)) + { + return applicationContext.getConfigParameters().getStorePath() + "/" + location; + } + return location; + } } diff --git a/dataset_download/source/java/ch/systemsx/cisd/openbis/datasetdownload/HTMLDirectoryRenderer.java b/dataset_download/source/java/ch/systemsx/cisd/openbis/datasetdownload/HTMLDirectoryRenderer.java new file mode 100644 index 0000000000000000000000000000000000000000..448eb6afdf180919ba94d95686fa1e50db84e31f --- /dev/null +++ b/dataset_download/source/java/ch/systemsx/cisd/openbis/datasetdownload/HTMLDirectoryRenderer.java @@ -0,0 +1,126 @@ +/* + * Copyright 2008 ETH Zuerich, CISD + * + * 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 ch.systemsx.cisd.openbis.datasetdownload; + +import java.io.PrintWriter; +import java.text.DecimalFormat; + +import org.apache.commons.lang.StringUtils; + +import ch.systemsx.cisd.common.utilities.Template; +import ch.systemsx.cisd.lims.base.ExternalData; + +/** + * + * + * @author Franz-Josef Elmer + */ +class HTMLDirectoryRenderer implements IDirectoryRenderer +{ + private static final DecimalFormat FORMAT_KB = new DecimalFormat("0.0 KB"); + private static final DecimalFormat FORMAT_MB = new DecimalFormat("0.0 MB"); + private static final DecimalFormat FORMAT_GB = new DecimalFormat("0.0 GB"); + + private static final int UNIT_KB = 1024; + + private static final int UNIT_MB = UNIT_KB * UNIT_KB; + + private static final int UNIT_GB = UNIT_MB * UNIT_KB; + + private static final Template ROW_TEMPLATE + = new Template("<tr><td><a href='${path}'>${name}</td><td>${size}</td></tr>"); + + private PrintWriter writer; + + private final String urlPrefix; + + private final String relativePathOrNull; + + HTMLDirectoryRenderer(String urlPrefix, String relativePathOrNull) + { + this.relativePathOrNull = relativePathOrNull; + this.urlPrefix = urlPrefix.endsWith("/") ? urlPrefix : urlPrefix + "/"; + } + + public void setWriter(PrintWriter writer) + { + this.writer = writer; + } + + public String getContentType() + { + return "text/html"; + } + + public void printHeader(ExternalData dataSet) + { + writer.println("<html><body>"); + writer.println("<h1>Data Set " + dataSet.getCode() + "</h1>"); + if (StringUtils.isNotBlank(relativePathOrNull)) + { + writer.println("Folder: " + relativePathOrNull); + } + writer.println("<table border='0' cellpadding='5' cellspacing='0'>"); + } + + public void printLinkToParentDirectory(String relativePath) + { + printRow("..", relativePath, ""); + } + + public void printDirectory(String name, String relativePath) + { + printRow(name, relativePath, ""); + } + + public void printFile(String name, String relativePath, long size) + { + printRow(name, relativePath, renderFileSize(size)); + } + + private void printRow(String name, String relativePath, String fileSize) + { + Template template = ROW_TEMPLATE.createFreshCopy(); + template.bind("path", urlPrefix + relativePath); + template.bind("name", name); + template.bind("size", fileSize); + writer.print(template.createText()); + } + + private String renderFileSize(long size) + { + if (size < 10 * UNIT_KB) + { + return Long.toString(size) + " Bytes"; + } + if (size < 10 * UNIT_MB) + { + return FORMAT_KB.format(size / (double) UNIT_KB); + } + if (size < 10 * UNIT_GB) + { + return FORMAT_MB.format(size / (double) UNIT_MB); + } + return FORMAT_GB.format(size / (double) UNIT_GB); + } + + public void printFooter() + { + writer.println("</table></body></html>"); + } + +} diff --git a/dataset_download/source/java/ch/systemsx/cisd/openbis/datasetdownload/IDirectoryRenderer.java b/dataset_download/source/java/ch/systemsx/cisd/openbis/datasetdownload/IDirectoryRenderer.java new file mode 100644 index 0000000000000000000000000000000000000000..a19803b6cc8516c33cae92068ec5b948b25f7c7d --- /dev/null +++ b/dataset_download/source/java/ch/systemsx/cisd/openbis/datasetdownload/IDirectoryRenderer.java @@ -0,0 +1,44 @@ +/* + * Copyright 2008 ETH Zuerich, CISD + * + * 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 ch.systemsx.cisd.openbis.datasetdownload; + +import java.io.PrintWriter; + +import ch.systemsx.cisd.lims.base.ExternalData; + +/** + * + * + * @author Franz-Josef Elmer + */ +public interface IDirectoryRenderer +{ + public String getContentType(); + + public void setWriter(PrintWriter writer); + + public void printHeader(ExternalData dataSet); + + public void printLinkToParentDirectory(String relativePath); + + public void printDirectory(String name, String relativePath); + + public void printFile(String name, String relativePath, long size); + + public void printFooter(); + +} diff --git a/dataset_download/sourceTest/java/ch/systemsx/cisd/openbis/datasetdownload/DatasetDownloadServletTest.java b/dataset_download/sourceTest/java/ch/systemsx/cisd/openbis/datasetdownload/DatasetDownloadServletTest.java index 0f3507ea1b741b2cd22ed89002699d9fc418a8e9..e2a69c347854566ce2e39811b489236885d61c11 100644 --- a/dataset_download/sourceTest/java/ch/systemsx/cisd/openbis/datasetdownload/DatasetDownloadServletTest.java +++ b/dataset_download/sourceTest/java/ch/systemsx/cisd/openbis/datasetdownload/DatasetDownloadServletTest.java @@ -18,11 +18,14 @@ package ch.systemsx.cisd.openbis.datasetdownload; import static org.testng.AssertJUnit.assertEquals; +import java.io.File; import java.io.PrintWriter; import java.io.StringWriter; +import java.util.Properties; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; import org.apache.log4j.Level; import org.jmock.Expectations; @@ -32,6 +35,11 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import ch.systemsx.cisd.common.logging.BufferedAppender; +import ch.systemsx.cisd.common.utilities.FileUtilities; +import ch.systemsx.cisd.common.utilities.OSUtilities; +import ch.systemsx.cisd.lims.base.ExternalData; +import ch.systemsx.cisd.lims.base.IDataSetService; +import ch.systemsx.cisd.lims.base.LocatorType; /** @@ -41,6 +49,16 @@ import ch.systemsx.cisd.common.logging.BufferedAppender; */ public class DatasetDownloadServletTest { + private static final File TEST_FOLDER = new File("targets/unit-test/store"); + + private static final String EXAMPLE_DATA_SET_FOLDER_NAME = "data-set-123"; + + private static final File EXAMPLE_DATA_SET_FOLDER = new File(TEST_FOLDER, EXAMPLE_DATA_SET_FOLDER_NAME); + + private static final String EXAMPLE_SESSION_ID = "AV76CF"; + + private static final String EXAMPLE_DATA_SET_CODE = "1234-1"; + private BufferedAppender logRecorder; private Mockery context; @@ -48,6 +66,10 @@ public class DatasetDownloadServletTest private HttpServletRequest request; private HttpServletResponse response; + + private IDataSetService dataSetService; + + private HttpSession httpSession; @BeforeMethod public void setUp() @@ -56,12 +78,17 @@ public class DatasetDownloadServletTest context = new Mockery(); request = context.mock(HttpServletRequest.class); response = context.mock(HttpServletResponse.class); + dataSetService = context.mock(IDataSetService.class); + httpSession = context.mock(HttpSession.class); + TEST_FOLDER.mkdirs(); + EXAMPLE_DATA_SET_FOLDER.mkdir(); } @AfterMethod public void tearDown() { logRecorder.reset(); + FileUtilities.deleteRecursively(TEST_FOLDER); // To following line of code should also be called at the end of each test method. // Otherwise one do not known which test failed. context.assertIsSatisfied(); @@ -71,25 +98,71 @@ public class DatasetDownloadServletTest public void testDoGet() throws Exception { final StringWriter writer = new StringWriter(); + final ExternalData externalData = new ExternalData(); + externalData.setCode(EXAMPLE_DATA_SET_CODE); + externalData.setLocatorType(new LocatorType(LocatorType.DEFAULT_LOCATOR_TYPE_CODE)); + externalData.setLocation(EXAMPLE_DATA_SET_FOLDER_NAME); context.checking(new Expectations() { { one(request).getParameter(DatasetDownloadServlet.DATASET_CODE_KEY); - will(returnValue("1234-1")); + will(returnValue(EXAMPLE_DATA_SET_CODE)); one(request).getParameter(DatasetDownloadServlet.SESSION_ID_KEY); - will(returnValue("AV76CF")); + will(returnValue(EXAMPLE_SESSION_ID)); one(response).getWriter(); will(returnValue(new PrintWriter(writer))); + + one(dataSetService).getDataSet(EXAMPLE_SESSION_ID, EXAMPLE_DATA_SET_CODE); + will(returnValue(externalData)); + + one(request).getSession(true); + will(returnValue(httpSession)); + + one(httpSession).setAttribute(DatasetDownloadServlet.DATA_SET_KEY, externalData); + one(httpSession).setAttribute(DatasetDownloadServlet.DATA_SET_ROOT_DIR_KEY, EXAMPLE_DATA_SET_FOLDER); + + one(request).getSession(false); + will(returnValue(httpSession)); + + one(httpSession).getAttribute(DatasetDownloadServlet.DATA_SET_KEY); + will(returnValue(externalData)); + + one(httpSession).getAttribute(DatasetDownloadServlet.DATA_SET_ROOT_DIR_KEY); + will(returnValue(EXAMPLE_DATA_SET_FOLDER)); + + one(request).getPathInfo(); + will(returnValue(null)); + + one(request).getRequestURI(); + will(returnValue("uri")); + + one(response).setContentType("text/html"); } }); - DatasetDownloadServlet servlet = new DatasetDownloadServlet(); - servlet.init(); + DatasetDownloadServlet servlet = createServlet(); servlet.doGet(request, response); + assertEquals("<html><body>" + OSUtilities.LINE_SEPARATOR + "<h1>Data Set 1234-1</h1>" + + OSUtilities.LINE_SEPARATOR + + "<table border=\'0\' cellpadding=\'5\' cellspacing=\'0\'>" + + OSUtilities.LINE_SEPARATOR + "</table></body></html>" + + OSUtilities.LINE_SEPARATOR, writer.toString()); - assertEquals("<html><body>Download dataset 1234-1 (sessionID:AV76CF)</body></html>", writer.toString()); + context.assertIsSatisfied(); + } + + private DatasetDownloadServlet createServlet() + { + Properties properties = new Properties(); + properties.setProperty(ConfigParameters.STOREROOT_DIR_KEY, TEST_FOLDER.toString()); + properties.setProperty(ConfigParameters.PORT_KEY, "8080"); + properties.setProperty(ConfigParameters.SERVER_URL_KEY, "http://localhost"); + properties.setProperty(ConfigParameters.USERNAME_KEY, "demo"); + properties.setProperty(ConfigParameters.PASSWORD_KEY, "pwd"); + ConfigParameters configParameters = new ConfigParameters(properties); + return new DatasetDownloadServlet(new ApplicationContext(dataSetService, configParameters)); } }