diff --git a/core/src/main/java/org/apache/struts2/StrutsConstants.java b/core/src/main/java/org/apache/struts2/StrutsConstants.java index 53e2ee867..732b96046 100644 --- a/core/src/main/java/org/apache/struts2/StrutsConstants.java +++ b/core/src/main/java/org/apache/struts2/StrutsConstants.java @@ -129,6 +129,9 @@ public final class StrutsConstants { /** The default UI template theme */ public static final String STRUTS_UI_THEME = "struts.ui.theme"; + /** Token to use to indicate start of theme to be expanded. */ + public static final String STRUTS_UI_THEME_EXPANSION_TOKEN = "struts.ui.theme.expansion.token"; + /** The maximize size of a multipart request (file upload) */ public static final String STRUTS_MULTIPART_MAXSIZE = "struts.multipart.maxSize"; diff --git a/core/src/main/java/org/apache/struts2/components/UIBean.java b/core/src/main/java/org/apache/struts2/components/UIBean.java index 6d9b826e9..9418a6e1d 100644 --- a/core/src/main/java/org/apache/struts2/components/UIBean.java +++ b/core/src/main/java/org/apache/struts2/components/UIBean.java @@ -84,6 +84,21 @@ import java.util.concurrent.ConcurrentMap; * String * define the template name * + * + * themeExpansionToken + * n/a + * String + * special token (defined with struts.ui.theme.expansion.token) used to search for template in parent theme + * (don't use it separately!) + * + * + * expandTheme + * n/a + * String + * concatenation of themeExpansionToken and theme which tells internal template loader mechanism + * to try load template from current theme and then from parent theme (and parent theme, and so on) + * when used with <#include/> directive + * * * * @@ -499,6 +514,7 @@ public abstract class UIBean extends Component { protected String defaultTemplateDir; protected String defaultUITheme; + protected String uiThemeExpansionToken; protected TemplateEngineManager templateEngineManager; // dynamic attributes support for tags used with FreeMarker templates @@ -514,6 +530,11 @@ public abstract class UIBean extends Component { this.defaultUITheme = theme; } + @Inject(StrutsConstants.STRUTS_UI_THEME_EXPANSION_TOKEN) + public void setUIThemeExpansionToken(String uiThemeExpansionToken) { + this.uiThemeExpansionToken = uiThemeExpansionToken; + } + @Inject public void setTemplateEngineManager(TemplateEngineManager mgr) { this.templateEngineManager = mgr; @@ -627,9 +648,15 @@ public abstract class UIBean extends Component { } public void evaluateParams() { - addParameter("templateDir", getTemplateDir()); - addParameter("theme", getTheme()); + String templateDir = getTemplateDir(); + String theme = getTheme(); + + addParameter("templateDir", templateDir); + addParameter("theme", theme); + addParameter("template", template != null ? findString(template) : getDefaultTemplate()); addParameter("dynamicAttributes", dynamicAttributes); + addParameter("themeExpansionToken", uiThemeExpansionToken); + addParameter("expandTheme", uiThemeExpansionToken + theme); String name = null; String providedLabel = null; diff --git a/core/src/main/java/org/apache/struts2/components/template/FreemarkerTemplateEngine.java b/core/src/main/java/org/apache/struts2/components/template/FreemarkerTemplateEngine.java index 2e3b84c4c..9368ec716 100644 --- a/core/src/main/java/org/apache/struts2/components/template/FreemarkerTemplateEngine.java +++ b/core/src/main/java/org/apache/struts2/components/template/FreemarkerTemplateEngine.java @@ -23,6 +23,7 @@ package org.apache.struts2.components.template; import java.io.IOException; import java.io.Writer; +import java.util.Locale; import java.util.Map; import java.util.List; diff --git a/core/src/main/java/org/apache/struts2/views/freemarker/FreemarkerManager.java b/core/src/main/java/org/apache/struts2/views/freemarker/FreemarkerManager.java index bc233dddb..4a122ec14 100644 --- a/core/src/main/java/org/apache/struts2/views/freemarker/FreemarkerManager.java +++ b/core/src/main/java/org/apache/struts2/views/freemarker/FreemarkerManager.java @@ -179,6 +179,7 @@ public class FreemarkerManager { protected Map tagLibraries; private FileManager fileManager; + private FreemarkerThemeTemplateLoader themeTemplateLoader; @Inject(StrutsConstants.STRUTS_I18N_ENCODING) public void setEncoding(String encoding) { @@ -220,6 +221,11 @@ public class FreemarkerManager { this.fileManager = fileManagerFactory.getFileManager(); } + @Inject + public void setThemeTemplateLoader(FreemarkerThemeTemplateLoader themeTemplateLoader) { + this.themeTemplateLoader = themeTemplateLoader; + } + public boolean getNoCharsetInContentType() { return noCharsetInContentType; } @@ -257,15 +263,9 @@ public class FreemarkerManager { LOG.error("Cannot load freemarker configuration: ",e); } } -// config = createConfiguration(servletContext); - // store this configuration in the servlet context servletContext.setAttribute(CONFIG_SERVLET_CONTEXT_KEY, config); - - config.setWhitespaceStripping(true); } - - return config; } @@ -289,11 +289,22 @@ public class FreemarkerManager { templatePath = servletContext.getInitParameter("templatePath"); } - config.setTemplateLoader(createTemplateLoader(servletContext, templatePath)); + configureTemplateLoader(createTemplateLoader(servletContext, templatePath)); loadSettings(servletContext); } + /** + * Sets the Freemarker Configuration's template loader with the FreemarkerThemeTemplateLoader + * at the top. + * + * @see org.apache.struts2.views.freemarker.FreemarkerThemeTemplateLoader + */ + protected void configureTemplateLoader(TemplateLoader templateLoader) { + themeTemplateLoader.init(templateLoader); + config.setTemplateLoader(themeTemplateLoader); + } + /** * Create the instance of the freemarker Configuration object. *

@@ -321,8 +332,7 @@ public class FreemarkerManager { if (encoding != null) { configuration.setDefaultEncoding(encoding); } - - + configuration.setLocalizedLookup(false); configuration.setWhitespaceStripping(true); return configuration; diff --git a/core/src/main/java/org/apache/struts2/views/freemarker/FreemarkerThemeTemplateLoader.java b/core/src/main/java/org/apache/struts2/views/freemarker/FreemarkerThemeTemplateLoader.java new file mode 100644 index 000000000..35a90a45d --- /dev/null +++ b/core/src/main/java/org/apache/struts2/views/freemarker/FreemarkerThemeTemplateLoader.java @@ -0,0 +1,95 @@ +package org.apache.struts2.views.freemarker; + +import java.io.IOException; +import java.io.Reader; +import java.util.List; + +import org.apache.struts2.StrutsConstants; +import org.apache.struts2.components.template.Template; +import org.apache.struts2.components.template.TemplateEngine; + +import com.opensymphony.xwork2.inject.Inject; + +import freemarker.cache.TemplateLoader; + +/** + * When loading a template, if sees theme token in path, does a template search through + * theme hierarchy for template, starting at the theme name after the token. + */ +public class FreemarkerThemeTemplateLoader implements TemplateLoader{ + private TemplateLoader parentTemplateLoader; + + // Injected + private String themeExpansionToken; + private TemplateEngine templateEngine; + + /** + * Initialize the loader for the given parent. + */ + public void init(TemplateLoader parent) { + this.parentTemplateLoader = parent; + } + + /** {@inheritDoc} */ + public Object findTemplateSource(String name) throws IOException { + int tokenIndex = (name == null) ? -1 : name.indexOf(themeExpansionToken); + if (tokenIndex < 0) { + return parentTemplateLoader.findTemplateSource(name); + } + + int themeEndIndex = name.indexOf('/', tokenIndex); + if (themeEndIndex < 0) { + return parentTemplateLoader.findTemplateSource(name); + } + + Template template = new Template( + name.substring(0, tokenIndex - 1), + name.substring(tokenIndex + themeExpansionToken.length(), themeEndIndex), + name.substring(themeEndIndex + 1)); + + List