Guides

Storage OnLine: Using S3 API with JSP for bucket list

The use of the RestS3Service, class, which has already been mentioned in the example web application described in this introductory guide to S3 APIs with JSP, will be discussed in more detail below.

The JSP pages created so far implement these features:

  • index.jsp: list of the buckets associated with our account;
  • listObjects.jsp: list of objects contained in the bucket.

Start page: index.jsp

Below is the source of the page:

<%@page import="org.jets3t.service.model.S3Bucket"%>
<%@ include file="WEB-INF/includes/common.jsp"%>
<html>
<head>
  <link rel="stylesheet" type="text/css" href="style.css" />
</head>
<body>
<h2>jets3t test on Hostingsolutions.it Storage OnLine</h2>

<h3>Elenco bucket</h3>

<c:catch var="ex">
<%
S3Bucket[] buckets = s3.listAllBuckets();
pageContext.setAttribute("buckets", buckets);
%>
</c:catch>

<c:if test="${!empty(ex)}">
  <div class="errorMessage">
    <p>Errore contattando lo storage: <c:out value="${ex.message}" /></p>
  </div>
</c:if>

<ul>
  <c:forEach items="${buckets}" var="bucket">
    <li>
      <c:url var="url" value="listObjectsAdvanced.jsp">
        <c:param name="bucketName">${bucket.name}</c:param>
      </c:url>
      <a href="${url}">${fn:escapeXml(bucket.name)}</a>
    </li>
  </c:forEach>
</ul>

</body>
</html>
      

In order to obtain the list of buckets it is necessary to use the method S3Service.listAllBuckets(), which returns an array of objects of type S3Bucket, stored in the variable buckets.

The instruction pageContext.setAttribute("buckets", buckets) makes the variable available to the page so that it can be used in expressions of type ${buckets},for instance in JSTL tags.

Note that both instructions are enclosed in the tags <c:catch>... </c:catch>: in case of problems, the ex variable (of type java.lang.Exception) will be available to the page to display an error message (see the <c:if> ) block.

Lastly, we use a list <ul> list to display the list of buckets. Note that:

  • the use of the tags <c:url></c:url> end <c:param></c:param> (also from JSTL) makes it much easier to create URLs with parameters;
  • the link on the bucket takes you to the listObjects.jsp detail page;
  • the function fn:escapeXml() effettua un escape characters before writing them in HTML: this practice, in addition to avoiding problems with buckets with special character names (< > &), also strengthens security and should always be used as it helps to defeat Cross-site scripting (XSS) attempts. The same feature is provided by the <c:out> tag.

List of objects: listObjects.jsp

This page is responsible for displaying all the contents of the bucket. Here is the source:

<%@page import="org.jets3t.service.model.S3Object"%>
<%@page import="org.jets3t.service.model.S3Bucket"%>
<%@ include file="WEB-INF/includes/common.jsp"%>
<html>
<head>
  <link rel="stylesheet" type="text/css" href="style.css" />
</head>
<body>
<c:catch var="ex">
<%
String bucketName = request.getParameter("bucketName");
String prefix = request.getParameter("prefix");

S3Bucket bucket = s3.getBucket(bucketName);

S3Object[] objects = s3.listObjects(bucketName, prefix, null);

pageContext.setAttribute("bucket", bucket);
pageContext.setAttribute("objects", objects);
%>
</c:catch>

<c:if test="${!empty(ex)}">
  <div class="errorMessage">
    <p>Errore contattando lo storage: <c:out value="${ex.message}" /></p>
  </div>
</c:if>

<h2>jets3t test on Hostingsolutions.it Storage OnLine</h2>

<h3>Elenco oggetti del bucket: <em>${fn:escapeXml(bucket.name)}</em></h3>

<p><a href="index.jsp">Elenco bucket</a></p>

<form method="post">
  <input name="prefix" value="${fn:escapeXml(param.prefix)}" />
  <button type="submit">Filtra</button>
</form>

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Size</th>
    </tr>
  </thead>
  <c:forEach items="${objects}" var="object">
    <tr>
      <td>
        ${fn:escapeXml(object.name)}
      </td>
      <td>
        ${object.contentLength}
      </td>
    </tr>
  </c:forEach>
</table>

</body>
</html>

Note:

  • the use of the S3Service.listObjects()method to obtain a list of the objects available in the storage;
  • the use of a prefix parameter, possibly null, to limit the list to only those objects whose names begin with that parameter;
  • a simple <form> that allows the user to specify the filter to be used.

As already mentioned in the guide to S3 APIs with php for saving, editing and deleting data from storage, the S3 API interfaces and the online storage systems that implement them do not have the concept of folders,but it is possible to emulate a folder-like tree structure by filtering the names by prefix. For example, fixing the separator character for folder names (e.g. the slash / as on Unix/Linux systems) and having these objects in storage:

  • images/computer.jpg;
  • images/sfondo_01.jpg;
  • sounds/beep.wav;
  • sounds/samples/crash.wav;
  • sounds/samples/drum.wav;

you can imagine a structure like this:

+-- /images
|      +- computer.jpg
|      +- sfondo_01.jpg
|
+-- /sounds
       +- beep.wav
       |
       +- /samples
            +- crash.wav
            +- drum.wav
      

This abstraction would not be easy to implement using only the prefix; however, the JetS3t Tolkit developers have come up with a more sophisticated solution using another method, namely S3Service.listObjectsChunked().

<%@page import="org.jets3t.service.StorageObjectsChunk"%>
<%@page import="org.jets3t.service.model.S3Object"%>
<%@page import="org.jets3t.service.model.S3Bucket"%>
<%@page import="org.jets3t.service.impl.rest.httpclient.RestS3Service"%>
<%@ include file="WEB-INF/includes/common.jsp"%>
<html>
<body>
<c:catch var="ex">
<%
String bucketName = request.getParameter("bucketName");
String prefix = request.getParameter("prefix");

S3Bucket bucket = s3.getBucket(bucketName);

//S3Object[] objects = s3.listObjects(bucketName, prefix, "/");
StorageObjectsChunk chunk = s3.listObjectsChunked(bucketName, prefix, "/", 1000, null);

pageContext.setAttribute("bucket", bucket);
pageContext.setAttribute("chunk", chunk);
%>
</c:catch>

<c:if test="${!empty(ex)}">
  <div class="errorMessage">
    <p>Errore contattando lo storage: <c:out value="${ex.message}" /></p>
  </div>
</c:if>

<h2>jets3t test on Hostingsolutions.it Storage OnLine</h2>

<h3>Elenco oggetti del bucket: <em>${fn:escapeXml(bucket.name)}</em></h3>

<p><a href="index.jsp">Elenco bucket</a></p>

<table border="1">
  <thead>
    <tr>
      <th>Name</th>
      <th>Size</th>
    </tr>
  </thead>
  <%--
  Se siamo in una sotto-cartella, mostra il link ".."
  --%>
  <c:if test="${param.parent != null or not empty(chunk.commonPrefixes)}">
    <tr>
      <td colspan="2">
        <c:url var="url" value="listObjectsAdvanced.jsp">
          <c:param name="bucketName">${fn:escapeXml(param.bucketName)}</c:param>
          <%-- Usiamo il parametro parent come prefisso --%>
          <c:param name="prefix">${fn:escapeXml(param.parent)}</c:param>
        </c:url>
        <a href="${url}">..</a>
      </td>
    </tr>
  </c:if>
  <%--
  Elenca le directory:
  --%>
  <c:forEach items="${chunk.commonPrefixes}" var="prefix">
    <tr>
      <td colspan="2">
        <c:url var="url" value="listObjectsAdvanced.jsp">
          <c:param name="bucketName">${fn:escapeXml(param.bucketName)}</c:param>
          <c:param name="prefix">${fn:escapeXml(prefix)}</c:param>
          <c:param name="parent">${fn:escapeXml(param.prefix)}</c:param>
        </c:url>
        <a href="${url}">${fn:escapeXml(fn:substringAfter(prefix, param.prefix))}</a>
      </td>
    </tr>
  </c:forEach>
  <%--
  Elenca gli oggetti della directory corrente:
  --%>
  <c:forEach items="${chunk.objects}" var="object">
    <c:if test="${object.name ne param.prefix}">
      <tr>
        <td>
          ${fn:escapeXml(fn:substringAfter(object.name, param.prefix))}
        </td>
        <td>
          ${object.contentLength}
        </td>
      </tr>
    </c:if>
  </c:forEach>
</table>

</body>
</html>
      

As you can see, the page has become considerably more complicated:

  • instead of the array of S3Objectwe now have an object of type StorageObjectsChunk, stored in the chunk variables;
  • the chunk.commonPrefixes property contains the names of the sub-folders of the current one
  • we use a parent parameter when entering a sub-folder, this way we can provide a 'hook' to return to a level;
  • Note the use of another JSTL function on strings, in addition to the previously mentioned fn:escapeXml() namely fn:substringAfter(), which is very convenient for displaying the name of objects without the prefix.

Risources