From 9ea199521481b08e8a349953323cd9a3b58d3801 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Leif=20=C3=85strand?= Date: Wed, 9 Jul 2014 18:45:37 +0300 Subject: [PATCH] Cache sass output compiled on the fly by VaadinServlet (#9532) Change-Id: I5ce36a5c3beffe7b686a8754b6f7311d83b00f87 --- .../src/com/vaadin/server/VaadinServlet.java | 134 ++++++++++++++---- 1 file changed, 108 insertions(+), 26 deletions(-) diff --git a/server/src/com/vaadin/server/VaadinServlet.java b/server/src/com/vaadin/server/VaadinServlet.java index 12e7c28cd8..09b8a22a46 100644 --- a/server/src/com/vaadin/server/VaadinServlet.java +++ b/server/src/com/vaadin/server/VaadinServlet.java @@ -16,12 +16,14 @@ package com.vaadin.server; import java.io.BufferedWriter; +import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; +import java.io.Serializable; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; @@ -29,7 +31,10 @@ import java.net.URLConnection; import java.util.Arrays; import java.util.Collection; import java.util.Enumeration; +import java.util.HashMap; import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; @@ -51,6 +56,59 @@ import com.vaadin.util.CurrentInstance; @SuppressWarnings("serial") public class VaadinServlet extends HttpServlet implements Constants { + private static class ScssCacheEntry implements Serializable { + + private final String css; + private final List sourceUris; + private final long timestamp; + + public ScssCacheEntry(String css, List sourceUris) { + this.css = css; + this.sourceUris = sourceUris; + + timestamp = getLastModified(); + } + + public String getCss() { + return css; + } + + private long getLastModified() { + long newest = 0; + for (String uri : sourceUris) { + File file = new File(uri); + if (!file.exists()) { + return -1; + } else { + newest = Math.max(newest, file.lastModified()); + } + } + + return newest; + } + + public boolean isStillValid() { + if (timestamp == -1) { + /* + * Don't ever bother checking anything if files used during the + * compilation were gone before the cache entry was created. + */ + return false; + } else if (timestamp != getLastModified()) { + /* + * Would in theory still be valid if the last modification is + * before the recorded timestamp, but that would still mean that + * something has changed since we last checked, so let's + * invalidate in that case as well to be on the safe side. + */ + return false; + } else { + return true; + } + } + + } + private VaadinServletService servletService; /** @@ -535,9 +593,17 @@ public class VaadinServlet extends HttpServlet implements Constants { * Mutex for preventing to scss compilations to take place simultaneously. * This is a workaround needed as the scss compiler currently is not thread * safe (#10292). + *

+ * In addition, this is also used to protect the cached compilation results. */ private static final Object SCSS_MUTEX = new Object(); + /** + * Global cache of scss compilation results. This map is protected from + * concurrent access by {@link #SCSS_MUTEX}. + */ + private static final Map scssCache = new HashMap(); + /** * Returns the default theme. Must never return null. * @@ -824,46 +890,62 @@ public class VaadinServlet extends HttpServlet implements Constants { } synchronized (SCSS_MUTEX) { - String realFilename = sc.getRealPath(scssFilename); - ScssStylesheet scss = ScssStylesheet.get(realFilename); - if (scss == null) { - // Not a file in the file system (WebContent directory). Use the - // identifier directly (VAADIN/themes/.../styles.css) so - // ScssStylesheet will try using the class loader. - if (scssFilename.startsWith("/")) { - scssFilename = scssFilename.substring(1); - } + ScssCacheEntry cacheEntry = scssCache.get(scssFilename); - scss = ScssStylesheet.get(scssFilename); + if (cacheEntry == null || !cacheEntry.isStillValid()) { + cacheEntry = compileScssOnTheFly(filename, scssFilename, sc); + scssCache.put(scssFilename, cacheEntry); } - if (scss == null) { - getLogger() - .log(Level.WARNING, - "Scss file {0} exists but ScssStylesheet was not able to find it", - scssFilename); - return false; - } - try { - getLogger().log(Level.FINE, "Compiling {0} for request to {1}", - new Object[] { realFilename, filename }); - scss.compile(); - } catch (Exception e) { - getLogger().log(Level.WARNING, "Scss compilation failed", e); + if (cacheEntry == null) { + // compilation did not produce any result, but logged a message return false; } // This is for development mode only so instruct the browser to - // never - // cache it + // never cache it response.setHeader("Cache-Control", "no-cache"); final String mimetype = getService().getMimeType(filename); - writeResponse(response, mimetype, scss.printState()); + writeResponse(response, mimetype, cacheEntry.getCss()); return true; } } + private ScssCacheEntry compileScssOnTheFly(String filename, + String scssFilename, ServletContext sc) throws IOException { + String realFilename = sc.getRealPath(scssFilename); + ScssStylesheet scss = ScssStylesheet.get(realFilename); + if (scss == null) { + // Not a file in the file system (WebContent directory). Use the + // identifier directly (VAADIN/themes/.../styles.css) so + // ScssStylesheet will try using the class loader. + if (scssFilename.startsWith("/")) { + scssFilename = scssFilename.substring(1); + } + + scss = ScssStylesheet.get(scssFilename); + } + + if (scss == null) { + getLogger() + .log(Level.WARNING, + "Scss file {0} exists but ScssStylesheet was not able to find it", + scssFilename); + return null; + } + try { + getLogger().log(Level.FINE, "Compiling {0} for request to {1}", + new Object[] { realFilename, filename }); + scss.compile(); + } catch (Exception e) { + getLogger().log(Level.WARNING, "Scss compilation failed", e); + return null; + } + + return new ScssCacheEntry(scss.printState(), scss.getSourceUris()); + } + /** * Check whether a URL obtained from a classloader refers to a valid static * resource in the directory VAADIN. -- 2.39.5