/** * Copyright 2005-2014 Restlet * * The contents of this file are subject to the terms of one of the following * open source licenses: Apache 2.0 or or EPL 1.0 (the "Licenses"). You can * select the license that you prefer but you may not use this file except in * compliance with one of these Licenses. * * You can obtain a copy of the Apache 2.0 license at * http://www.opensource.org/licenses/apache-2.0 * * You can obtain a copy of the EPL 1.0 license at * http://www.opensource.org/licenses/eclipse-1.0 * * See the Licenses for the specific language governing permissions and * limitations under the Licenses. * * Alternatively, you can obtain a royalty free commercial license with less * limitations, transferable or non-transferable, directly at * http://restlet.com/products/restlet-framework * * Restlet is a registered trademark of Restlet S.A.S. */ package org.restlet.ext.thymeleaf; import java.io.IOException; import java.io.Writer; import java.util.Locale; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import org.restlet.Request; import org.restlet.Response; import org.restlet.data.Form; import org.restlet.data.MediaType; import org.restlet.representation.WriterRepresentation; import org.restlet.util.Resolver; import org.thymeleaf.TemplateEngine; import org.thymeleaf.context.Context; import org.thymeleaf.context.IContext; import org.thymeleaf.context.VariablesMap; import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver; import org.thymeleaf.templateresolver.ITemplateResolver; import org.thymeleaf.templateresolver.TemplateResolver; import org.thymeleaf.util.Validate; /** * Thymeleaf template representation. Useful for dynamic string-based * representations. * * @see <a href="http://www.thymeleaf.org/">Thymeleaf home page</a> * @author Grzegorz Godlewski */ public class TemplateRepresentation extends WriterRepresentation { /** * Context that leverages an instance of {@link Resolver}. * * @author Grzegorz Godlewski */ private static class ResolverContext implements IContext { /** The Locale. */ private Locale locale; /** The Resolver instance. */ private Resolver<Object> resolver; /** * Constructor. * * @param locale * The Locale. * @param resolver * The Resolver instance. */ public ResolverContext(Locale locale, Resolver<Object> resolver) { this.locale = locale; this.resolver = resolver; } public final void addContextExecutionInfo(final String templateName) { Validate.notEmpty(templateName, "Template name cannot be null or empty"); } public Locale getLocale() { return locale; } @SuppressWarnings("serial") public VariablesMap<String, Object> getVariables() { return new VariablesMap<String, Object>() { @Override public boolean containsKey(Object key) { return resolver.resolve(key.toString()) != null; } @Override public Object get(Object key) { return resolver.resolve(key.toString()); } }; } } /** * Returns a new instance of {@link TemplateEngine} based by default on a * {@link TemplateResolver} returned by calling * {@link #createTemplateResolver()}. * * @return A new instance of {@link TemplateEngine} */ public static TemplateEngine createTemplateEngine() { return createTemplateEngine(createTemplateResolver()); } /** * Returns a new instance of {@link TemplateEngine} based by default on a * {@link TemplateResolver} returned by calling * {@link #createTemplateResolver()}. * * @return A new instance of {@link TemplateEngine} */ public static TemplateEngine createTemplateEngine(ITemplateResolver resolver) { TemplateEngine engine = new TemplateEngine(); engine.setTemplateResolver(resolver); return engine; } /** * Returns a new instance of {@link ITemplateResolver} with default * configuration (XHTML template model, templates located inside * "/WEB-INF/templates/", suffixed by ".html". * * @return A new instance of {@link ITemplateResolver}. */ public static ITemplateResolver createTemplateResolver() { ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver(); // XHTML is the default mode, but we will set it anyway for better // understanding of code templateResolver.setTemplateMode("XHTML"); // This will convert "home" to "/WEB-INF/templates/home.html" templateResolver.setPrefix("/WEB-INF/templates/"); templateResolver.setSuffix(".html"); // Set template cache TTL to 1 hour. If not set, entries would live in // cache until expelled by LRU templateResolver.setCacheTTLMs(3600000L); return templateResolver; } /** The template's data model. */ protected volatile IContext context; /** The Thymeleaf engine. */ private volatile TemplateEngine engine; /** The template locale */ private final Locale locale; /** The template name. */ private volatile String templateName; /** * Constructor. * * @param templateName * The Thymeleaf template's name. The actual template is * retrieved using the Thymeleaf configuration. * @param locale * The locale of the template. * @param dataModel * The Thymeleaf template's data model. * @param mediaType * The representation's media type. */ public TemplateRepresentation(String templateName, Locale locale, Map<String, Object> dataModel, MediaType mediaType) { this(templateName, createTemplateEngine(), locale, mediaType); } /** * Constructor. * * @param templateName * The Thymeleaf template's name. The full path is resolved by * the configuration. * @param locale * The locale of the template. * @param mediaType * The representation's media type. */ public TemplateRepresentation(String templateName, Locale locale, MediaType mediaType) { this(templateName, locale, new ConcurrentHashMap<String, Object>(), mediaType); } /** * Constructor. * * @param templateName * The Thymeleaf template's name. The actual template is * retrieved using the Thymeleaf configuration. * @param engine * The template engine. * @param locale * The locale of the template. * @param dataModel * The Thymeleaf template's data model. * @param mediaType * The representation's media type. */ public TemplateRepresentation(String templateName, TemplateEngine engine, Locale locale, Map<String, Object> dataModel, MediaType mediaType) { super(mediaType); this.locale = locale; this.engine = engine; this.templateName = templateName; setDataModel(dataModel); } /** * Constructor. * * @param templateName * The Thymeleaf template's name. The full path is resolved by * the configuration. * @param locale * The locale of the template * @param mediaType * The representation's media type. */ public TemplateRepresentation(String templateName, TemplateEngine engine, Locale locale, MediaType mediaType) { this(templateName, engine, locale, new ConcurrentHashMap<String, Object>(), mediaType); } /** * Constructor based on a Thymeleaf 'encoded' representation. * * @param templateRepresentation * The representation to 'decode'. * @param locale * The locale of the template. * @param mediaType * The representation's media type. * @throws IOException * @throws ParseErrorException * @throws ResourceNotFoundException */ public TemplateRepresentation( TemplateRepresentation templateRepresentation, Locale locale, MediaType mediaType) throws IOException { this(templateRepresentation, createTemplateEngine(), locale, mediaType); } /** * Constructor based on a Thymeleaf 'encoded' representation. * * @param templateRepresentation * The representation to 'decode'. * @param engine * The template engine. * @param locale * The locale of the template. * @param mediaType * The representation's media type. * @throws IOException * @throws ParseErrorException * @throws ResourceNotFoundException */ public TemplateRepresentation( TemplateRepresentation templateRepresentation, TemplateEngine engine, Locale locale, MediaType mediaType) throws IOException { super(mediaType); this.locale = locale; this.engine = engine; this.templateName = templateRepresentation.getTemplateName(); } /** * Returns the representation's locale. * * @return The representation's locale. */ public Locale getLocale() { return locale; } /** * Returns the template's name. * * @return The template's name. */ public String getTemplateName() { return templateName; } /** * Sets the Thymeleaf context. * * @param context * The Thymeleaf context */ protected void setContext(IContext context) { this.context = context; } /** * Sets the template's data model. * * @param dataModel * The template's data model. */ public void setDataModel(Map<String, Object> dataModel) { Context ctx = new Context(locale); ctx.setVariables(dataModel); setContext(ctx); } /** * Sets the template's data model from a request/response pair. This default * implementation uses a Resolver. * * @see Resolver * @see Resolver#createResolver(Request, Response) * * @param request * The request where data are located. * @param response * The response where data are located. */ public void setDataModel(Request request, Response response) { Form form = new Form(request.getEntity()); Context ctx = new Context(locale); ctx.setVariables(form.getValuesMap()); setContext(ctx); } /** * Sets the template's data model from a resolver. * * @param resolver * The resolver. */ public void setDataModel(Resolver<Object> resolver) { setContext(new ResolverContext(locale, resolver)); } /** * Sets the template's name. * * @param templateName * The template's name. */ public void setTemplateName(String templateName) { this.templateName = templateName; } /** * Writes the datum as a stream of characters. * * @param writer * The writer to use when writing. */ @Override public void write(Writer writer) throws IOException { try { // Load the template // Process the template engine.process(templateName, context, writer); } catch (Exception e) { final org.restlet.Context context = org.restlet.Context .getCurrent(); if (context != null) { context.getLogger().log(Level.WARNING, "Unable to process the template", e); } e.printStackTrace(); throw new IOException("Template processing error. " + e.getMessage()); } } }