Add template hashing support to Renderer This allows servlets to embed the Soy template's hash as part of an ETag for a resource rendered from that template file. Change-Id: I40e3e2163cf6dbfa04e5b5228448986652faaedf
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/DebugRenderer.java b/gitiles-servlet/src/main/java/com/google/gitiles/DebugRenderer.java index 1de2f69..0a57b6b 100644 --- a/gitiles-servlet/src/main/java/com/google/gitiles/DebugRenderer.java +++ b/gitiles-servlet/src/main/java/com/google/gitiles/DebugRenderer.java
@@ -18,6 +18,7 @@ import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableMap; +import com.google.common.hash.HashCode; import com.google.template.soy.SoyFileSet; import com.google.template.soy.tofu.SoyTofu; @@ -37,10 +38,15 @@ } @Override + public HashCode getTemplateHash(String soyFile) { + return computeTemplateHash(soyFile); + } + + @Override protected SoyTofu getTofu() { SoyFileSet.Builder builder = SoyFileSet.builder() .setCompileTimeGlobals(globals); - for (URL template : templates) { + for (URL template : templates.values()) { try { checkState(new File(template.toURI()).exists(), "Missing Soy template %s", template); } catch (URISyntaxException e) {
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/DefaultRenderer.java b/gitiles-servlet/src/main/java/com/google/gitiles/DefaultRenderer.java index 3893148..f3f244b 100644 --- a/gitiles-servlet/src/main/java/com/google/gitiles/DefaultRenderer.java +++ b/gitiles-servlet/src/main/java/com/google/gitiles/DefaultRenderer.java
@@ -48,7 +48,7 @@ globals, staticPrefix, customTemplates, siteTitle); SoyFileSet.Builder builder = SoyFileSet.builder() .setCompileTimeGlobals(this.globals); - for (URL template : templates) { + for (URL template : templates.values()) { builder.add(template); } tofu = builder.build().compileToTofu();
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/Renderer.java b/gitiles-servlet/src/main/java/com/google/gitiles/Renderer.java index d7814a8..c9e5e80 100644 --- a/gitiles-servlet/src/main/java/com/google/gitiles/Renderer.java +++ b/gitiles-servlet/src/main/java/com/google/gitiles/Renderer.java
@@ -16,24 +16,31 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.base.Function; -import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Iterables; +import com.google.common.collect.MapMaker; import com.google.common.collect.Maps; +import com.google.common.hash.Funnels; +import com.google.common.hash.HashCode; +import com.google.common.hash.Hasher; +import com.google.common.hash.Hashing; +import com.google.common.io.ByteStreams; import com.google.common.net.HttpHeaders; import com.google.template.soy.tofu.SoyTofu; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentMap; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -88,14 +95,25 @@ } } - protected ImmutableList<URL> templates; + protected ImmutableMap<String, URL> templates; protected ImmutableMap<String, String> globals; + private final ConcurrentMap<String, HashCode> hashes = new MapMaker() + .initialCapacity(SOY_FILENAMES.size()) + .concurrencyLevel(1) + .makeMap(); protected Renderer(Function<String, URL> resourceMapper, Map<String, String> globals, String staticPrefix, Iterable<URL> customTemplates, String siteTitle) { checkNotNull(staticPrefix, "staticPrefix"); - Iterable<URL> allTemplates = FluentIterable.from(SOY_FILENAMES).transform(resourceMapper); - templates = ImmutableList.copyOf(Iterables.concat(allTemplates, customTemplates)); + + ImmutableMap.Builder<String, URL> b = ImmutableMap.builder(); + for (String name : SOY_FILENAMES) { + b.put(name, resourceMapper.apply(name)); + } + for (URL u : customTemplates) { + b.put(u.toString(), u); + } + templates = b.build(); Map<String, String> allGlobals = Maps.newHashMap(); for (Map.Entry<String, String> e : STATIC_URL_GLOBALS.entrySet()) { @@ -106,6 +124,29 @@ this.globals = ImmutableMap.copyOf(allGlobals); } + public HashCode getTemplateHash(String soyFile) { + HashCode h = hashes.get(soyFile); + if (h == null) { + h = computeTemplateHash(soyFile); + hashes.put(soyFile, h); + } + return h; + } + + HashCode computeTemplateHash(String soyFile) { + URL u = templates.get(soyFile); + checkState(u != null, "Missing Soy template %s", soyFile); + + Hasher h = Hashing.sha1().newHasher(); + try (InputStream is = u.openStream(); + OutputStream os = Funnels.asOutputStream(h)) { + ByteStreams.copy(is, os); + } catch (IOException e) { + throw new IllegalStateException("Missing Soy template " + soyFile, e); + } + return h.hash(); + } + void render(HttpServletRequest req, HttpServletResponse res, String templateName, Map<String, ?> soyData) throws IOException { res.setContentType("text/html");