All Downloads are FREE. Search and download functionalities are using the official Maven repository.

sirius.web.templates.Templates Maven / Gradle / Ivy

There is a newer version: 22.2.3
Show newest version
/*
 * Made with all the love in the world
 * by scireum in Remshalden, Germany
 *
 * Copyright by scireum GmbH
 * http://www.scireum.de - [email protected]
 */

package sirius.web.templates;

import com.google.common.base.Charsets;
import com.google.common.collect.Lists;
import sirius.kernel.Sirius;
import sirius.kernel.async.CallContext;
import sirius.kernel.commons.Context;
import sirius.kernel.commons.Strings;
import sirius.kernel.di.GlobalContext;
import sirius.kernel.di.std.Part;
import sirius.kernel.di.std.Parts;
import sirius.kernel.di.std.PriorityParts;
import sirius.kernel.di.std.Register;
import sirius.kernel.extensions.Extension;
import sirius.kernel.extensions.Extensions;
import sirius.kernel.health.Exceptions;
import sirius.kernel.health.HandledException;
import sirius.kernel.health.Log;

import javax.annotation.Nonnull;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.Collection;
import java.util.List;

/**
 * Content generator which generates output based on templates.
 * 

* In contrast to the web server and its handlers Velocity is used here as template engine. This is because * these templates are easier to write as they don't need andy type information. as these templates are less * frequently executed the lower performance does not matter. The language reference of velocity, which is one of the * most commonly used language for templates can be found here: * http://velocity.apache.org/engine/devel/vtl-reference-guide.html *

* The template sources are loaded via {@link Resources#resolve(String)}. If no resolver is available or none of the * available ones can load the template, it is tried to load the template from the classpath. *

* To extend the built in velocity engine, macro libraries can be enumerated in the system config under * content.velocity-libraries (For examples see component-web.conf). Also {@link ContentContextExtender} can * be implemented and registered in order to provider default variables within the execution context. *

* Specific output types are generated by {@link ContentHandler} implementations. Those are either picked by the file * name of the template, or by setting {@link Generator#handler(String)}. So if a file ends with .pdf.vm it is * first evaluated by velocity (expecting to generate XHTML) and then rendered to a PDF by flying saucer. * Alternatively the handler type pdf-vm can be set to ensure that this handler is picked. */ @Register(classes = Templates.class) public class Templates { /** * If a specific output encoding is required (other than the system encoding - most definitely UTF-8) a variable * using this key can be supplied to the generator, specifying the name of the encoding to use. *

* If possible however it is preferable to use {@link Generator#encoding(String)} to set the encoding. */ public static final String ENCODING = "encoding"; /* * Logger used by the content generator framework */ public static final Log LOG = Log.get("templates"); /* * Contains all implementations of ContentHandler sorted by getPriority ascending */ @PriorityParts(ContentHandler.class) private Collection handlers; /* * Contains all implementations of ContentContextExtender */ @Parts(ContentContextExtender.class) private Collection extenders; @sirius.kernel.di.std.Context private GlobalContext ctx; @Part private Resources resources; /** * Used to generate content by either evaluating a template or directly supplied template code. *

* This uses a builder like pattern (a.k.a. fluent API) and requires to either call {@link #generate()} or * {@link #generateTo(OutputStream)} to finally generate the content. */ public class Generator { private String templateName; private String templateCode; private String handlerType; private Context context = Context.create(); private String encoding; /** * Applies the context to the generator. *

* This will join the given context with the one previously set (Or the system context). All values with * the same name will be overwritten using the values in the given context. * * @param ctx the context to be applied to the one already present * @return the generator itself for fluent API calls */ public Generator applyContext(Context ctx) { context.putAll(ctx); return this; } /** * Adds a variable with the given name (key) and value to the internal context. *

* If a value with the same key was already defined, it will be overwritten. * * @param key the name of the variable to set * @param value the value of the variable to set * @return the generator itself for fluent API calls */ public Generator put(String key, Object value) { context.put(key, value); return this; } /** * Sets the output encoding which is used to generate the output files. * * @param encoding the encoding to use for output files * @return the generator itself for fluent API calls */ public Generator encoding(String encoding) { this.encoding = encoding; return this; } /** * Determines which template file should be used. *

* The content is resolved by calling {@link Resources#resolve(String)}. * * @param templateName the name of the template to use * @return the generator itself for fluent API calls */ public Generator useTemplate(String templateName) { this.templateName = templateName; return this; } /** * Sets the template code to be used directly as string. *

* Most probably this will be velocity code. Once a direct code is set, the template specified by * {@link #useTemplate(String)} will be ignored. * * @param templateCode the template code to evaluate * @param handlerType String reference for the handler to be used * (i.e. {@link sirius.web.templates.velocity.VelocityContentHandler#VM}) * @return the generator itself for fluent API calls */ public Generator direct(String templateCode, String handlerType) { this.templateCode = templateCode; handler(handlerType); return this; } /** * Specifies which {@link ContentHandler} is used to generate the content. *

* Most of the time, the content handler is auto-detected using the file name of the template. An example * would be .pdf.vm which will force the {@link sirius.web.templates.velocity.VelocityPDFContentHandler} * to generate a PDF file using the template. However, by using {@code generator.handler("pdf-vm")} * it can be ensured, that this handler is picked, without relying on the file name. * * @param handlerType the name of the handler type to use. Constants can be found by looking at the * {@link Register} annotations of the implementing classes of {@link ContentHandler}. * @return the generator itself for fluent API calls */ public Generator handler(String handlerType) { this.handlerType = handlerType; return this; } /** * Calls the appropriate {@link ContentHandler} to generate the output which is written into the given * output stream. * * @param out the output stream to which the generated content is written */ public void generateTo(OutputStream out) { if (Strings.isFilled(templateName)) { CallContext.getCurrent().addToMDC("content-generator-template", templateName); } try { try { if (Strings.isFilled(handlerType)) { generateContentUsingHandler(out); } else { findAndInvokeContentHandler(out); } } catch (HandledException e) { throw e; } catch (Throwable e) { throw Exceptions.handle() .error(e) .to(LOG) .withSystemErrorMessage("Error applying template '%s': %s (%s)", Strings.isEmpty(templateName) ? templateCode : templateName) .handle(); } } finally { CallContext.getCurrent().removeFromMDC("content-generator-template"); } } private void findAndInvokeContentHandler(OutputStream out) throws Exception { for (ContentHandler handler : handlers) { if (handler.generate(this, out)) { return; } } throw Exceptions.handle() .to(LOG) .withSystemErrorMessage("No handler was able to render the given template: %s", Strings.isEmpty(templateName) ? templateCode : templateName) .handle(); } private void generateContentUsingHandler(OutputStream out) throws Exception { ContentHandler handler = ctx.findPart(handlerType, ContentHandler.class); if (!handler.generate(this, out)) { throw Exceptions.handle() .to(LOG) .withSystemErrorMessage("Error using '%s' to generate template '%s'.", handlerType, Strings.isEmpty(templateName) ? templateCode : templateName) .handle(); } } /** * Invokes the appropriate {@link ContentHandler} and returns the generated content handler as string. *

* Most probably the input will be a velocity template which generates readable text (which also might be * XML or HTML). * * @return the generated string contents */ public String generate() { ByteArrayOutputStream out = new ByteArrayOutputStream(); generateTo(out); return new String(out.toByteArray(), Charsets.UTF_8); } /** * Can be used by a {@link ContentHandler} to obtain a preset templateName. * * @return the templateName which was previously set or null if no template name was given */ public String getTemplateName() { return templateName; } /** * Can be used by a {@link ContentHandler} to obtain a preset templateCode. * * @return the templateCode which was previously set or null if no template code was given */ public String getTemplateCode() { return templateCode; } /** * Can be used by a {@link ContentHandler} to access the context which contains all previously set variables. * * @return the previously set context containing all applied variables. */ public Context getContext() { return context; } /** * Can be used by a {@link ContentHandler} to determine the file ending of the selected template. This is * used to select which content handler is actually used to generate the output. * * @param extension the expected file extension, without a "." at the beginning * @return true if the given template ends with the given extension, false otherwise. This * first dot is considered the start of the file extension so "foobar.test.js" has "test.js" as extension. * If the templateName is null, this method always returns false. */ public boolean isTemplateFileExtension(@Nonnull String extension) { return Strings.isFilled(templateName) && extension.equalsIgnoreCase(Strings.split(templateName, ".") .getSecond()); } /** * Can be used by a {@link ContentHandler} to determine the effective ending of the underlying template name. * * @param extension the expected end of the file name. * @return true if the given template ends with the given extension, false otherwise. * In contrast to {@link #isTemplateFileExtension(String)} this will not consider the first "." to be the * file extension but rather really check if the template name ends with the given extension. Therefore * for a template named test.js.vm this will return true for * {@code isTemplateEndsWith(".vm")} but false for {@code isTemplateFileExtension("vm")} */ public boolean isTemplateEndsWith(@Nonnull String extension) { return Strings.isFilled(templateName) && extension.toLowerCase().endsWith(extension); } /** * Can be used by a {@link ContentHandler} to determine the effective encoding used for the generated output. * This is either set via {@link #encoding(String)} or by placing a variable named {@link #ENCODING} in the * context or it is the default encoding used by the JVM (most probably UTF-8). * * @return the effective encoding used to generate the output */ public String getEncoding() { if (Strings.isFilled(encoding)) { return encoding; } if (context.containsKey(ENCODING)) { return (String) context.get(ENCODING); } return Charsets.UTF_8.name(); } /** * Contains the handler type. This can be used by a {@link ContentHandler} to skip all filename checks and * always generate its output. * * @return the handlerType previously set using {@link #handler(String)} or null if no handler type * was set. */ public String getHandlerType() { return handlerType; } /** * Uses the {@link Resolver} implementations or the classloader to load the template as input stream. * * @return the contents of the template as stream or null if the template cannot be resolved */ public InputStream getTemplate() { try { if (templateName == null) { throw Exceptions.handle() .to(LOG) .withSystemErrorMessage("No template was given to evaluate.") .handle(); } URL url = resources.resolve(templateName).map(r -> r.getUrl()).orElse(null); if (url == null) { throw Exceptions.handle() .to(LOG) .withSystemErrorMessage("Unable to resolve '%s'", templateName) .handle(); } return url.openStream(); } catch (IOException e) { throw Exceptions.handle(LOG, e); } } } /** * Creates a new generator which can be used to generate a template based output. * * @return a new {@link Generator} which can be used to generate output */ public Generator generator() { Generator result = new Generator(); for (ContentContextExtender extender : extenders) { extender.extend(result.getContext()); } return result; } /** * Returns a list of all extensions provided for the given key. *

* This can be used to provide templates that contain sections which can be extended by other * components. Think of a generic template containing a menu. Items can be added to this menu * using this mechanism. *

* Internally the {@link Extensions} framework is used. Therefore all extensions * for the key X have to be defined in content.extensions.X like this: *

     *   content.extensions {
     *       X {
     *           extension-a {
     *               priority = 110
     *               template = "a.html"
     *           }
     *           extension-b {
     *               priority = 120
     *               template = "b.html"
     *           }
     *       }
     *   }
     * 
*

* To utilize these extensions in Rythm, use the includeExtensions("name") tag. For Velocity a macro with * the same name is provided. * * @param key the name of the list of content extensions to retrieve * @return a list of templates registered for the given extension using the system config and the Extensions * framework * @see Extensions */ public List getExtensions(String key) { List result = Lists.newArrayList(); for (Extension e : Extensions.getExtensions("content.extensions." + key)) { if (Sirius.isFrameworkEnabled(e.get("framework").asString())) { result.add(e.get("template").asString()); } } return result; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy