package com.google.sitebricks;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.util.Arrays;
import javax.servlet.ServletContext;
import net.jcip.annotations.Immutable;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.sitebricks.compiler.TemplateCompiler;
import com.google.sitebricks.routing.PageBook;
/**
* @author Dhanji R. Prasanna (dhanji@gmail.com)
*/
@Immutable
public class TemplateLoader {
private final Provider<ServletContext> context;
private final TemplateSystem templateSystem;
@Inject
public TemplateLoader(Provider<ServletContext> context, TemplateSystem templateSystem) {
this.context = context;
this.templateSystem = templateSystem;
}
public Renderable compile(PageBook.Page page) {
Show methodShow = page.getShow();
Template template = load(page.pageClass(), methodShow);
TemplateCompiler templateCompiler = templateSystem.compilerFor(template.getName());
if (templateCompiler == null) {
templateCompiler = templateSystem.compilerFor("html");
}
return templateCompiler.compile(page.pageClass(), template);
}
public Renderable compile(Class<?> templateClass) {
Template template = load(templateClass, null);
TemplateCompiler templateCompiler = templateSystem.compilerFor(template.getName());
//
// This is how the old mechanism worked, for example if dynamic.js comes through the system we still pass back
// the html compiler. JVZ: not sure why this wouldn't be directly routed to the right resource. TODO: investigate
//
if (templateCompiler == null) {
templateCompiler = templateSystem.compilerFor("html");
}
return templateCompiler.compile(templateClass, template);
}
// protected for the tests access...
protected Template load(Class<?> pageClass, Show methodShow) {
//
// try to find the template name
//
String template = null;
String extension = null;
Show show = pageClass.getAnnotation(Show.class);
if (null != show) {
template = show.value();
}
if (methodShow != null) {
if (template != null) {
template = template + methodShow.value();
}
else {
template = methodShow.value();
}
}
//
// an empty string means no template name was given
//
if (template == null || template.length() == 0) {
// use the default name for the page class
template = pageClass.getSimpleName();
} else {
// Check and see if the supplied template value has a supported file extension
for (String ext : templateSystem.getTemplateExtensions()) {
String type = ext.replace("%s.", ".");
if (template.endsWith(type)) {
extension = type;
break;
}
}
}
if (null == template) {
throw new MissingTemplateException(String.format("Could not determine the base template name for %s", Show.class));
}
TemplateSource templateSource = null;
String text;
boolean appendExtension = false;
try {
final ServletContext servletContext = context.get();
InputStream stream = null;
//
// If there was a matching file extension, short-circuit the deep search
//
if (template.contains(".") || null != extension) {
// Check class neighborhood for direct match
stream = pageClass.getResourceAsStream(template);
// Check url conventions for direct match
if (null == stream) {
stream = open(template, servletContext);
}
// Same as above, but checks in WEB-INF
if (null == stream) {
stream = openWebInf(template, servletContext);
}
// Finally, try to get the resource from the servlet context internally
if (null == stream) {
stream = servletContext.getResourceAsStream(template);
}
}
//
// No direct match, so start hunting.
// First, look in class neighborhood for template
//
if (null == stream) {
appendExtension = true;
for (String ext : templateSystem.getTemplateExtensions()) {
String name = String.format(ext, template);
stream = pageClass.getResourceAsStream(name);
if (null != stream) {
extension = ext;
break;
}
}
}
//
// Check for qualified file paths in context
// TODO: I think this is redundant, but make sure before deleting
//
if (null == stream) {
for (String ext : templateSystem.getTemplateExtensions()) {
String name = String.format(ext, pageClass.getSimpleName());
stream = open(name, servletContext);
if (null != stream) {
extension = ext;
break;
}
}
}
//
// Same thing, but check in WEB-INF
//
if (null == stream) {
for (String ext : templateSystem.getTemplateExtensions()) {
String name = String.format(ext, pageClass.getSimpleName());
stream = openWebInf(name, servletContext);
if (null != stream) {
extension = ext;
break;
}
}
}
//
// Finally, look in the ServletContext resource path if not in classpath
//
if (null == stream) {
for (String ext : templateSystem.getTemplateExtensions()) {
String name = String.format(ext, template);
stream = servletContext.getResourceAsStream(name);
if (null != stream) {
extension = ext;
break;
}
}
}
//
//if there's still no template, then error out
//
if (null == stream) {
throw new MissingTemplateException(String.format(
"Could not find a suitable template for %s, did you remember to place an @Show? None of " +
Arrays.toString(templateSystem.getTemplateExtensions()).replace("%s.", ".")
+ " could be found in either package [%s], in the root of the resource dir OR in WEB-INF/.",
pageClass.getName(), pageClass.getSimpleName(), pageClass.getPackage().getName()));
}
text = read(stream);
} catch (IOException e) {
throw new TemplateLoadingException("Could not load template for (i/o error): " + pageClass, e);
}
if (appendExtension)
template += "." + extension;
return new Template(template, text, templateSource);
}
private static InputStream open(String templateName, ServletContext context) {
try {
String path = context.getRealPath(templateName);
return path == null ? null : new FileInputStream(path);
} catch (FileNotFoundException e) {
return null;
}
}
private static InputStream openWebInf(String templateName, ServletContext context) {
return open("/WEB-INF/" + templateName, context);
}
private static String read(InputStream stream) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
StringBuilder builder = new StringBuilder();
try {
while (reader.ready()) {
builder.append(reader.readLine());
builder.append("\n");
}
} finally {
stream.close();
}
return builder.toString();
}
}