/**
* Abiquo community edition
* cloud management application for hybrid clouds
* Copyright (C) 2008-2010 - Abiquo Holdings S.L.
*
* This application is free software; you can redistribute it and/or
* modify it under the terms of the GNU LESSER GENERAL PUBLIC
* LICENSE as published by the Free Software Foundation under
* version 3 of the License
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* LESSER GENERAL PUBLIC LICENSE v.3 for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
package com.abiquo.appliancemanager.web.servlet;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.JarURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.zip.GZIPOutputStream;
import java.util.zip.ZipEntry;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.abiquo.am.resources.handler.CheckRepositoryHandler;
import com.abiquo.am.services.TemplateConventions;
import com.abiquo.appliancemanager.config.AMConfiguration;
// TODO add authentication support
public class StaticRepositoryServlet extends HttpServlet
{
private static final long serialVersionUID = 2983718598265271809L;
protected String pathPrefix = AMConfiguration.getRepositoryPath();
public static interface LookupResult
{
public void respondGet(HttpServletResponse resp) throws IOException;
public void respondHead(HttpServletResponse resp);
public long getLastModified();
}
public static class Error implements LookupResult
{
protected final int statusCode;
protected final String message;
public Error(final int statusCode, final String message)
{
this.statusCode = statusCode;
this.message = message;
}
@Override
public long getLastModified()
{
return -1;
}
@Override
public void respondGet(final HttpServletResponse resp) throws IOException
{
resp.sendError(statusCode, message);
}
@Override
public void respondHead(final HttpServletResponse resp)
{
throw new UnsupportedOperationException();
}
}
public static class StaticFile implements LookupResult
{
protected final long lastModified;
protected final String mimeType;
protected final int contentLength;
protected final boolean acceptsDeflate;
protected final URL url;
public StaticFile(final long lastModified, final String mimeType, final int contentLength,
final boolean acceptsDeflate, final URL url)
{
this.lastModified = lastModified;
this.mimeType = mimeType;
this.contentLength = contentLength;
this.acceptsDeflate = acceptsDeflate;
this.url = url;
}
@Override
public long getLastModified()
{
return lastModified;
}
protected boolean willDeflate()
{
return acceptsDeflate && deflatable(mimeType) && contentLength >= deflateThreshold;
}
protected void setHeaders(final HttpServletResponse resp)
{
resp.setStatus(HttpServletResponse.SC_OK);
resp.setContentType(mimeType);
if (contentLength >= 0 && !willDeflate())
{
resp.setContentLength(contentLength);
}
}
@Override
public void respondGet(final HttpServletResponse resp) throws IOException
{
setHeaders(resp);
final OutputStream os;
if (willDeflate())
{
resp.setHeader("Content-Encoding", "gzip");
os = new GZIPOutputStream(resp.getOutputStream(), bufferSize);
}
else
{
os = resp.getOutputStream();
}
transferStreams(url.openStream(), os);
}
@Override
public void respondHead(final HttpServletResponse resp)
{
if (willDeflate())
{
throw new UnsupportedOperationException();
}
setHeaders(resp);
}
}
@Override
protected void doGet(final HttpServletRequest req, final HttpServletResponse resp)
throws IOException
{
lookup(req).respondGet(resp);
}
@Override
protected void doPut(final HttpServletRequest req, final HttpServletResponse resp)
throws IOException
{
doGet(req, resp);
}
@Override
protected void doHead(final HttpServletRequest req, final HttpServletResponse resp)
throws IOException, ServletException
{
try
{
lookup(req).respondHead(resp);
}
catch (UnsupportedOperationException e)
{
super.doHead(req, resp);
}
}
@Override
protected long getLastModified(final HttpServletRequest req)
{
return lookup(req).getLastModified();
}
protected LookupResult lookup(final HttpServletRequest req)
{
new CheckRepositoryHandler().canUseRepository();
LookupResult r = (LookupResult) req.getAttribute("lookupResult");
if (r == null)
{
r = lookupNoCache(req);
req.setAttribute("lookupResult", r);
}
return r;
}
protected LookupResult lookupNoCache(final HttpServletRequest req)
{
String path = getPath(req);
path = TemplateConventions.customEncode(path);
path = path.replace("files/", "");
// XXX if(isForbidden(path))
// return new Error(HttpServletResponse.SC_FORBIDDEN, "Forbidden");
final URL url;
try
{
// XXX url = getServletContext().getResource(path); XXX
url = new File(pathPrefix + path).toURI().toURL();
}
catch (MalformedURLException e)
{
return new Error(HttpServletResponse.SC_BAD_REQUEST, "Malformed path");
}
if (url == null)
{
return new Error(HttpServletResponse.SC_NOT_FOUND, "Not found");
}
final String mimeType = getMimeType(path);
final String realpath = new File(pathPrefix + path).getAbsolutePath();
// XXX getServletContext().getRealPath(path);
if (realpath != null)
{
// Try as an ordinary file
File f = new File(realpath);
if (!f.isFile())
{
return new Error(HttpServletResponse.SC_FORBIDDEN, "Forbidden");
}
else
{
return new StaticFile(f.lastModified(),
mimeType,
(int) f.length(),
acceptsDeflate(req),
url);
}
}
else
{
try
{
// Try as a JAR Entry
final ZipEntry ze = ((JarURLConnection) url.openConnection()).getJarEntry();
if (ze != null)
{
if (ze.isDirectory())
{
return new Error(HttpServletResponse.SC_FORBIDDEN, "Forbidden");
}
else
{
return new StaticFile(ze.getTime(),
mimeType,
(int) ze.getSize(),
acceptsDeflate(req),
url);
}
}
else
{
// Unexpected?
return new StaticFile(-1, mimeType, -1, acceptsDeflate(req), url);
}
}
catch (ClassCastException e)
{
// Unknown resource type
return new StaticFile(-1, mimeType, -1, acceptsDeflate(req), url);
}
catch (IOException e)
{
return new Error(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Internal server error");
}
}
}
protected String getPath(final HttpServletRequest req)
{
String servletPath = req.getServletPath();
String pathInfo = coalesce(req.getPathInfo(), "");
return servletPath + pathInfo;
}
protected boolean isForbidden(final String path)
{
String lpath = path.toLowerCase();
return lpath.startsWith("/web-inf/") || lpath.startsWith("/meta-inf/");
}
protected String getMimeType(final String path)
{
return "application/octet-stream";
// XXX return coalesce(getServletContext().getMimeType(path), "application/octet-stream");
}
protected static boolean acceptsDeflate(final HttpServletRequest req)
{
final String ae = req.getHeader("Accept-Encoding");
return ae != null && ae.contains("gzip");
}
protected static boolean deflatable(final String mimetype)
{
return mimetype.startsWith("text/") || mimetype.equals("application/postscript")
|| mimetype.startsWith("application/ms") || mimetype.startsWith("application/vnd")
|| mimetype.endsWith("xml");
}
protected static final int deflateThreshold = 4 * 1024;
protected static final int bufferSize = 4 * 1024;
protected static void transferStreams(final InputStream is, final OutputStream os)
throws IOException
{
try
{
byte[] buf = new byte[bufferSize];
int bytesRead;
while ((bytesRead = is.read(buf)) != -1)
{
os.write(buf, 0, bytesRead);
}
}
finally
{
is.close();
os.close();
}
}
public static <T> T coalesce(final T... ts)
{
for (T t : ts)
{
if (t != null)
{
return t;
}
}
return null;
}
}
// TODO this can not be getServletContext().getMimeType
//
// extends DefaultServlet
// {
// private static final long serialVersionUID = 5741560066901267141L;
//
// protected String pathPrefix = AMConfigurationManager.getInstance().getAMConfiguration()
// .getRepositoryPath();
//
//
//
// public void init(ServletConfig config) throws ServletException
// {
// super.init(config);
//
// // if (config.getInitParameter("pathPrefix") != null)
// // {
// // pathPrefix = config.getInitParameter("pathPrefix");
// // }
// }
//
// protected String getRelativePath(HttpServletRequest req)
// {
// String relativePath = super.getRelativePath(req);
// if(relativePath.startsWith("/"))
// {
// relativePath = relativePath.substring(1);
// }
//
// return pathPrefix + relativePath;
// }
// }