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 000000000..e2669c791 --- /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; + } + +} 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 491ee02ab..4c5871db7 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; - -public class StrutsTilesContainerFactory extends TilesContainerFactory { - - - @Override - protected void storeContainerDependencies(Object context, Map initParameters, Map configuration, BasicTilesContainer container) throws TilesException { - TilesContextFactory contextFactory = - (TilesContextFactory) createFactory(configuration, - CONTEXT_FACTORY_INIT_PARAM); - - contextFactory = new StrutsTilesContextFactory(contextFactory); - - DefinitionsFactory defsFactory = - (DefinitionsFactory) createFactory(configuration, - DEFINITIONS_FACTORY_INIT_PARAM); - - PreparerFactory prepFactory = - (PreparerFactory) createFactory(configuration, - PREPARER_FACTORY_INIT_PARAM); - - contextFactory.init(configuration); - TilesApplicationContext tilesContext = - contextFactory.createApplicationContext(context); - - container.setDefinitionsFactory(defsFactory); - container.setContextFactory(contextFactory); - container.setPreparerFactory(prepFactory); - container.setApplicationContext(tilesContext); - } +/** + * 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 { /** - * Wrapper factory, used to decorate the TilesRequestContext with a - * FreemarkerResult aware version. - * + * The freemarker renderer name. */ - class StrutsTilesContextFactory implements TilesContextFactory { + public static final String FREEMARKER_RENDERER_NAME = "freemarker"; - private TilesContextFactory factory; + /** + * Supported pattern types + */ + public static final String PATTERN_WILDCARD = "WILDCARD"; + public static final String PATTERN_REGEXP = "REGEXP"; - public StrutsTilesContextFactory(TilesContextFactory factory) { - this.factory = factory; - } + @Override + protected BasicTilesContainer instantiateContainer(TilesApplicationContext applicationContext) { + return new CachingTilesContainer(); + } - public void init(Map map) { - factory.init(map); - } + @Override + protected List getTilesRequestContextFactoriesToBeChained(ChainedTilesRequestContextFactory parent) { - public TilesApplicationContext createApplicationContext(Object context) { - return factory.createApplicationContext(context); - } + List factories = super.getTilesRequestContextFactoriesToBeChained(parent); - public TilesRequestContext createRequestContext( - TilesApplicationContext tilesApplicationContext, - Object... requestItems) { - TilesRequestContext context = factory.createRequestContext(tilesApplicationContext, requestItems); - return new StrutsTilesRequestContext(context); + registerRequestContextFactory(FreeMarkerTilesRequestContextFactory.class.getName(), factories, parent); + + return factories; + } + + @Override + protected void registerAttributeRenderers( + BasicRendererFactory rendererFactory, + TilesApplicationContext applicationContext, + TilesRequestContextFactory contextFactory, + TilesContainer container, + AttributeEvaluatorFactory attributeEvaluatorFactory) { + + 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); + } + + @Override + protected AttributeRenderer createDefaultAttributeRenderer( + BasicRendererFactory rendererFactory, + TilesApplicationContext applicationContext, + TilesRequestContextFactory contextFactory, + TilesContainer container, + AttributeEvaluatorFactory attributeEvaluatorFactory) { + + ChainedDelegateAttributeRenderer retValue = new ChainedDelegateAttributeRenderer(); + + 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)); + + 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 PatternDefinitionResolver createPatternDefinitionResolver(Class customizationKeyClass) { + + DefinitionPatternMatcherFactory wildcardFactory = new WildcardDefinitionPatternMatcherFactory(); + DefinitionPatternMatcherFactory regexpFactory = new RegexpDefinitionPatternMatcherFactory(); + + PrefixedPatternDefinitionResolver resolver = new PrefixedPatternDefinitionResolver<>(); + resolver.registerDefinitionPatternMatcherFactory(PATTERN_WILDCARD, wildcardFactory); + resolver.registerDefinitionPatternMatcherFactory(PATTERN_REGEXP, regexpFactory); + + return resolver; + } + + @Override + protected List getSourceURLs(TilesApplicationContext applicationContext, + TilesRequestContextFactory contextFactory) { + try { + Set finalSet = new HashSet<>(); + Set webINFSet = applicationContext.getResources("/WEB-INF/**/tiles*.xml"); + Set 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()); + + 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 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 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 000000000..b2e2700e8 --- /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(); + } + +} 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 b04908af1..cf874b1d0 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 INIT; - - static { - INIT = new HashMap(); - INIT.put(TilesContainerFactory.CONTAINER_FACTORY_INIT_PARAM, - StrutsTilesContainerFactory.class.getName()); + @Override + protected TilesInitializer createTilesInitializer() { + LOG.info("Starting Struts Tiles 2 integration ..."); + return new StrutsTilesInitializer(); } - - 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); - } - - protected ServletContext decorate(ServletContext context) { - return new ConfiguredServletContext(context, INIT); - } - -} +} \ No newline at end of file