blob: 2653af12c062ba3891743b9e3c78a5104d162ad6 [file] [log] [blame]
/*
* 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 org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
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;
/**
* {@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();
}
}