com.github.edgarespina.mwa.mvc.HtmlTemplates Maven / Gradle / Ivy
package com.github.edgarespina.mwa.mvc;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.util.Assert;
import org.springframework.web.context.ServletContextAware;
import org.springframework.web.context.support.ServletContextResourceLoader;
import org.springframework.web.servlet.ModelAndView;
import com.google.common.base.Charsets;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import com.google.common.io.Files;
import com.google.common.io.Resources;
/**
* Publish one or more HTML templates as model attributes.
* Given a template directory:
*
*
* /templates
* a.html
* b.html
* /module
* a.html
*
*
* If you want to publish the content of this directory as Spring model
* attributes, you need:
*
*
* new ModernViewResolver(
* new HtmlTemplates("/templates", " html");
* );
*
*
* From the view you can access to a template by:
*
*
* <script type="text/html" id="superA">
* ${templates.a}
* </script>
*
*
* @author edgar.espina
* @since 0.1
*/
public class HtmlTemplates extends AbstractModelContribution implements
ServletContextAware {
/**
* Load templates from file system.
*
* @author edgar.espina
* @since 0.5
*/
private interface TemplateLoader {
/**
* Returns templates as map instance.
*
* @param directory Where we should look for? Required.
* @param extension The template's extension. Required.
* @return A map with all the templates.
* @throws IOException If a file cannot be read it.
*/
Map get(String directory, String extension)
throws IOException;
}
/**
* Load templates from the file system.
*
* @author edgar.espina
* @since 0.5
*/
private class DefaultTemplateLoader implements TemplateLoader {
/**
* The lookup strategy.
*/
private ResourcePatternResolver resolver;
/**
* Creates a {@link DefaultTemplateLoader}.
*
* @param context The Servlet Context.
*/
public DefaultTemplateLoader(final ServletContext context) {
resolver = new PathMatchingResourcePatternResolver(
new ServletContextResourceLoader(context));
}
/**
* {@inheritDoc}
*/
@Override
public Map get(final String directory,
final String extension) throws IOException {
Map scope = new HashMap();
Resource[] resources =
resolver.getResources(directory + "/**/*." + extension);
for (Resource resource : resources) {
String[] path = relativePath(directory, resource);
// Read content.
String content = Resources.toString(resource.getURL(), Charsets.UTF_8);
String filename = path[path.length - 1];
String filenameNoExt =
filename.substring(0,
filename.length() - Files.getFileExtension(filename).length()
- 1);
scope(scope, path).put(filenameNoExt, content);
}
return scope;
}
/**
* Creates a relative path.
*
* @param directory The directory.
* @param resource The resource.
* @return A relative path.
* @throws IOException If the URI cannot be detected.
*/
private String[] relativePath(final String directory,
final Resource resource) throws IOException {
String fullpath = resource.getURI().getPath();
final String separator = "/";
String relativePath = fullpath
.substring(fullpath.indexOf(directory) + separator.length());
// Make it relative.
Iterable path = Splitter.on(separator)
.omitEmptyStrings()
.trimResults()
.split(relativePath);
return Iterables.toArray(path, String.class);
}
/**
* Return correct scope for the given path.
*
* @param scope The root scope.
* @param path The path of the template.
* @return The correct scope for the given path.
*/
private Map scope(final Map scope,
final String[] path) {
Map root = scope;
for (int i = 0; i < path.length - 1; i++) {
@SuppressWarnings("unchecked")
Map child = (Map) root.get(path[i]);
if (child == null) {
child = new HashMap();
root.put(path[i], child);
}
root = child;
}
return root;
}
}
/**
* Cache support for {@link TemplateLoader}.
*
* @author edgar.espina
* @since 0.5
*/
private class CacheTemplateLoader implements TemplateLoader {
/**
* Who is the man?
*/
private TemplateLoader loader;
/**
* The cache.
*/
private Map cache;
/**
* Creates a new {@link CacheTemplateLoader}.
*
* @param loader Who is the man?
*/
public CacheTemplateLoader(final TemplateLoader loader) {
this.loader = loader;
}
/**
* {@inheritDoc}
*/
@Override
public Map get(final String directory,
final String extension)
throws IOException {
if (cache == null) {
cache = loader.get(directory, extension);
}
return cache;
}
}
/**
* Where are the templates?
*/
private String directory;
/**
* How they looks?
*/
private String extension;
/**
* The template loader.
*/
private TemplateLoader loader;
/**
* Creates a new {@link HtmlTemplates} model contribution.
*
* @param directory Where are the templates (/temp, /templates, etc.)?
* @param extension How they looks (html, mustache, etc.)?
*/
public HtmlTemplates(final String directory, final String extension) {
Assert.notNull(directory, "The template's directory is required.");
Assert.isTrue(directory.length() > 0,
"The template's directory is required.");
Assert.notNull(extension, "The template's directory is required.");
Assert.isTrue(extension.length() > 0,
"The template's directory is required.");
this.directory = directory;
this.extension = extension;
}
/**
* {@inheritDoc}
*/
@Override
public void contribute(final HttpServletRequest request,
final HttpServletResponse response, final ModelAndView modelAndView)
throws IOException {
modelAndView.getModel().putAll(loader.get(directory, extension));
}
/**
* {@inheritDoc}
*/
@Override
public void setServletContext(final ServletContext context) {
try {
this.loader = new DefaultTemplateLoader(context);
if (useCache()) {
// Wrap the default loader and add cache support
this.loader = new CacheTemplateLoader(this.loader);
// hit and load the cache immediately
this.loader.get(directory, extension);
}
} catch (IOException ex) {
throw new IllegalStateException("Unable to load templates.", ex);
}
}
}