package com.hbasebook.hush.servlet.filter;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.hbasebook.hush.ResourceManager;
import com.hbasebook.hush.model.ShortUrl;
import com.hbasebook.hush.servlet.RequestInfo;
/**
* The filter handles the short Id to URL redirects.
*/
public class RedirectFilter implements Filter {
public static final String QR_EXTENSION = ".q";
public static final String DETAILS_EXTENSION = "+";
/**
* Initialized the filter instance.
*
* @param filterConfig The filter configuration.
* @throws ServletException When initializing the filter fails.
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// not used
}
/**
* Called when the filter is decommissioned.
*/
@Override
public void destroy() {
// not used
}
/**
* Filter the requests. Used to detect actual pages versus shortened URL Ids.
*
* @param request The current request.
* @param response The response to write to.
* @param chain The filter chain instance.
* @throws IOException When handling the in- or output fails.
* @throws ServletException Might be thrown when there is an internal issue.
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
final HttpServletRequest httpRequest = (HttpServletRequest) request;
final HttpServletResponse httpResponse = (HttpServletResponse) response;
String uri = httpRequest.getRequestURI();
// check if the request shall pass
if (uri.equals("/") || uri.startsWith("/user") ||
uri.startsWith("/admin") || uri.endsWith(".jsp") ||
uri.endsWith(".css")) {
chain.doFilter(request, response);
} else if (uri.endsWith(DETAILS_EXTENSION)) {
// redirect http://hostname/shortId+ to
// http://hostname/details.jsp?sid=shortId
String shortId = uri.substring(1, uri.length() -
DETAILS_EXTENSION.length());
httpRequest.setAttribute("sid", shortId);
RequestDispatcher dispatcher =
httpRequest.getRequestDispatcher("/details.jsp");
if (dispatcher != null) {
dispatcher.forward(request, response);
}
} else {
// otherwise assume it is a short Id
ResourceManager rm = ResourceManager.getInstance();
boolean isQrCode = uri.endsWith(QR_EXTENSION);
String shortId = isQrCode ? uri.substring(1, uri.length() -
QR_EXTENSION.length()) : uri.substring(1);
ShortUrl surl = rm.getUrlManager().getShortUrl(shortId);
if (surl == null) {
// if the short Id was bogus show a "shush" (our fail whale)
httpResponse.sendError(404);
} else if (isQrCode) {
// show QRCode if requested
sendQRCode(httpResponse, surl.getLongUrl());
} else {
// increment counters and redirect!
rm.getCounters().incrementUsage(shortId, new RequestInfo(httpRequest));
String refShortId = surl.getRefShortId();
if (refShortId != null) {
// increment the aggregate link
rm.getCounters().incrementUsage(refShortId,
new RequestInfo(httpRequest));
}
httpResponse.sendRedirect(surl.getLongUrl());
}
}
}
/**
* Copies a Google Chart API QRCode image to the output stream.
*
* @param response The response instance to use.
* @param url The URL to encode.
* @throws IOException When reading or writing the image fails.
*/
private void sendQRCode(HttpServletResponse response, String url)
throws IOException {
URL qrUrl = new URL("http://chart.apis.google.com/chart?" +
"chs=100x100&cht=qr&chl=" + response.encodeURL(url));
InputStream in = new BufferedInputStream(qrUrl.openStream());
OutputStream out = response.getOutputStream();
byte[] buf = new byte[1024];
int contentLength = 0;
while (true) {
int length = in.read(buf);
if (length < 0) {
break;
}
out.write(buf, 0, length);
contentLength += length;
}
response.setContentType("image/png");
response.setContentLength(contentLength);
out.flush();
}
}