Java tutorial
package com.github.jknack.handlebars.springmvc; /** * Copyright (c) 2012-2015 Edgar Espina * * This file is part of Handlebars.java. * * 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. */ import static org.apache.commons.lang3.Validate.notEmpty; import static org.apache.commons.lang3.Validate.notNull; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.net.URI; import java.util.*; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationContext; import org.springframework.context.MessageSource; import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.view.AbstractTemplateViewResolver; import org.springframework.web.servlet.view.AbstractUrlBasedView; import com.github.jknack.handlebars.*; import com.github.jknack.handlebars.Formatter; import com.github.jknack.handlebars.cache.HighConcurrencyTemplateCache; import com.github.jknack.handlebars.cache.NullTemplateCache; import com.github.jknack.handlebars.cache.TemplateCache; import com.github.jknack.handlebars.helper.DefaultHelperRegistry; import com.github.jknack.handlebars.helper.I18nHelper; import com.github.jknack.handlebars.helper.I18nSource; import com.github.jknack.handlebars.io.TemplateLoader; import com.github.jknack.handlebars.io.URLTemplateLoader; /** * A Handlebars {@link ViewResolver view resolver}. * * @author edgar.espina * @since 0.1 */ public class Handlebars4ViewResolver extends AbstractTemplateViewResolver implements InitializingBean, HelperRegistry { /** * The default content type. */ public static final String DEFAULT_CONTENT_TYPE = "text/html;charset=UTF-8"; /** * The handlebars object. */ private Handlebars handlebars; /** * The value's resolvers. */ private ValueResolver[] valueResolvers = ValueResolver.VALUE_RESOLVERS; /** * Fail on missing file. Default is: true. */ private boolean failOnMissingFile = true; /** * The helper registry. */ private HelperRegistry registry = new DefaultHelperRegistry(); /** True, if the message helper (based on {@link MessageSource}) should be registered. */ private boolean registerMessageHelper = true; /** * If true, the i18n helpers will use a {@link MessageSource} instead of a plain * {@link ResourceBundle} . */ private boolean bindI18nToMessageSource; /** * If true, templates will be deleted once applied. Useful, in some advanced template inheritance * use cases. Used by <code>{{#block}} helper</code>. Default is: false. * At any time you can override the default setup with: * * <pre> * {{#block "footer" delete-after-merge=true}} * </pre> * */ private boolean deletePartialAfterMerge; /** * Set variable formatters. */ private Formatter[] formatters; /** Location of the handlebars.js file. */ private String handlebarsJsFile; /** Template cache. */ private TemplateCache templateCache = new HighConcurrencyTemplateCache(); /** * Creates a new {@link Handlebars4ViewResolver}. * * @param viewClass The view's class. Required. */ public Handlebars4ViewResolver(final Class<? extends HandlebarsView> viewClass) { setViewClass(viewClass); setContentType(DEFAULT_CONTENT_TYPE); setPrefix(TemplateLoader.DEFAULT_PREFIX); setSuffix(TemplateLoader.DEFAULT_SUFFIX); } /** * Creates a new {@link Handlebars4ViewResolver}. */ public Handlebars4ViewResolver() { this(HandlebarsView.class); } /** * Creates a new {@link Handlebars4ViewResolver} that utilizes the parameter handlebars for the * underlying template lifecycle management. * * @param handlebars The {@link Handlebars} instance used for template lifecycle management. * Required. */ public Handlebars4ViewResolver(final Handlebars handlebars) { this(handlebars, HandlebarsView.class); } /** * Creates a new {@link Handlebars4ViewResolver} that utilizes the parameter handlebars for the * underlying template lifecycle management. * * @param handlebars The {@link Handlebars} instance used for template lifecycle management. * Required. * @param viewClass The view's class. Required. */ public Handlebars4ViewResolver(final Handlebars handlebars, final Class<? extends HandlebarsView> viewClass) { this(viewClass); this.handlebars = handlebars; } /** * {@inheritDoc} */ @Override protected AbstractUrlBasedView buildView(final String viewName) throws Exception { return configure((HandlebarsView) super.buildView(viewName)); } /** * Configure the handlebars view. * * @param view The handlebars view. * @return The configured view. * @throws IOException If a resource cannot be loaded. */ protected AbstractUrlBasedView configure(final HandlebarsView view) throws IOException { String url = view.getUrl(); // Remove prefix & suffix. url = url.substring(getPrefix().length(), url.length() - getSuffix().length()); // Compile the template. try { view.setTemplate(handlebars.compile(url)); view.setValueResolver(valueResolvers); } catch (IOException ex) { if (failOnMissingFile) { throw ex; } logger.debug("File not found: " + url); } return view; } /** * The required view class. * * @return The required view class. */ @Override protected Class<?> requiredViewClass() { return HandlebarsView.class; } @Override public void afterPropertiesSet() { // If no handlebars object was passed in as a constructor parameter if (handlebars == null) { // Creates a new template loader. TemplateLoader templateLoader = createTemplateLoader(getApplicationContext()); // Creates a new handlebars object. handlebars = notNull(createHandlebars(templateLoader), "A handlebars object is required."); } handlebars.with(registry); if (handlebarsJsFile != null) { handlebars.handlebarsJsFile(handlebarsJsFile); } if (formatters != null) { for (Formatter formatter : formatters) { handlebars.with(formatter); } } if (registerMessageHelper) { // Add a message source helper handlebars.registerHelper("message", new MessageSourceHelper(getApplicationContext())); } if (bindI18nToMessageSource) { I18nSource i18nSource = createI18nSource(getApplicationContext()); I18nHelper.i18n.setSource(i18nSource); I18nHelper.i18nJs.setSource(i18nSource); } TemplateCache cache = handlebars.getCache(); if (cache == NullTemplateCache.INSTANCE) { handlebars.with(templateCache); } // set delete partial after merge handlebars.setDeletePartialAfterMerge(deletePartialAfterMerge); } /** * Creates a new i18n source. * * @param context The application context. * @return A new i18n source. */ private static I18nSource createI18nSource(final ApplicationContext context) { return new I18nSource() { @Override public String message(final String key, final Locale locale, final Object... args) { return context.getMessage(key, args, locale); } @Override public String[] keys(final String basename, final Locale locale) { ResourceBundle bundle = ResourceBundle.getBundle(basename, locale); Enumeration<String> keys = bundle.getKeys(); List<String> result = new ArrayList<String>(); while (keys.hasMoreElements()) { String key = keys.nextElement(); result.add(key); } return result.toArray(new String[result.size()]); } }; } /** * Creates a new {@link Handlebars} object using the parameter {@link TemplateLoader}. * * @param templateLoader A template loader. * @return A new handlebar's object. */ protected Handlebars createHandlebars(final TemplateLoader templateLoader) { return new Handlebars(templateLoader); } /** * Creates a new template loader. * * @param context The application's context. * @return A new template loader. */ protected TemplateLoader createTemplateLoader(final ApplicationContext context) { URLTemplateLoader templateLoader = new SpringTemplateLoader(context); // Override prefix and suffix. templateLoader.setPrefix(getPrefix()); templateLoader.setSuffix(getSuffix()); return templateLoader; } /** * A handlebars instance. * * @return A handlebars instance. */ public Handlebars getHandlebars() { if (handlebars == null) { throw new IllegalStateException("afterPropertiesSet() method hasn't been call it."); } return handlebars; } /** * Set the value resolvers. * * @param valueResolvers The value resolvers. Required. */ public void setValueResolvers(final ValueResolver... valueResolvers) { this.valueResolvers = notEmpty(valueResolvers, "At least one value-resolver must be present."); } /** * Set variable formatters. * * @param formatters Formatters to add. */ public void setFormatters(final Formatter... formatters) { this.formatters = notEmpty(formatters, "At least one formatter must be present."); } /** * Set the handlebars.js location used it to compile/precompile template to JavaScript. * <p> * Using handlebars.js 2.x: * </p> * * <pre> * Handlebars handlebars = new Handlebars() * .handlebarsJsFile("handlebars-v2.0.0.js"); * </pre> * <p> * Using handlebars.js 1.x: * </p> * * <pre> * Handlebars handlebars = new Handlebars() * .handlebarsJsFile("handlebars-v4.0.4.js"); * </pre> * * Default handlebars.js is <code>handlebars-v4.0.4.js</code>. * * @param location A classpath location of the handlebar.js file. */ public void setHandlebarsJsFile(final String location) { this.handlebarsJsFile = notEmpty(location, "Location is required"); } /** * True, if the view resolver should fail on missing files. Default is: true. * * @param failOnMissingFile True, if the view resolver should fail on * missing files. Default is: true. */ public void setFailOnMissingFile(final boolean failOnMissingFile) { this.failOnMissingFile = failOnMissingFile; } /** * Register all the helpers in the map. * * @param helpers The helpers to be registered. Required. * @see Handlebars#registerHelper(String, Helper) */ public void setHelpers(final Map<String, Helper<?>> helpers) { notNull(helpers, "The helpers are required."); for (Map.Entry<String, Helper<?>> helper : helpers.entrySet()) { registry.registerHelper(helper.getKey(), helper.getValue()); } } /** * <p> * Register all the helper methods for the given helper source. * </p> * <p> * A helper method looks like: * </p> * * <pre> * public static? CharSequence methodName(context?, parameter*, options?) { * } * </pre> * * Where: * <ul> * <li>A method can/can't be static</li> * <li>The method's name became the helper's name</li> * <li>Context, parameters and options are all optionals</li> * <li>If context and options are present they must be the first and last arguments of the * method</li> * </ul> * * Instance and static methods will be registered as helpers. * * @param helperSource The helper source. Required. * @return This handlebars object. */ @Override public Handlebars4ViewResolver registerHelpers(final Object helperSource) { registry.registerHelpers(helperSource); return this; } /** * <p> * Register all the helper methods for the given helper source. * </p> * <p> * A helper method looks like: * </p> * * <pre> * public static? CharSequence methodName(context?, parameter*, options?) { * } * </pre> * * Where: * <ul> * <li>A method can/can't be static</li> * <li>The method's name became the helper's name</li> * <li>Context, parameters and options are all optionals</li> * <li>If context and options are present they must be the first and last arguments of the * method</li> * </ul> * * Only static methods will be registered as helpers. * * @param helperSource The helper source. Required. * @return This handlebars object. */ @Override public Handlebars4ViewResolver registerHelpers(final Class<?> helperSource) { registry.registerHelpers(helperSource); return this; } @Override public <C> Helper<C> helper(final String name) { return registry.helper(name); } @Override public Set<Map.Entry<String, Helper<?>>> helpers() { return registry.helpers(); } @Override public <H> Handlebars4ViewResolver registerHelper(final String name, final Helper<H> helper) { registry.registerHelper(name, helper); return this; } @Override public <H> Handlebars4ViewResolver registerHelperMissing(final Helper<H> helper) { registry.registerHelperMissing(helper); return this; } @Override public Handlebars4ViewResolver registerHelpers(final URI location) throws Exception { registry.registerHelpers(location); return this; } @Override public Handlebars4ViewResolver registerHelpers(final File input) throws Exception { registry.registerHelpers(input); return this; } @Override public Handlebars4ViewResolver registerHelpers(final String filename, final Reader source) throws Exception { registry.registerHelpers(filename, source); return this; } @Override public Handlebars4ViewResolver registerHelpers(final String filename, final InputStream source) throws Exception { registry.registerHelpers(filename, source); return this; } @Override public Handlebars4ViewResolver registerHelpers(final String filename, final String source) throws Exception { registry.registerHelpers(filename, source); return this; } /** * Same as {@link #setRegisterMessageHelper(boolean)} with a false argument. The message helper * wont be registered when you call this method. * * @return This handlebars view resolver. */ public Handlebars4ViewResolver withoutMessageHelper() { setRegisterMessageHelper(false); return this; } /** * True, if the message helper (based on {@link MessageSource}) should be registered. Default is: * true. * * @param registerMessageHelper True, if the message helper (based on {@link MessageSource}) * should be registered. Default is: true. */ public void setRegisterMessageHelper(final boolean registerMessageHelper) { this.registerMessageHelper = registerMessageHelper; } /** * @param bindI18nToMessageSource If true, the i18n helpers will use a {@link MessageSource} * instead of a plain {@link ResourceBundle}. Default is: false. */ public void setBindI18nToMessageSource(final boolean bindI18nToMessageSource) { this.bindI18nToMessageSource = bindI18nToMessageSource; } /** * If true, templates will be deleted once applied. Useful, in some advanced template inheritance * use cases. Used by <code>{{#block}} helper</code>. Default is: false. * At any time you can override the default setup with: * * <pre> * {{#block "footer" delete-after-merge=true}} * </pre> * * @param deletePartialAfterMerge True for clearing up templates once they got applied. Used by * <code>{{#block}} helper</code>. */ public void setDeletePartialAfterMerge(final boolean deletePartialAfterMerge) { this.deletePartialAfterMerge = deletePartialAfterMerge; } @Override public void setCache(final boolean cache) { if (!cache) { templateCache = NullTemplateCache.INSTANCE; } super.setCache(cache); } /** * @param templateCache Set a template cache. Default is: {@link HighConcurrencyTemplateCache}. */ public void setTemplateCache(final TemplateCache templateCache) { this.templateCache = templateCache; } @Override public Decorator decorator(final String name) { return this.registry.decorator(name); } @Override public Handlebars4ViewResolver registerDecorator(final String name, final Decorator decorator) { registry.registerDecorator(name, decorator); return this; } }