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

org.apache.struts2.views.velocity.StrutsVelocityManager Maven / Gradle / Ivy

The newest version!
/*
 * 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.views.velocity;

import org.apache.struts2.ObjectFactory;
import org.apache.struts2.inject.Container;
import org.apache.struts2.inject.Inject;
import org.apache.struts2.util.ValueStack;
import jakarta.servlet.ServletContext;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.StrutsException;
import org.apache.struts2.views.TagLibraryDirectiveProvider;
import org.apache.struts2.views.util.ContextUtil;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.context.Context;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import static java.util.Collections.emptyList;
import static java.util.Objects.requireNonNullElse;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.struts2.views.util.ContextUtil.STRUTS;
import static org.apache.velocity.runtime.DeprecatedRuntimeConstants.OLD_CUSTOM_DIRECTIVES;

/**
 * Manages the environment for Velocity result types
 *
 * @since 7.0
 */
public class StrutsVelocityManager implements VelocityManager {

    private static final Logger LOG = LogManager.getLogger(StrutsVelocityManager.class);

    private ObjectFactory objectFactory;

    public static final String DEFAULT_CONFIG_FILE = "velocity.properties";
    public static final String KEY_VELOCITY_STRUTS_CONTEXT = ".KEY_velocity.struts2.context";

    private VelocityEngine velocityEngine;

    private VelocityTools velocityTools;

    /**
     * Names of contexts that will be chained on every request
     */
    private List chainedContextNames = emptyList();

    private Properties velocityProperties;

    private String customConfigFile;

    private List tagLibraries;

    @Inject
    public void setObjectFactory(ObjectFactory fac) {
        this.objectFactory = fac;
    }

    @Inject
    public void setContainer(Container container) {
        this.tagLibraries = container.getInstanceNames(TagLibraryDirectiveProvider.class).stream()
                .map(prefix -> container.getInstance(TagLibraryDirectiveProvider.class, prefix)).toList();
    }

    /**
     * @return a reference to the VelocityEngine used by all Struts Velocity results except directly
     * accessed *.vm pages (unless otherwise configured)
     */
    @Override
    public VelocityEngine getVelocityEngine() {
        return velocityEngine;
    }

    /**
     * This method is responsible for creating the standard VelocityContext used by all Struts Velocity views.
     *
     * @param stack the current {@link ValueStack}
     * @param req   the current HttpServletRequest
     * @param res   the current HttpServletResponse
     * @return a new StrutsVelocityContext
     */
    @Override
    public Context createContext(ValueStack stack, HttpServletRequest req, HttpServletResponse res) {
        Context context = null;
        if (velocityTools != null) {
            context = velocityTools.createContext();
        }
        if (context == null) {
            context = buildContext(stack, req, res);
        }
        req.setAttribute(KEY_VELOCITY_STRUTS_CONTEXT, context);
        return context;
    }

    protected Context buildContext(ValueStack stack, HttpServletRequest req, HttpServletResponse res) {
        List chainedContexts = prepareChainedContexts(req, res, stack.getContext());
        Context context = new StrutsVelocityContext(chainedContexts, stack);
        ContextUtil.getStandardContext(stack, req, res).forEach(context::put);
        VelocityStrutsUtil util = new VelocityStrutsUtil(velocityEngine, context, stack, req, res);
        context.put(STRUTS, util);
        return context;
    }

    /**
     * Constructs contexts for chaining on this request. This method does not perform any initialization of the
     * contexts. All that must be done in the context itself.
     *
     * @param servletRequest  the servlet request object
     * @param servletResponse the servlet response object
     * @param extraContext    map with extra context
     * @return a List of contexts to chain or an empty list
     */
    protected List prepareChainedContexts(HttpServletRequest servletRequest,
                                                           HttpServletResponse servletResponse,
                                                           Map extraContext) {
        List contextList = new ArrayList<>();
        for (String className : chainedContextNames) {
            try {
                VelocityContext velocityContext = (VelocityContext) objectFactory.buildBean(className, extraContext);
                contextList.add(velocityContext);
            } catch (Exception e) {
                LOG.warn("Unable to instantiate chained VelocityContext {}, skipping", className, e);
            }
        }
        return contextList;
    }

    /**
     * initializes the StrutsVelocityManager.  this should be called during the initialization process, say by
     * ServletDispatcher.  this may be called multiple times safely although calls beyond the first won't do anything
     *
     * @param context the current servlet context
     */
    @Override
    public synchronized void init(ServletContext context) {
        if (velocityEngine != null) {
            return;
        }
        velocityEngine = newVelocityEngine(context);
        if (velocityTools != null) {
            velocityTools.init(context, velocityEngine);
        }
    }

    protected Properties loadConfiguration(ServletContext context) {
        if (context == null) {
            throw new IllegalArgumentException("Error attempting to create a loadConfiguration from a null ServletContext!");
        }
        Properties properties = new Properties();
        applyDefaultConfiguration(context, properties); // Apply defaults before loading user overrides
        String defaultUserDirective = properties.getProperty("userdirective");

        applyUserConfiguration(context, properties);

        if (velocityProperties != null) { // Apply additional overriding properties if any
            velocityProperties.stringPropertyNames().forEach(k -> properties.setProperty(k, velocityProperties.getProperty(k)));
        }

        String userDirective = properties.getProperty(OLD_CUSTOM_DIRECTIVES);
        String newDirective = isBlank(userDirective) ? defaultUserDirective : userDirective.strip() + "," + defaultUserDirective;
        properties.setProperty(OLD_CUSTOM_DIRECTIVES, newDirective);

        if (LOG.isDebugEnabled()) {
            LOG.debug("Initializing Velocity with the following properties ...");
            properties.stringPropertyNames().forEach(k -> LOG.debug("    '{}' = '{}'", k, properties.getProperty(k)));
        }
        return properties;
    }

    /**
     * Load optional velocity properties using the following loading strategy
     * 
    *
  • relative to the servlet context path
  • *
  • relative to the WEB-INF directory
  • *
  • on the classpath
  • *
*/ private void applyUserConfiguration(ServletContext context, Properties properties) { String configFile = requireNonNullElse(customConfigFile, DEFAULT_CONFIG_FILE).trim(); try { if (loadFile(properties, context.getRealPath(configFile))) { return; } } catch (IOException e) { LOG.warn("Unable to load Velocity configuration from servlet context path", e); } try { if (loadFile(properties, context.getRealPath("/WEB-INF/" + configFile))) { return; } } catch (IOException e) { LOG.warn("Unable to load Velocity configuration from WEB-INF path", e); } try { loadClassPathFile(properties, configFile); } catch (IOException e) { LOG.warn("Unable to load Velocity configuration from classpath", e); } } private boolean loadClassPathFile(Properties properties, String configFile) throws IOException { try (InputStream is = StrutsVelocityManager.class.getClassLoader().getResourceAsStream(configFile)) { if (is == null) { return false; } properties.load(is); LOG.info("Initializing Velocity using {} from classpath", configFile); return true; } } private boolean loadFile(Properties properties, String fileName) throws IOException { if (fileName == null) { return false; } File file = new File(fileName); if (!file.isFile()) { return false; } try (InputStream is = new FileInputStream(file)) { properties.load(is); LOG.info("Initializing Velocity using {}", file.getCanonicalPath() + " from file system"); return true; } } @Inject(StrutsConstants.STRUTS_VELOCITY_CONFIGFILE) public void setCustomConfigFile(String customConfigFile) { this.customConfigFile = customConfigFile; } @Inject(StrutsConstants.STRUTS_VELOCITY_TOOLBOXLOCATION) public void setToolBoxLocation(String toolboxLocation) { if (!isBlank(toolboxLocation)) { this.velocityTools = new VelocityTools(toolboxLocation); } } public VelocityTools getVelocityTools() { return velocityTools; } /** * Allow users to specify via the struts.properties file a set of additional VelocityContexts to chain to the * StrutsVelocityContext. The intent is to allow these contexts to store helper objects that the ui developer may * want access to. Examples of reasonable VelocityContexts would be an IoCVelocityContext, a * SpringReferenceVelocityContext, and a ToolboxVelocityContext * * @param contexts comma separated velocity context's */ @Inject(StrutsConstants.STRUTS_VELOCITY_CONTEXTS) public void setChainedContexts(String contexts) { this.chainedContextNames = Arrays.stream(contexts.split(",")).filter(StringUtils::isNotBlank).collect(toList()); } /** * Instantiates a new VelocityEngine. *

* The following is the default Velocity configuration *

     *  resource.loader = file, class
     *  file.resource.loader.path = real path of webapp
     *  class.resource.loader.description = Velocity Classpath Resource Loader
     *  class.resource.loader.class = org.apache.struts2.views.velocity.StrutsResourceLoader
     * 
* This default configuration can be overridden by specifying a struts.velocity.configfile property in the * struts.properties file. the specified config file will be searched for in the following order: *
    *
  • relative to the servlet context path
  • *
  • relative to the WEB-INF directory
  • *
  • on the classpath
  • *
* * @param context the current ServletContext. may not be null * @return the new velocity engine */ protected VelocityEngine newVelocityEngine(ServletContext context) { if (context == null) { throw new IllegalArgumentException("Error attempting to create a new VelocityEngine from a null ServletContext!"); } VelocityEngine velocityEngine = new VelocityEngine(); velocityEngine.setApplicationAttribute(ServletContext.class.getName(), context); // Required for webapp loader try { velocityEngine.init(loadConfiguration(context)); } catch (Exception e) { throw new StrutsException("Unable to instantiate VelocityEngine!", e); } return velocityEngine; } /** * Once we've loaded up the user defined configurations, we will want to apply Struts specification configurations. *
    *
  • if Velocity.RESOURCE_LOADER has not been defined, then we will use the defaults which is a joined file, * class loader for unpackaed wars and a straight class loader otherwise
  • *
  • we need to define the various Struts custom user directives such as #param, #tag, and #bodytag
  • *
* * @param context the servlet context * @param properties velocity properties */ private void applyDefaultConfiguration(ServletContext context, Properties properties) { LOG.debug("Load a default resource loader definition if there isn't one present."); if (properties.getProperty(Velocity.RESOURCE_LOADER) == null) { properties.setProperty(Velocity.RESOURCE_LOADER, "strutsfile, strutsclass"); } // If there's a "real" path add it for the strutsfile resource loader. If there's no real path, and they haven't // configured a loader then we change resource loader property to just use the strutsclass loader String realPath = context.getRealPath(""); if (realPath != null) { setStrutsFileResourceLoader(properties, realPath); } else { clearStrutsFileResourceLoader(properties); } setStrutsClasspathResourceLoader(properties); String directives = tagLibraries.stream().map(TagLibraryDirectiveProvider::getDirectiveClasses) .flatMap(Collection::stream).map(directive -> directive.getName() + ",").collect(joining()); String userDirective = properties.getProperty(OLD_CUSTOM_DIRECTIVES); String newDirective = isBlank(userDirective) ? directives : userDirective.strip() + "," + directives; properties.setProperty(OLD_CUSTOM_DIRECTIVES, newDirective); } private void setStrutsFileResourceLoader(Properties properties, String realPath) { properties.setProperty("strutsfile.resource.loader.description", "Velocity File Resource Loader"); properties.setProperty("strutsfile.resource.loader.class", "org.apache.velocity.runtime.resource.loader.FileResourceLoader"); properties.setProperty("strutsfile.resource.loader.path", realPath); properties.setProperty("strutsfile.resource.loader.modificationCheckInterval", "2"); properties.setProperty("strutsfile.resource.loader.cache", "true"); } private void clearStrutsFileResourceLoader(Properties properties) { String prop = properties.getProperty(Velocity.RESOURCE_LOADER) .replace("strutsfile,", "") .replace(", strutsfile", "") .replace("strutsfile", ""); properties.setProperty(Velocity.RESOURCE_LOADER, prop); } /** * Refactored the Velocity templates for the Struts taglib into the classpath from the web path. This will * enable Struts projects to have access to the templates by simply including the Struts jar file. * Unfortunately, there does not appear to be a macro for the class loader keywords */ private void setStrutsClasspathResourceLoader(Properties properties) { properties.setProperty("strutsclass.resource.loader.description", "Velocity Classpath Resource Loader"); properties.setProperty("strutsclass.resource.loader.class", "org.apache.struts2.views.velocity.StrutsResourceLoader"); properties.setProperty("strutsclass.resource.loader.modificationCheckInterval", "2"); properties.setProperty("strutsclass.resource.loader.cache", "true"); } /** * @return the velocityProperties */ public Properties getVelocityProperties() { return velocityProperties; } /** * @param velocityProperties the velocityProperties to set */ public void setVelocityProperties(Properties velocityProperties) { this.velocityProperties = velocityProperties; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy