Java tutorial
/* * Copyright 2009 Google Inc. * * 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 com.rainycape.reducer.servlets; import com.google.appengine.api.memcache.Expiration; import com.google.appengine.api.memcache.MemcacheService; import com.google.appengine.api.memcache.MemcacheServiceFactory; import com.google.common.base.Predicate; import com.google.common.collect.Collections2; import com.google.common.collect.Iterators; import com.google.common.collect.Lists; import com.google.common.collect.Ordering; import org.apache.commons.fileupload.FileItemIterator; import org.apache.commons.fileupload.FileItemStream; import org.apache.commons.fileupload.FileUploadException; import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.apache.commons.io.IOUtils; import javax.annotation.Nullable; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.io.StringReader; import java.net.MalformedURLException; import java.net.URL; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Collection; import java.util.Date; import java.util.Enumeration; import java.util.List; import java.util.logging.Logger; @SuppressWarnings("serial") public abstract class BaseServlet extends HttpServlet { private static final String EXPIRE_URLS_PARAM = "expire_urls"; private static final String CONTENT_TYPE_ERROR = "text/plain"; private static final int STATUS_CODE_ERROR = 400; private static final int DISABLE_URL_CACHE_VALUE = 0; private static final int DEFAULT_URL_CACHE_TIME_SECS = 300; private static final String MAX_AGE_PARAM = "max-age"; private static final int DISABLE_MAX_AGE = 0; private static final int DEFAULT_MAX_AGE_PARAM = 600; private final MemcacheService memcache = MemcacheServiceFactory.getMemcacheService(); private static final Logger logger = Logger.getLogger(BaseServlet.class.getName()); @Override protected void service(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { String filecontents; boolean useMemcache = isMemcacheAllowed(req); if (ServletFileUpload.isMultipartContent(req)) { filecontents = collectFromFileUpload(req); } else { filecontents = collectFromFormArgs(req); } filecontents = filecontents.trim(); if (filecontents.length() == 0) { resp.setStatus(STATUS_CODE_ERROR); resp.setContentType(CONTENT_TYPE_ERROR); resp.getWriter().println("No data to parse!"); return; } final String key = getKeyForContents(filecontents); Object cachedCopy; if (useMemcache && (cachedCopy = memcache.get(key)) != null) { maybeSetHttpCacheHeaders(req, resp); render(resp, (String) cachedCopy); } else { StringReader reader = new StringReader(filecontents); Response results = process(resp, reader); if (results.isCacheable()) { maybeSetHttpCacheHeaders(req, resp); if (useMemcache) { memcache.put(key, results.getBody()); } } render(resp, results.getBody()); } } private boolean isMemcacheAllowed(HttpServletRequest hreq) { // If there are any caching related headers, don't use any server side // caches. This isn't built to HTTP spec, but it covers the // shift-reload case. String cacheControl = hreq.getHeader("Cache-Control"); if (cacheControl != null && (cacheControl.contains("max-age") || cacheControl.contains("no-cache"))) { return false; } String pragma = hreq.getHeader("Pragma"); if (pragma != null && pragma.contains("no-cache")) { return false; } try { long ims = hreq.getDateHeader("If-Modified-Since"); if (ims != -1) { return false; } } catch (IllegalArgumentException e) { return false; } return true; } private void maybeSetHttpCacheHeaders(HttpServletRequest req, HttpServletResponse resp) { long cachePolicy = getCachingPolicy(req, MAX_AGE_PARAM, DISABLE_MAX_AGE, DEFAULT_MAX_AGE_PARAM); if (cachePolicy != DISABLE_MAX_AGE) { resp.setDateHeader("Expires", new Date().getTime() + cachePolicy * 1000); resp.setHeader("Cache-Control", "max-age=" + cachePolicy); } } private String collectFromFileUpload(final HttpServletRequest req) throws IOException, ServletException { StringBuilder collector = new StringBuilder(); try { ServletFileUpload sfu = new ServletFileUpload(); FileItemIterator it = sfu.getItemIterator(req); while (it.hasNext()) { FileItemStream item = it.next(); if (!item.isFormField()) { InputStream stream = item.openStream(); collector.append(IOUtils.toString(stream, "UTF-8")); collector.append("\n"); IOUtils.closeQuietly(stream); } } } catch (FileUploadException e) { throw new ServletException(e); } return collector.toString(); } private String collectFromFormArgs(final HttpServletRequest req) throws IOException, ServletException { StringBuilder collector = new StringBuilder(); for (String urlParameterName : getSortedParameterNames(req)) { final String[] values = req.getParameterValues(urlParameterName); for (String value : values) { if (value.matches("^https?://.*")) { acquireFromRemoteUrl(req, collector, value); } else { acquireFromParameterValue(collector, value); } } } return collector.toString(); } private void acquireFromRemoteUrl(final HttpServletRequest req, StringBuilder concatenatedContents, final String url) throws IOException, ServletException { logger.severe("fetching url " + url); try { final String cached = maybeFetchUrlFromCache(req, url); if (cached != null) { concatenatedContents.append(cached); } else { final String urlContents = fetchUrl(url); acquireFromParameterValue(concatenatedContents, urlContents); maybePutUrlInCache(req, url, urlContents); } } catch (MalformedURLException ex) { throw new ServletException(ex); } } private void acquireFromParameterValue(StringBuilder concatenatedContents, String value) { concatenatedContents.append(value); concatenatedContents.append("\n"); } private Collection<String> getSortedParameterNames(HttpServletRequest req) { // We want a deterministic order so that dependencies can span input files. // We don't trust the servlet container to return query parameters in any // order, so we impose our own ordering. In this case, we use natural String // ordering. @SuppressWarnings("unchecked") List<String> list = Lists .newArrayList(Iterators.forEnumeration((Enumeration<String>) req.getParameterNames())); // Some parameter names should be ignored. Iterable<String> filtered = Collections2.filter(list, new Predicate<String>() { @Override public boolean apply(@Nullable String s) { return !(s.contains("_") || s.contains("-")); } }); return Ordering.natural().sortedCopy(filtered); } private String getKeyForContents(final String filecontents) throws ServletException { MessageDigest sha1; try { sha1 = MessageDigest.getInstance("SHA-1"); } catch (NoSuchAlgorithmException e) { throw new ServletException(e); } byte[] hashValue = sha1.digest(filecontents.getBytes()); sha1.reset(); return new String(hashValue); } private int getCachingPolicy(final HttpServletRequest req, String paramName, int disabledValue, int defaultValue) { int returnValue = defaultValue; final String asString = req.getParameter(paramName); if (asString != null) { try { int seconds = Integer.parseInt(asString); if (seconds >= 0) { returnValue = seconds; } } catch (NumberFormatException e) { returnValue = disabledValue; } } return returnValue; } private String fetchUrl(final String url) throws IOException { final URL u = new URL(url); return IOUtils.toString(u.openStream(), "UTF-8"); } private void maybePutUrlInCache(final HttpServletRequest req, final String url, final String contents) { int cacheForSecs = getUrlCachePolicyFromParams(req); if (cacheForSecs == DISABLE_URL_CACHE_VALUE) { return; } memcache.put(url, contents, Expiration.byDeltaSeconds(cacheForSecs)); } private int getUrlCachePolicyFromParams(HttpServletRequest req) { return getCachingPolicy(req, EXPIRE_URLS_PARAM, DISABLE_URL_CACHE_VALUE, DEFAULT_URL_CACHE_TIME_SECS); } private String maybeFetchUrlFromCache(final HttpServletRequest req, final String url) { // If the client asks us to not cache, we also delete any cached values for // this key. if (getUrlCachePolicyFromParams(req) == DISABLE_URL_CACHE_VALUE || !isMemcacheAllowed(req)) { memcache.delete(url); return null; } Object cached = memcache.get(url); if (cached != null) { return (String) cached; } else { return null; } } protected abstract void render(HttpServletResponse resp, String response) throws IOException; protected abstract Response process(HttpServletResponse resp, StringReader reader) throws IOException; }