Re-implements tiles integration based on new API
Project: http://git-wip-us.apache.org/repos/asf/struts/repo Commit: http://git-wip-us.apache.org/repos/asf/struts/commit/70716e94 Tree: http://git-wip-us.apache.org/repos/asf/struts/tree/70716e94 Diff: http://git-wip-us.apache.org/repos/asf/struts/diff/70716e94 Branch: refs/heads/master Commit: 70716e945295526d207cab6c66306a3113f8a333 Parents: 85b2198 Author: Lukasz Lenart <lukasz.len...@gmail.com> Authored: Wed Nov 25 20:29:18 2015 +0100 Committer: Lukasz Lenart <lukasz.len...@gmail.com> Committed: Wed Nov 25 20:29:18 2015 +0100 ---------------------------------------------------------------------- .../StrutsFreeMarkerAttributeRenderer.java | 72 +++++ .../tiles/StrutsTilesContainerFactory.java | 266 +++++++++++++++---- .../struts2/tiles/StrutsTilesInitializer.java | 47 ++++ .../struts2/tiles/StrutsTilesListener.java | 56 +--- 4 files changed, 350 insertions(+), 91 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/struts/blob/70716e94/plugins/tiles/src/main/java/org/apache/struts2/tiles/StrutsFreeMarkerAttributeRenderer.java ---------------------------------------------------------------------- diff --git a/plugins/tiles/src/main/java/org/apache/struts2/tiles/StrutsFreeMarkerAttributeRenderer.java b/plugins/tiles/src/main/java/org/apache/struts2/tiles/StrutsFreeMarkerAttributeRenderer.java new file mode 100644 index 0000000..e2669c7 --- /dev/null +++ b/plugins/tiles/src/main/java/org/apache/struts2/tiles/StrutsFreeMarkerAttributeRenderer.java @@ -0,0 +1,72 @@ +package org.apache.struts2.tiles; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.config.ConfigurationException; +import com.opensymphony.xwork2.inject.Container; +import freemarker.template.TemplateException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.struts2.ServletActionContext; +import org.apache.struts2.views.freemarker.FreemarkerResult; +import org.apache.tiles.Attribute; +import org.apache.tiles.context.TilesRequestContext; +import org.apache.tiles.impl.InvalidTemplateException; +import org.apache.tiles.renderer.impl.AbstractTypeDetectingAttributeRenderer; +import org.apache.tiles.servlet.context.ServletTilesRequestContext; +import org.apache.tiles.servlet.context.ServletUtil; + +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; + +public class StrutsFreeMarkerAttributeRenderer extends AbstractTypeDetectingAttributeRenderer { + + private static Logger LOG = LogManager.getLogger(StrutsFreeMarkerAttributeRenderer.class); + + @Override + public void write(Object value, Attribute attribute, TilesRequestContext request) throws IOException { + if (value != null) { + if (value instanceof String) { + LOG.trace("Rendering freemarker tile ..."); + + ServletTilesRequestContext servletRequest = ServletUtil.getServletRequest(request); + HttpServletRequest httpRequest = servletRequest.getRequest(); + + ActionContext ctx = ServletActionContext.getActionContext(httpRequest); + if (ctx == null) { + throw new ConfigurationException("There is no ActionContext for current request!"); + } + ActionInvocation invocation = ctx.getActionInvocation(); + + String include = (String) value; + FreemarkerResult result = new FreemarkerResult(include); + result.setWriter(request.getWriter()); + + Container container = ctx.getContainer(); + container.inject(result); + + try { + result.doExecute(include, invocation); + } catch (TemplateException e) { + LOG.error("Exception was thrown during rendering value {}: {}", value, e.getMessage()); + throw new InvalidTemplateException(e); + } + } else { + LOG.error("Value {} is not a String, cannot render template!", value); + throw new InvalidTemplateException("Cannot render a template that is not a string: " + String.valueOf(value)); + } + } else { + LOG.error("Value is null, cannot render template!"); + throw new InvalidTemplateException("Cannot render a null template"); + } + } + + public boolean isRenderable(Object value, Attribute attribute, TilesRequestContext request) { + if (value instanceof String) { + String string = (String) value; + return string.startsWith("/") && string.endsWith(".ftl"); + } + return false; + } + +} http://git-wip-us.apache.org/repos/asf/struts/blob/70716e94/plugins/tiles/src/main/java/org/apache/struts2/tiles/StrutsTilesContainerFactory.java ---------------------------------------------------------------------- diff --git a/plugins/tiles/src/main/java/org/apache/struts2/tiles/StrutsTilesContainerFactory.java b/plugins/tiles/src/main/java/org/apache/struts2/tiles/StrutsTilesContainerFactory.java index 491ee02..4c5871d 100644 --- a/plugins/tiles/src/main/java/org/apache/struts2/tiles/StrutsTilesContainerFactory.java +++ b/plugins/tiles/src/main/java/org/apache/struts2/tiles/StrutsTilesContainerFactory.java @@ -21,73 +21,249 @@ package org.apache.struts2.tiles; +import ognl.OgnlException; +import ognl.OgnlRuntime; +import ognl.PropertyAccessor; import org.apache.tiles.TilesApplicationContext; -import org.apache.tiles.TilesException; -import org.apache.tiles.context.TilesContextFactory; +import org.apache.tiles.TilesContainer; +import org.apache.tiles.context.ChainedTilesRequestContextFactory; import org.apache.tiles.context.TilesRequestContext; -import org.apache.tiles.definition.DefinitionsFactory; -import org.apache.tiles.factory.TilesContainerFactory; +import org.apache.tiles.context.TilesRequestContextFactory; +import org.apache.tiles.definition.DefinitionsFactoryException; +import org.apache.tiles.definition.pattern.DefinitionPatternMatcherFactory; +import org.apache.tiles.definition.pattern.PatternDefinitionResolver; +import org.apache.tiles.definition.pattern.PrefixedPatternDefinitionResolver; +import org.apache.tiles.definition.pattern.regexp.RegexpDefinitionPatternMatcherFactory; +import org.apache.tiles.definition.pattern.wildcard.WildcardDefinitionPatternMatcherFactory; +import org.apache.tiles.el.ELAttributeEvaluator; +import org.apache.tiles.el.JspExpressionFactoryFactory; +import org.apache.tiles.el.TilesContextBeanELResolver; +import org.apache.tiles.el.TilesContextELResolver; +import org.apache.tiles.evaluator.AttributeEvaluatorFactory; +import org.apache.tiles.evaluator.BasicAttributeEvaluatorFactory; +import org.apache.tiles.evaluator.impl.DirectAttributeEvaluator; +import org.apache.tiles.factory.BasicTilesContainerFactory; +import org.apache.tiles.factory.TilesContainerFactoryException; +import org.apache.tiles.freemarker.context.FreeMarkerTilesRequestContextFactory; import org.apache.tiles.impl.BasicTilesContainer; -import org.apache.tiles.preparer.PreparerFactory; +import org.apache.tiles.impl.mgmt.CachingTilesContainer; +import org.apache.tiles.locale.LocaleResolver; +import org.apache.tiles.ognl.ApplicationScopeNestedObjectExtractor; +import org.apache.tiles.ognl.DelegatePropertyAccessor; +import org.apache.tiles.ognl.NestedObjectDelegatePropertyAccessor; +import org.apache.tiles.ognl.OGNLAttributeEvaluator; +import org.apache.tiles.ognl.PropertyAccessorDelegateFactory; +import org.apache.tiles.ognl.RequestScopeNestedObjectExtractor; +import org.apache.tiles.ognl.SessionScopeNestedObjectExtractor; +import org.apache.tiles.ognl.TilesApplicationContextNestedObjectExtractor; +import org.apache.tiles.ognl.TilesContextPropertyAccessorDelegateFactory; +import org.apache.tiles.renderer.AttributeRenderer; +import org.apache.tiles.renderer.TypeDetectingAttributeRenderer; +import org.apache.tiles.renderer.impl.BasicRendererFactory; +import org.apache.tiles.renderer.impl.ChainedDelegateAttributeRenderer; +import org.apache.tiles.util.URLUtil; +import javax.el.ArrayELResolver; +import javax.el.BeanELResolver; +import javax.el.CompositeELResolver; +import javax.el.ELResolver; +import javax.el.ListELResolver; +import javax.el.MapELResolver; +import javax.el.ResourceBundleELResolver; +import java.io.IOException; +import java.net.URL; +import java.util.HashSet; +import java.util.List; import java.util.Map; +import java.util.Set; +/** + * Dedicated Struts factory to build Tiles container with support for: + * - Freemarker + * - OGNL (as default) + * - EL + * - Wildcards + * + * If you need additional features create your own listener and factory, + * you can base on code from Tiles' CompleteAutoloadTilesContainerFactory + */ +public class StrutsTilesContainerFactory extends BasicTilesContainerFactory { -public class StrutsTilesContainerFactory extends TilesContainerFactory { + /** + * The freemarker renderer name. + */ + public static final String FREEMARKER_RENDERER_NAME = "freemarker"; + /** + * Supported pattern types + */ + public static final String PATTERN_WILDCARD = "WILDCARD"; + public static final String PATTERN_REGEXP = "REGEXP"; @Override - protected void storeContainerDependencies(Object context, Map<String, String> initParameters, Map<String, String> configuration, BasicTilesContainer container) throws TilesException { - TilesContextFactory contextFactory = - (TilesContextFactory) createFactory(configuration, - CONTEXT_FACTORY_INIT_PARAM); + protected BasicTilesContainer instantiateContainer(TilesApplicationContext applicationContext) { + return new CachingTilesContainer(); + } - contextFactory = new StrutsTilesContextFactory(contextFactory); + @Override + protected List<TilesRequestContextFactory> getTilesRequestContextFactoriesToBeChained(ChainedTilesRequestContextFactory parent) { - DefinitionsFactory defsFactory = - (DefinitionsFactory) createFactory(configuration, - DEFINITIONS_FACTORY_INIT_PARAM); + List<TilesRequestContextFactory> factories = super.getTilesRequestContextFactoriesToBeChained(parent); - PreparerFactory prepFactory = - (PreparerFactory) createFactory(configuration, - PREPARER_FACTORY_INIT_PARAM); + registerRequestContextFactory(FreeMarkerTilesRequestContextFactory.class.getName(), factories, parent); - contextFactory.init(configuration); - TilesApplicationContext tilesContext = - contextFactory.createApplicationContext(context); + return factories; + } + + @Override + protected void registerAttributeRenderers( + BasicRendererFactory rendererFactory, + TilesApplicationContext applicationContext, + TilesRequestContextFactory contextFactory, + TilesContainer container, + AttributeEvaluatorFactory attributeEvaluatorFactory) { - container.setDefinitionsFactory(defsFactory); - container.setContextFactory(contextFactory); - container.setPreparerFactory(prepFactory); - container.setApplicationContext(tilesContext); + super.registerAttributeRenderers( + rendererFactory, + applicationContext, + contextFactory, + container, + attributeEvaluatorFactory); + + StrutsFreeMarkerAttributeRenderer freemarkerRenderer = new StrutsFreeMarkerAttributeRenderer(); + freemarkerRenderer.setApplicationContext(applicationContext); + freemarkerRenderer.setAttributeEvaluatorFactory(attributeEvaluatorFactory); + freemarkerRenderer.setRequestContextFactory(contextFactory); + + rendererFactory.registerRenderer(FREEMARKER_RENDERER_NAME, freemarkerRenderer); } - /** - * Wrapper factory, used to decorate the TilesRequestContext with a - * FreemarkerResult aware version. - * - */ - class StrutsTilesContextFactory implements TilesContextFactory { + @Override + protected AttributeRenderer createDefaultAttributeRenderer( + BasicRendererFactory rendererFactory, + TilesApplicationContext applicationContext, + TilesRequestContextFactory contextFactory, + TilesContainer container, + AttributeEvaluatorFactory attributeEvaluatorFactory) { - private TilesContextFactory factory; + ChainedDelegateAttributeRenderer retValue = new ChainedDelegateAttributeRenderer(); - public StrutsTilesContextFactory(TilesContextFactory factory) { - this.factory = factory; - } + retValue.addAttributeRenderer((TypeDetectingAttributeRenderer) rendererFactory + .getRenderer(DEFINITION_RENDERER_NAME)); + retValue.addAttributeRenderer((TypeDetectingAttributeRenderer) rendererFactory + .getRenderer(FREEMARKER_RENDERER_NAME)); + retValue.addAttributeRenderer((TypeDetectingAttributeRenderer) rendererFactory + .getRenderer(TEMPLATE_RENDERER_NAME)); + retValue.addAttributeRenderer((TypeDetectingAttributeRenderer) rendererFactory + .getRenderer(STRING_RENDERER_NAME)); - public void init(Map<String, String> map) { - factory.init(map); - } + retValue.setApplicationContext(applicationContext); + retValue.setRequestContextFactory(contextFactory); + retValue.setAttributeEvaluatorFactory(attributeEvaluatorFactory); + + return retValue; + } + + @Override + protected AttributeEvaluatorFactory createAttributeEvaluatorFactory( + TilesApplicationContext applicationContext, + TilesRequestContextFactory contextFactory, + LocaleResolver resolver) { + + BasicAttributeEvaluatorFactory attributeEvaluatorFactory = new BasicAttributeEvaluatorFactory(new DirectAttributeEvaluator()); + attributeEvaluatorFactory.registerAttributeEvaluator("OGNL", createOGNLEvaluator()); + attributeEvaluatorFactory.registerAttributeEvaluator("EL", createELEvaluator(applicationContext)); + + return attributeEvaluatorFactory; + } + + @Override + protected <T> PatternDefinitionResolver<T> createPatternDefinitionResolver(Class<T> customizationKeyClass) { - public TilesApplicationContext createApplicationContext(Object context) { - return factory.createApplicationContext(context); + DefinitionPatternMatcherFactory wildcardFactory = new WildcardDefinitionPatternMatcherFactory(); + DefinitionPatternMatcherFactory regexpFactory = new RegexpDefinitionPatternMatcherFactory(); + + PrefixedPatternDefinitionResolver<T> resolver = new PrefixedPatternDefinitionResolver<>(); + resolver.registerDefinitionPatternMatcherFactory(PATTERN_WILDCARD, wildcardFactory); + resolver.registerDefinitionPatternMatcherFactory(PATTERN_REGEXP, regexpFactory); + + return resolver; + } + + @Override + protected List<URL> getSourceURLs(TilesApplicationContext applicationContext, + TilesRequestContextFactory contextFactory) { + try { + Set<URL> finalSet = new HashSet<>(); + Set<URL> webINFSet = applicationContext.getResources("/WEB-INF/**/tiles*.xml"); + Set<URL> metaINFSet = applicationContext.getResources("classpath*:META-INF/**/tiles*.xml"); + + if (webINFSet != null) { + finalSet.addAll(webINFSet); + } + if (metaINFSet != null) { + finalSet.addAll(metaINFSet); + } + + return URLUtil.getBaseTilesDefinitionURLs(finalSet); + } catch (IOException e) { + throw new DefinitionsFactoryException("Cannot load definition URLs", e); } + } + + protected ELAttributeEvaluator createELEvaluator(TilesApplicationContext applicationContext) { + + ELAttributeEvaluator evaluator = new ELAttributeEvaluator(); + evaluator.setApplicationContext(applicationContext); + JspExpressionFactoryFactory efFactory = new JspExpressionFactoryFactory(); + efFactory.setApplicationContext(applicationContext); + evaluator.setExpressionFactory(efFactory.getExpressionFactory()); - public TilesRequestContext createRequestContext( - TilesApplicationContext tilesApplicationContext, - Object... requestItems) { - TilesRequestContext context = factory.createRequestContext(tilesApplicationContext, requestItems); - return new StrutsTilesRequestContext(context); + ELResolver elResolver = new CompositeELResolver() { + { + add(new TilesContextELResolver()); + add(new TilesContextBeanELResolver()); + add(new ArrayELResolver(false)); + add(new ListELResolver(false)); + add(new MapELResolver(false)); + add(new ResourceBundleELResolver()); + add(new BeanELResolver(false)); + } + }; + + evaluator.setResolver(elResolver); + + return evaluator; + } + + protected OGNLAttributeEvaluator createOGNLEvaluator() { + try { + PropertyAccessor objectPropertyAccessor = OgnlRuntime.getPropertyAccessor(Object.class); + PropertyAccessor mapPropertyAccessor = OgnlRuntime.getPropertyAccessor(Map.class); + PropertyAccessor applicationContextPropertyAccessor = + new NestedObjectDelegatePropertyAccessor<>( + new TilesApplicationContextNestedObjectExtractor(), + objectPropertyAccessor); + PropertyAccessor requestScopePropertyAccessor = + new NestedObjectDelegatePropertyAccessor<>( + new RequestScopeNestedObjectExtractor(), mapPropertyAccessor); + PropertyAccessor sessionScopePropertyAccessor = + new NestedObjectDelegatePropertyAccessor<>( + new SessionScopeNestedObjectExtractor(), mapPropertyAccessor); + PropertyAccessor applicationScopePropertyAccessor = + new NestedObjectDelegatePropertyAccessor<>( + new ApplicationScopeNestedObjectExtractor(), mapPropertyAccessor); + PropertyAccessorDelegateFactory<TilesRequestContext> factory = + new TilesContextPropertyAccessorDelegateFactory( + objectPropertyAccessor, applicationContextPropertyAccessor, + requestScopePropertyAccessor, sessionScopePropertyAccessor, + applicationScopePropertyAccessor); + PropertyAccessor tilesRequestAccessor = new DelegatePropertyAccessor<>(factory); + OgnlRuntime.setPropertyAccessor(TilesRequestContext.class, tilesRequestAccessor); + return new OGNLAttributeEvaluator(); + } catch (OgnlException e) { + throw new TilesContainerFactoryException("Cannot initialize OGNL evaluator", e); } } -} + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/struts/blob/70716e94/plugins/tiles/src/main/java/org/apache/struts2/tiles/StrutsTilesInitializer.java ---------------------------------------------------------------------- diff --git a/plugins/tiles/src/main/java/org/apache/struts2/tiles/StrutsTilesInitializer.java b/plugins/tiles/src/main/java/org/apache/struts2/tiles/StrutsTilesInitializer.java new file mode 100644 index 0000000..b2e2700 --- /dev/null +++ b/plugins/tiles/src/main/java/org/apache/struts2/tiles/StrutsTilesInitializer.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.struts2.tiles; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.tiles.TilesApplicationContext; +import org.apache.tiles.factory.AbstractTilesContainerFactory; +import org.apache.tiles.servlet.wildcard.WildcardServletTilesApplicationContext; +import org.apache.tiles.startup.AbstractTilesInitializer; + +import javax.servlet.ServletContext; + +public class StrutsTilesInitializer extends AbstractTilesInitializer { + + private static final Logger LOG = LogManager.getLogger(StrutsTilesInitializer.class); + + @Override + protected TilesApplicationContext createTilesApplicationContext(TilesApplicationContext preliminaryContext) { + LOG.debug("Initializing Tiles wildcard support ..."); + return new WildcardServletTilesApplicationContext((ServletContext) preliminaryContext.getContext()); + } + + @Override + protected AbstractTilesContainerFactory createContainerFactory(TilesApplicationContext context) { + LOG.trace("Creating dedicated Struts factory to create Tiles container"); + return new StrutsTilesContainerFactory(); + } + +} http://git-wip-us.apache.org/repos/asf/struts/blob/70716e94/plugins/tiles/src/main/java/org/apache/struts2/tiles/StrutsTilesListener.java ---------------------------------------------------------------------- diff --git a/plugins/tiles/src/main/java/org/apache/struts2/tiles/StrutsTilesListener.java b/plugins/tiles/src/main/java/org/apache/struts2/tiles/StrutsTilesListener.java index b04908a..cf874b1 100644 --- a/plugins/tiles/src/main/java/org/apache/struts2/tiles/StrutsTilesListener.java +++ b/plugins/tiles/src/main/java/org/apache/struts2/tiles/StrutsTilesListener.java @@ -1,6 +1,4 @@ /* - * $Id$ - * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information @@ -21,57 +19,23 @@ package org.apache.struts2.tiles; -import java.util.HashMap; -import java.util.Map; - -import javax.servlet.ServletContext; - -import org.apache.tiles.TilesContainer; -import org.apache.tiles.TilesException; -import org.apache.tiles.factory.TilesContainerFactory; -import org.apache.tiles.web.startup.TilesListener; - -import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.tiles.startup.TilesInitializer; +import org.apache.tiles.web.startup.AbstractTilesListener; /** - * Listener used to automatically inject ServletContext - * init parameters so that they don't need to be configured - * explicitly for tiles integration. This is provided - * mainly for backwards compatibility with Struts 2.0.1 - * configuration. + * Listener used to automatically tie Tiles support into Struts * * @since Struts 2.0.2 - * @version $Rev$ - * */ -public class StrutsTilesListener extends TilesListener { +public class StrutsTilesListener extends AbstractTilesListener { private static final Logger LOG = LogManager.getLogger(StrutsTilesListener.class); - private static final Map<String, String> INIT; - - static { - INIT = new HashMap<String, String>(); - INIT.put(TilesContainerFactory.CONTAINER_FACTORY_INIT_PARAM, - StrutsTilesContainerFactory.class.getName()); - } - - protected TilesContainer createContainer(ServletContext context) - throws TilesException { - if(context.getInitParameter(TilesContainerFactory.CONTEXT_FACTORY_INIT_PARAM) == null) { - context = decorate(context); - } - else { - if (LOG.isWarnEnabled()) { - LOG.warn("Tiles container factory is explicitly set. Not injecting struts configuration."); - } - } - return super.createContainer(context); + @Override + protected TilesInitializer createTilesInitializer() { + LOG.info("Starting Struts Tiles 2 integration ..."); + return new StrutsTilesInitializer(); } - - protected ServletContext decorate(ServletContext context) { - return new ConfiguredServletContext(context, INIT); - } - -} +} \ No newline at end of file