/* * Copyright 2013-present Facebook, 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.facebook.buck.httpserver; import com.google.common.annotations.VisibleForTesting; import com.google.common.net.MediaType; import com.google.template.soy.SoyFileSet; import com.google.template.soy.data.SoyMapData; import com.google.template.soy.tofu.SoyTofu; import java.io.IOException; import java.net.URL; import javax.annotation.Nullable; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.AbstractHandler; /** {@link Handler} that provides a response by populating a Soy template. */ class TemplateHandler extends AbstractHandler { /** * Whether to re-parse the templates for this handler on every request. * * <p>Should be set to {@code false} when checked in, but it is convenient to set this to {@code * true} in development. */ private static final boolean DEBUG = Boolean.getBoolean("buck.soy.debug"); private final TemplateHandlerDelegate delegate; /** This field is set lazily by {@link #createAndMaybeCacheSoyTofu()}. */ @Nullable private volatile SoyTofu tofu; protected TemplateHandler(TemplateHandlerDelegate templateBasedHandler) { this.delegate = templateBasedHandler; } /** * Handles a request. Invokes {@link TemplateHandlerDelegate#getTemplateForRequest(Request)} to * get the template and {@link TemplateHandlerDelegate#getDataForRequest(Request)} to get the * template data, and then combines them to produce the response. */ @Override public void handle( String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { String html = createHtmlForResponse(baseRequest); if (html != null) { Responses.writeSuccessfulResponse(html, MediaType.HTML_UTF_8, baseRequest, response); } else { Responses.writeFailedResponse(baseRequest, response); } } @Nullable String createHtmlForResponse(Request baseRequest) throws IOException { String template = delegate.getTemplateForRequest(baseRequest); SoyTofu.Renderer renderer = createAndMaybeCacheSoyTofu().newRenderer(template); SoyMapData data = delegate.getDataForRequest(baseRequest); if (data != null) { renderer.setData(data); return renderer.render(); } else { return null; } } @VisibleForTesting TemplateHandlerDelegate getDelegate() { return delegate; } /** * Returns the {@link SoyTofu} for {@link TemplateHandlerDelegate#getTemplates()}. If {@link * #DEBUG} is {@code false}, then the result will be cached because {@link SoyTofu} objects can be * expensive to construct. */ private SoyTofu createAndMaybeCacheSoyTofu() { // In debug mode, create a new SoyTofu object for each request. This makes it possible to test // new versions of the templates without restarting buckd. if (DEBUG) { return createSoyTofu(); } // In production, cache the SoyTofu object for efficiency. if (tofu != null) { return tofu; } synchronized (this) { if (tofu == null) { tofu = createSoyTofu(); } } return tofu; } /** Creates the {@link SoyTofu} for {@link TemplateHandlerDelegate#getTemplates()}. */ private SoyTofu createSoyTofu() { SoyFileSet.Builder builder = SoyFileSet.builder(); for (String soyFile : delegate.getTemplates()) { URL url = delegate.getClass().getResource(soyFile); builder.add(url); } return builder.build().compileToTofu(); } }