package net.noderunner.amazon.s3.emulator;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import net.noderunner.amazon.s3.Entry;
import net.noderunner.amazon.s3.Headers;
import net.noderunner.amazon.s3.Owner;
import net.noderunner.amazon.s3.S3Object;
import net.noderunner.http.servlet.ServletServer;
/**
* Amazon S3 emulator that stores data in an internal sorted map.
* Not highly scalable or reliable, but may be fine for testing your application.
*
* Some browse methods are not complete.
* Get Data/Put/Head are mostly complete.
* What's supported is in the test suite.
*
* @author Elias Ross
*/
public class Server extends HttpServlet {
private static final long serialVersionUID = 1L;
private transient Log log = LogFactory.getLog(getClass());
private transient ServletServer ss;
private boolean bucket = false;
private SortedMap<Entry, S3Object> map = Collections.synchronizedSortedMap(new TreeMap<Entry, S3Object>());
public Server() throws IOException {
ss = new ServletServer(this);
log.info("Server created " + this);
}
/**
* Closes socket, stops accepting requests.
*/
public void close() throws IOException {
log("close");
ss.close();
}
@Override
protected void doDelete(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
Entry e = entry(req);
S3Object remove = map.remove(e);
if (remove == null) {
resp.sendError(404, "Not found " + e);
} else {
resp.sendError(HttpURLConnection.HTTP_NO_CONTENT, "Deleted");
}
}
private Entry entry(HttpServletRequest req) {
return new Entry(key(uri(req)));
}
/**
* Listening port.
*/
public int getPort() {
return ss.getPort();
}
/**
* Starts accepting requests.
*/
public void start() {
ss.start();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
URI uri = uri(req);
boolean debug = log.isDebugEnabled();
if (debug)
log("doGet " + uri);
if ("/".equals(uri.getPath())) {
list(req, resp);
} else {
String key = uri.getPath().substring(1);
Entry e = new Entry(key);
S3Object obj = map.get(e);
if (debug)
log("map.get(" + key + ") = " + obj);
if (obj == null) {
resp.sendError(404, "Not here: " + e);
return;
}
Headers h = new Headers();
h = h.mergeMetadata(obj.getMetadata());
for (Map.Entry<String, List<String>> me : h.getHeaders().entrySet()) {
for (String v : me.getValue()) {
resp.setHeader(me.getKey(), v);
}
}
resp.getOutputStream().write(obj.getData());
}
}
private void list(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String prefix = req.getParameter("prefix");
String marker = req.getParameter("marker");
String delimiter = req.getParameter("delimiter");
String maxKeysStr = req.getParameter("max-keys");
if (log.isDebugEnabled())
log("list prefix=" + prefix + " delimiter=" + delimiter);
int maxKeys = Integer.MAX_VALUE;
if (maxKeysStr != null)
maxKeys = Integer.parseInt(maxKeysStr);
Writer w = new Writer();
SortedMap<Entry, S3Object> submap = new TreeMap<Entry, S3Object>(map);
if (prefix != null)
submap = submap.tailMap(new Entry(prefix));
int keyCount = 0;
boolean truncated = false;
String nextMarker = null;
for (Entry e : submap.keySet()) {
if (++keyCount > maxKeys) {
truncated = true;
break;
}
String key = e.getKey();
String remain = key;
nextMarker = key;
if (prefix != null) {
if (!key.startsWith(prefix))
break;
remain = key.substring(prefix.length());
}
if (delimiter != null && remain.indexOf(delimiter) != -1)
continue;
if (log.isDebugEnabled())
log("include key=" + key);
w.start("Contents");
w.start("Key").write(key).end();
w.start("LastModified").write(e.getLastModified()).end();
w.start("Size").write(e.getSize()).end();
w.start("Owner");
w.start("ID").write(e.getOwner().getId()).end()
.start("DisplayName").write(e.getOwner().getDisplayName())
.end();
w.end();
w.end();
}
Writer hw = new Writer();
hw.start("ListBucketResult");
hw.start("Name").write("localhost").end();
hw.start("Prefix").write(s(prefix)).end();
hw.start("Marker").write(s(marker)).end();
if (delimiter != null) {
hw.start("Delimiter").write(delimiter).end();
if (truncated) {
hw.start("NextMarker").write(nextMarker).end();
}
}
hw.start("IsTruncated").write(String.valueOf(truncated)).end();
if (maxKeysStr != null)
hw.start("MaxKeys").write(maxKeysStr).end();
hw.write(w);
hw.end();
PrintWriter pw = resp.getWriter();
pw.write(hw.toString());
pw.flush();
bucket = true;
}
private String s(String s) {
if (s == null)
return "";
return s;
}
@Override
protected void doHead(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
URI uri = uri(req);
if (log.isDebugEnabled())
log("doHead " + uri);
if (map.containsKey(entry(req))) {
log("found");
resp.sendError(HttpURLConnection.HTTP_OK, "Found URI");
} else {
log("not found");
resp.sendError(404, "Not found");
}
}
private URI uri(HttpServletRequest req) {
try {
return new URI(req.getRequestURI());
} catch (URISyntaxException e1) {
throw new RuntimeException(e1);
}
}
private String key(URI uri) {
return uri.getPath().substring(1);
}
@Override
protected void doPut(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
URI uri = uri(req);
log("doPut " + uri);
if ("/".equals(uri.getPath())) {
log("create bucket");
bucket = true;
} else {
Entry e = new Entry(key(uri));
e.setLastModified(new Date());
e.setSize(req.getContentLength());
e.setOwner(new Owner("id", "name"));
ByteArrayOutputStream os = new ByteArrayOutputStream();
ServletInputStream is = req.getInputStream();
byte b[] = new byte[128];
while (true) {
int len = is.read(b);
if (len == -1)
break;
os.write(b, 0, len);
}
S3Object s3 = new S3Object(os.toByteArray());
map.put(e, s3);
Headers h = new Headers();
@SuppressWarnings("unchecked")
Enumeration<String> names = req.getHeaderNames();
for (String n : Collections.list(names))
h.put(n, req.getHeader(n));
s3.setMetadata(h.extractMetadata());
log("put '" + e + "' as: " + s3);
}
}
@Override
public void log(String s) {
log.debug(s);
}
/**
* Returns a debug <code>String</code>.
*/
@Override
public String toString()
{
return super.toString() +
" bucket=" + this.bucket +
" ss=" + this.ss +
" map=" + this.map +
"";
}
}