Java tutorial
/* * Copyright 2009-2010 BeDataDriven (alex@bedatadriven.com) * * 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.bedatadriven.rebar.appcache.server; import com.bedatadriven.rebar.appcache.client.Html5AppCache; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.*; import java.util.*; import java.util.Map.Entry; import java.util.logging.Logger; /** * Serves the appropriate permutation of a loader script or application manifest. * <p/> * <code> * Requested file: MyModuleName/bootstrap.js * Server will look up permutations in */ public class DefaultSelectionServlet extends HttpServlet { public static final int CACHE_OBSOLETE = 404; private static final Logger logger = Logger.getLogger(DefaultSelectionServlet.class.getName()); private final Map<String, PropertyProvider> providers; public DefaultSelectionServlet() { providers = new HashMap<>(); registerProvider("user.agent", new UserAgentProvider()); } public final void registerProvider(String propertyName, PropertyProvider provider) { providers.put(propertyName, provider); } @Override protected final void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // get the non-permuted version of the file Path path = getModuleBase(req); // special hook to help remove application cache if (path.file.endsWith(".appcache") && isAppCacheDisabled(req)) { resp.sendError(HttpServletResponse.SC_NOT_FOUND); return; } // read the permutation map that was prepared during the linker phase JsonArray permutationMap = readPermutationMap(path); if (permutationMap == null) { serveDefault(resp, path); } else { try { String permutation = computePermutation(req, permutationMap); if (permutation == null) { handleNoAvailablePermutation(path, resp); } else { servePermutationSpecificFile(path, permutation, resp); } } catch (Exception e) { handleSelectionException(path, e, resp); } } } private boolean isAppCacheDisabled(HttpServletRequest req) { if (req.getCookies() != null) { for (Cookie cookie : req.getCookies()) { if (cookie.getName().equals(Html5AppCache.DISABLE_COOKIE_NAME) && cookie.getValue().equals(Html5AppCache.DISABLE_COOKIE_VALUE)) { return true; } } } return false; } private void serveDefault(HttpServletResponse resp, Path resource) throws IOException { serve(resp, resource.moduleBase + resource.file); } private void servePermutationSpecificFile(Path path, String permutation, HttpServletResponse resp) throws IOException, ServletException { // first verify that the file exists and is readable String resource = resolvePermutationSpecificResource(path, permutation); if (!new File(getServletContext().getRealPath(resource)).exists()) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } else { resp.setDateHeader("Expires", new Date().getTime()); if (path.file.endsWith(".appcache")) { resp.setContentType("text/cache-manifest"); } else if (path.file.endsWith(".js")) { resp.setContentType("application/javascript"); } serve(resp, resource); } } protected String resolvePermutationSpecificResource(Path path, String permutation) throws ServletException { if (path.file.startsWith(path.moduleName + ".")) { return path.moduleBase + permutation + path.file.substring(path.moduleName.length()); } else { logger.severe("ScriptSelectionServlet does not know how to serve '" + path.file + ", expected '" + path.moduleName + ".xxxx'"); throw new ServletException(); } } private void serve(HttpServletResponse resp, String path) throws IOException { InputStream is = new FileInputStream(getServletContext().getRealPath(path)); ServletOutputStream os = resp.getOutputStream(); byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = is.read(buffer)) != -1) { os.write(buffer, 0, bytesRead); } } private Path getModuleBase(HttpServletRequest req) throws ServletException { String uri = req.getRequestURI(); int lastSlash = uri.lastIndexOf('/'); if (lastSlash < 1) { throw new ServletException("Request for resource must be in module path. URI = " + uri); } String file = uri.substring(lastSlash + 1); String path = uri.substring(0, lastSlash + 1); lastSlash = path.lastIndexOf('/', path.length() - 2); String module = path.substring(lastSlash + 1, path.length() - 1); return new Path(path, file, module); } private JsonArray readPermutationMap(Path path) { try { InputStreamReader reader = new InputStreamReader( new FileInputStream(getServletContext().getRealPath(path.moduleBase + "permutations"))); JsonParser parser = new JsonParser(); return (JsonArray) parser.parse(reader); } catch (FileNotFoundException e) { logger.info( "No permutations map found, (we are probably in dev mode) will return default selection script"); return null; } } private String computePermutation(HttpServletRequest req, JsonArray permutationMap) throws ServletException { Map<String, String> properties = computeProperties(req); Set<String> matches = new HashSet<>(); for (int i = 0; i != permutationMap.size(); ++i) { JsonObject permutation = (JsonObject) permutationMap.get(i); if (matches(properties, permutation)) { matches.add(permutation.get("permutation").getAsString()); } } if (matches.size() == 1) { return matches.iterator().next(); } else { return null; } } private Map<String, String> computeProperties(HttpServletRequest req) { Map<String, String> properties = new HashMap<>(); for (Entry<String, PropertyProvider> entry : providers.entrySet()) { properties.put(entry.getKey(), entry.getValue().get(req)); } return properties; } private boolean matches(Map<String, String> properties, JsonObject permutation) { String strongName = permutation.get("permutation").getAsString(); JsonObject permProperties = permutation.getAsJsonObject("properties"); for (Map.Entry<String, JsonElement> property : permProperties.entrySet()) { String expected = property.getValue().getAsString(); String actual = properties.get(property.getKey()); if (actual != null && !expected.equals(actual)) { logger.finest("Rejecting " + strongName + ", expected property '" + property.getValue() + "' " + "with value '" + expected + "', found '" + actual + "'"); return false; } } return true; } /** * Handles the case in which an exception was thrown while trying to compute * properties for the selection of the permutation. * */ protected void handleSelectionException(Path path, Exception e, HttpServletResponse resp) throws IOException { sendErrorMessage(path, "Error selecting permutation: " + e.getMessage(), resp); } protected void handleNoAvailablePermutation(Path path, HttpServletResponse resp) throws IOException { sendErrorMessage(path, "Your browser is unsupported", resp); } protected final void sendErrorMessage(Path path, String message, HttpServletResponse resp) throws IOException { if (path.file.endsWith(".js")) { resp.setContentType("application/javascript"); resp.getWriter().println("window.alert('" + message.replace("'", "\'") + "');"); } else { resp.sendError(CACHE_OBSOLETE, message); } } protected final static class Path { public final String moduleBase; public final String file; public final String moduleName; public Path(String path, String file, String moduleName) { super(); this.moduleBase = path; this.file = file; this.moduleName = moduleName; } } }