/*
* Copyright 2013 eXo Platform SAS
*
* 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 juzu.template;
import juzu.PropertyMap;
import juzu.Response;
import juzu.impl.common.Tools;
import juzu.impl.plugin.template.TemplateService;
import juzu.io.Chunk;
import juzu.io.ChunkBuffer;
import juzu.io.OutputStream;
import juzu.io.Stream;
import juzu.io.UndeclaredIOException;
import juzu.impl.plugin.application.Application;
import juzu.impl.request.Request;
import juzu.impl.common.Path;
import juzu.impl.template.spi.TemplateStub;
import juzu.impl.template.spi.juzu.dialect.gtmpl.MessageKey;
import juzu.request.ApplicationContext;
import juzu.request.RequestContext;
import javax.inject.Inject;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.concurrent.atomic.AtomicReference;
/**
* <p></p>A template as seen by an application. A template is identified by its {@link #path} and can used to produce markup.
* Templates perform rendering using a parameter map and a locale as inputs and produces a markup response.</p>
*
* <p>Templates can be used to produce a controller response using the API like</p>
*
* <code><pre>
* public Response.Content index() {
* return template.ok();
* }
* </pre></code>
*
* <p>The template API offers also methods for returning other response status:</p>
*
* <ul>
* <li><code>template.notFound()</code></li>
* <li><code>template.status(code)</code></li>
* </ul>
*
* <p>Template rendering can also be parameterized with a parameter map:</p>
*
* <code><pre>
* public Response.Content index() {
* return template.with(parameters).ok();
* }
* </pre></code>
*
* <p>Template can be parameterized using a fluent API with the {@link Builder} object provided by the {@link #with()}
* method:</p>
*
* <code><pre>return template.with().set("date", new java.util.Date()).ok()</pre></code>
*
* <p>The template compiler produces also a subclass of the template that can be used instead of this base template class.
* This sub class overrides the {@link #with()} method to return a builder that provides typed methods when the
* template declares parameters:</p>
*
* <code><pre>return template.with().date(new java.util.Date()).ok()</pre></code>
*
* @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a>
*/
public abstract class Template {
/** . */
private final Path path;
/** . */
private final TemplateService plugin;
@Inject
Application application;
public Template(TemplateService plugin, String path) {
this(plugin, Path.parse(path));
}
public Template(TemplateService plugin, Path path) {
this.plugin = plugin;
this.path = path;
}
/**
* Returns the template path.
*
* @return the temlate path
*/
public final Path getPath() {
return path;
}
@Override
public final String toString() {
return getClass().getSimpleName() + "[path=" + path + "]";
}
/**
* Renders the template.
*
* @return the ok resource response
*/
public final Response.Content ok() {
return with().ok();
}
/**
* Renders the template.
*
* @param locale the locale
* @return the ok resource response
*/
public final Response.Content ok(Locale locale) {
return with().with(locale).ok();
}
/**
* Renders the template and set the response on the current {@link RequestContext}.
*
* @param parameters the parameters
* @return the ok resource response
*/
public final Response.Content ok(Map<String, ?> parameters) {
return with(parameters).ok();
}
/**
* Renders the template.
*
* @param parameters the parameters
* @param locale the locale
* @return the ok resource response
*/
public final Response.Content ok(Map<String, ?> parameters, Locale locale) {
return with(parameters).with(locale).ok();
}
/**
* Renders the template.
*
* @return the not found resource response
*/
public final Response.Content notFound() {
return notFound(null, null);
}
/**
* Renders the template.
*
* @param locale the locale
* @return the not found resource response
*/
public final Response.Content notFound(Locale locale) {
return notFound(null, locale);
}
/**
* Renders the template.
*
* @param parameters the parameters
* @return the not found resource response
*/
public final Response.Content notFound(Map<String, ?> parameters) {
return notFound(parameters, null);
}
/**
* Renders the template.
*
* @param parameters the parameters
* @param locale the locale
* @return the not found resource response
*/
public final Response.Content notFound(Map<String, ?> parameters, Locale locale) {
return with(parameters).with(locale).notFound();
}
/**
* Renders the template to the specified appendable.
*
* @param appendable the appendable
* @throws TemplateExecutionException any execution exception
* @throws UndeclaredIOException any io exception
*/
public <A extends Appendable> A renderTo(A appendable) throws TemplateExecutionException, UndeclaredIOException {
return with().renderTo(appendable);
}
/**
* Renders the template to the specified appendable.
*
* @param appendable the appendable
* @param locale the locale
* @throws TemplateExecutionException any execution exception
* @throws UndeclaredIOException any io exception
*/
public <A extends Appendable> A renderTo(A appendable, Locale locale) throws TemplateExecutionException, UndeclaredIOException {
return with().with(locale).renderTo(appendable);
}
/**
* Renders the template to the specified appendable.
*
* @param appendable the appendable
* @param parameters the attributes
* @throws TemplateExecutionException any execution exception
* @throws UndeclaredIOException any io exception
*/
public <A extends Appendable> A renderTo(A appendable, Map<String, ?> parameters) throws TemplateExecutionException, UndeclaredIOException {
return with(parameters).renderTo(appendable);
}
/**
* Renders the template to the specified printer.
*
* @param printer the printer
* @throws TemplateExecutionException any execution exception
* @throws UndeclaredIOException any io exception
*/
public void renderTo(Stream printer) throws TemplateExecutionException, UndeclaredIOException {
with().renderTo(printer);
}
/**
* Renders the template to the specified printer.
*
* @param printer the printer
* @param locale the locale
* @throws TemplateExecutionException any execution exception
* @throws UndeclaredIOException any io exception
*/
public void renderTo(Stream printer, Locale locale) throws TemplateExecutionException, UndeclaredIOException {
with().with(locale).renderTo(printer);
}
/**
* Renders the template to the specified printer.
*
* @param printer the printer
* @param parameters the attributes
* @throws TemplateExecutionException any execution exception
* @throws UndeclaredIOException any io exception
*/
public void renderTo(Stream printer, Map<String, ?> parameters) throws TemplateExecutionException, UndeclaredIOException {
with(parameters).renderTo(printer);
}
/**
* Create a new builder.
*
* @return a new builder instance
*/
protected abstract Builder builder();
/**
* Returns a builder to further customize the template rendering.
*
* @return a new builder instance
*/
public Builder with() {
return builder();
}
/**
* Returns a builder to further customize the template rendering.
*
* @return a new builder instance
*/
public Builder with(Map<String, ?> parameters) {
Builder builder = with();
builder.parameters = (Map<String, Object>)parameters;
return builder;
}
/**
* Returns a builder to further customize the template rendering.
*
* @return a new builder instance
*/
public Builder with(Locale locale) {
Builder builder = with();
builder.locale = locale;
return builder;
}
/**
* A builder providing a fluent syntax for rendering a template.
*/
public class Builder {
/** The parameters. */
private Map<String, Object> parameters;
/** The locale. */
private Locale locale;
private Locale computeLocale() {
if (locale == null) {
return Request.getCurrent().getUserContext().getLocale();
} else {
return locale;
}
}
private void doRender(PropertyMap properties, Appendable appendable) throws UndeclaredIOException {
OutputStream out = OutputStream.create(Tools.UTF_8, appendable);
doRender(properties, out);
final AtomicReference<IOException> ios = new AtomicReference<IOException>();
out.close(new Thread.UncaughtExceptionHandler() {
public void uncaughtException(Thread t, Throwable e) {
if (e instanceof IOException) {
ios.set((IOException)e);
}
}
});
if (ios.get() != null) {
throw new UndeclaredIOException(ios.get());
}
}
private void doRender(PropertyMap properties, Stream stream) {
try {
// Get the specified locale or the current user's one
final Locale locale = computeLocale();
//
TemplateStub stub = plugin.resolveTemplateStub(path);
if (stub == null) {
throw new UnsupportedOperationException("Handle me gracefully: couldn't get stub for template " + path);
}
//
TemplateRenderContext context = new TemplateRenderContext(
stub,
properties,
parameters,
locale
) {
/** . */
ResourceBundle bundle = null;
/** . */
boolean bundleLoaded = false;
@Override
public void renderTag(String name, Renderable body, Map<String, String> parameters) throws IOException {
TagHandler handler = plugin.resolveTag(name);
handler.render(this, body, parameters);
}
@Override
public TemplateStub resolveTemplate(String path) {
return plugin.resolveTemplateStub(path);
}
@Override
public Object resolveBean(String expression) throws InvocationTargetException {
return application.resolveBean(expression);
}
@Override
public String resolveMessage(MessageKey key) {
// Lazy load the bundle here
if (!bundleLoaded) {
bundleLoaded = true;
if (locale != null) {
ApplicationContext applicationContext = Request.getCurrent().getApplicationContext();
if (applicationContext != null) {
bundle = applicationContext.resolveBundle(locale);
}
}
}
//
String value = null;
if (bundle != null) {
try {
value = bundle.getString(key.getValue());
}
catch (MissingResourceException notFound) {
// System.out.println("Could not resolve message " + key.getValue());
}
}
return value != null ? value : "";
}
};
//
context.render(stream);
}
catch (IOException e) {
throw new UndeclaredIOException(e);
}
}
/**
* Update the locale.
*
* @param locale the new locale
* @return this builder
*/
public Builder with(Locale locale) {
this.locale = locale;
return this;
}
/**
* Update a parameter, if the value is not null the parameter with the specified name is set, otherwise the
* parameter is removed. If the parameter is set and a value was set previously, the old value is overwritten
* otherwise. If the parameter is removed and the value does not exist, nothing happens.
*
* @param name the parameter name
* @param value the parameter value
* @return this builder
* @throws NullPointerException if the name argument is null
*/
public Builder set(String name, Object value) throws NullPointerException {
if (name == null) {
throw new NullPointerException("The parameter argument cannot be null");
}
if (value != null) {
if (parameters == null) {
parameters = new HashMap<String, Object>();
}
parameters.put(name, value);
}
else if (parameters != null) {
parameters.remove(name);
}
return this;
}
/**
* Renders the template and set the response on the current {@link RequestContext}.
*
* @return the ok resource response
*/
public final Response.Content ok() throws UndeclaredIOException {
return status(200);
}
/**
* Renders the template and returns a response with the not found status.
*
* @return the not found response
*/
public final Response.Content notFound() throws UndeclaredIOException {
return status(404);
}
/**
* Renders the template and returns a response with the specified status.
*
* @return the response
*/
public final Response.Content status(int status) throws UndeclaredIOException {
StringBuilder sb = new StringBuilder();
PropertyMap properties = new PropertyMap();
doRender(properties, sb);
ChunkBuffer buffer = new ChunkBuffer().append(Chunk.create(sb)).close();
return new Response.Content(status, properties, buffer);
}
/**
* Renders the template to the specified appendable.
*
* @param appendable the appendable
* @throws TemplateExecutionException any execution exception
* @throws UndeclaredIOException any io exception
*/
public <A extends Appendable> A renderTo(A appendable) throws TemplateExecutionException, UndeclaredIOException {
doRender(null, appendable);
return appendable;
}
/**
* Renders the template to the specified printer.
*
* @param printer the printer
* @throws TemplateExecutionException any execution exception
* @throws UndeclaredIOException any io exception
*/
public void renderTo(Stream printer) throws TemplateExecutionException, UndeclaredIOException {
if (printer == null) {
throw new NullPointerException("No null printe provided");
}
doRender(null, printer);
}
}
}