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

org.apache.struts.scripting.ScriptAction Maven / Gradle / Ivy

There is a newer version: 1.5.0-RC2
Show newest version
/*
 * $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
 * 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.struts.scripting;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

import javax.script.Bindings;
import javax.script.ScriptContext;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionMessages;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * This Action uses scripts to perform its action. The scripting framework is
 * Bean Scripting Framework 3.0 (JSR 223) which allows the scripts to be written
 * many of the popular scripting languages including JavaScript, Perl, Python,
 * and even VBA.
 *
 * 

To determine what script will be executed, the "parameter" attribute of the * action mapping should contain the name of the script relative to the web * application root directory (i.e. https://server/app).

* *

Before the script completes, the next {@code ActionForward} needs to be * specified. This can be done one of two ways:

* *
    *
  1. Set {@code struts.forwardName} to the name of the forward
  2. *
  3. Set {@code struts.forward} to the actual ActionForward object
  4. *
* *

A number of pre-defined variables are available to the script:

* *
    *
  • {@code request} - The HTTP request
  • *
  • {@code response} - The HTTP response
  • *
  • {@code session} - The session
  • *
  • {@code application} - The servlet context
  • *
  • {@code struts} - A grouping of all Struts-related objects
  • *
  • {@code log} - A logging instance
  • *
* *

You can add your own variables by creating a {@link ScriptContextFilter} * and configuring it in struts-scripting.properties:

* *
    *
  • {@code struts-scripting.filters.FILTER_NAME.class=FILTER_CLASS} * - The class implementing {@code ScriptContextFilter} where FILTER_NAME * is the name you are calling the filter.
  • *
  • {@code struts-scripting.filters.FILTER_NAME.PROPERTY_NAME=PROPERTY_VALUE} * - A property to be used by the filter.
  • *
* *

To use other scripting engines, add them to the classpath.

* *

To register more extensions to a scripting engine, create a file called * {@code struts-scripting.properties} and add one propertie for each * engine:

* *
    *
  • {@code struts-scripting.engine.ENGINE_NAME.extensions} * - A comma-delimited list of file extensions that will be used to * identify the engine to use to execute the script.
  • *
* *

This code was originally based off code from JPublish, but has since been * almost completely rewritten.

*/ public class ScriptAction extends Action { private static final long serialVersionUID = -383996253054413439L; /** * The {@code Log} instance for this class. */ private final static Logger LOG = LoggerFactory.getLogger(ScriptAction.class); /** The entry-point to JSR223-scripting */ private static final ScriptEngineManager SCRIPT_ENGINE_MANAGER = new ScriptEngineManager(); /** The default path to the properties file. */ protected static final String PROPS_PATH = "/struts-scripting.properties"; /** The base property for alternate BSF engines. */ protected static final String ENGINE_BASE = "struts-scripting.engine."; /** The base property for classes that put new variables in the context. */ protected static final String FILTERS_BASE = "struts-scripting.filters."; /** A list of initialized filters. */ private static ScriptContextFilter[] filters = null; /** Holds the "compiled" scripts and their information. */ private ConcurrentHashMap scripts = new ConcurrentHashMap<>(); static { final Properties props = new Properties(); try { InputStream in = ScriptAction.class.getClassLoader() .getResourceAsStream(PROPS_PATH); if (in == null) { in = ScriptAction.class.getClassLoader().getResourceAsStream( "/struts-bsf.properties"); if (in != null) { LOG.warn("The struts-bsf.properties file has been " + "deprecated. Please use " + "struts-scripting.properties instead."); } else { LOG.warn("struts-scripting.properties not found, using " + "default engine mappings."); } } if (in != null) { props.load(in); } } catch (Exception ex) { LOG.warn("Unable to load struts-scripting.properties, using " + " default engine mappings."); } final List allSefs = SCRIPT_ENGINE_MANAGER.getEngineFactories(); final int pos = ENGINE_BASE.length(); for (Enumeration e = props.propertyNames(); e.hasMoreElements();) { final String propName = e.nextElement().toString(); if (propName.startsWith(ENGINE_BASE) && propName.endsWith(".extensions")) { final String name = propName.substring(pos, propName.indexOf('.', pos)); ScriptEngineFactory[] sefs = allSefs.stream().filter((sef) -> sef.getNames().contains(name)) .toArray(ScriptEngineFactory[]::new); if (sefs.length == 0) { LOG.warn("No ScriptEngineFactory found - name: '{}'", name); continue; } else if (sefs.length > 1) { LOG.warn("More than one ScriptEngineFactory found, taking the first one - name: '{}'", name); } final ScriptEngineFactory sef = sefs[0]; LOG.info("Found ScriptingEngineFactory - name: {} language: {} {}", name, sef.getLanguageName(), sef.getLanguageVersion()); final String propValue = props.getProperty(propName).trim(); final String[] exts = propValue.split(","); if (exts.length == 0) { continue; } LOG.atInfo().log(() -> { final StringBuilder sb = new StringBuilder(); sb.append("Registering extension"); if (exts.length > 1) { sb.append('s'); } sb .append(" to ScriptingEngineFactory - name: '") .append(name) .append("' ext"); if (exts.length > 1) { sb.append('s'); } sb.append(": "); if (exts.length == 1) { sb .append('\'') .append(exts[0]) .append('\''); } else { sb.append(Arrays.toString(exts)); } return sb.toString(); }); for (String ext : exts) { ext = ext.trim(); if (!ext.isEmpty()) { SCRIPT_ENGINE_MANAGER.registerEngineExtension(ext, sef); } } } } filters = loadFilters(props); } /** * Executes the script. * * @param mapping The action mapping * @param form The action form * @param request The request object * @param response The response object * * @return The action forward * * @throws Exception If something goes wrong */ public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { final Map params = new LinkedHashMap<>(); final String scriptName = parseScriptName(mapping.getParameter(), params); if (scriptName == null) { LOG.error("No script specified in the parameter attribute"); throw new Exception("No script specified"); } LOG.debug("Executing script: {}", scriptName); HttpSession session = request.getSession(); ServletContext application = getServlet().getServletContext(); Script script = loadScript(scriptName, application); Bindings bindings = script.getBindings(); bindings.putAll(params); bindings.put("request", request); bindings.put("response", response); if (session == null) { LOG.debug("HTTP session is null"); } else { bindings.put("session", session); } bindings.put("application", application); bindings.put("log", LOG); StrutsInfo struts = new StrutsInfo(this, mapping, form, getResources(request)); bindings.put("struts", struts); final ScriptContext scriptContext = script.scriptEngine.getContext(); for (ScriptContextFilter filter : filters) { filter.apply(scriptContext); } script.eval(); ActionForward af = struts.getForward(); return af; } /** * Parses the script name and puts any URL parameters in * the context. * * @param url The script URL consisting of a path and * optional parameters * @param params A parameter-map to declare new parameters in * * @return The name of the script to execute */ protected String parseScriptName(String url, Map params) { LOG.debug("Parsing {}", url); if (url == null) { return null; } final String[] parsed = url.split("\\?", 2); if (parsed.length == 0) { return null; } if (parsed.length == 1) { LOG.debug("No query string: {}", parsed[0]); return parsed[0]; } LOG.debug("Found a query string"); final String[] args = parsed[1].split("&"); for (String arg : args) { final int i = arg.indexOf('='); String key = urlDecode(i > 0 ? arg.substring(0, i) : arg); while (params.containsKey(key)) { LOG.warn("Script variable {} already exists", key); key = "_" + key; } final String value = i > 0 && arg.length() > i + 1 ? urlDecode(arg.substring(i + 1)) : null; params.put(key, value); LOG.debug("Registering param {} with value {}", key, value); } return parsed[0]; } /** * Decodes a {@code application/x-www-form-urlencoded} string * using a the UTF-8-encoding scheme. * * @param s the {@code String} to decode * * @return the newly decoded {@code String} * * @see URLDecoder#decode(java.lang.String, java.lang.String) */ private String urlDecode(final String s) { if (s == null) { return null; } try { return URLDecoder.decode(s, StandardCharsets.UTF_8.toString()); } catch (UnsupportedEncodingException e) { // Should never thrown LOG.error("URL-Decode: ", e); } return null; } /** * Loads the script from cache if possible. Reloads if the script has been * recently modified. * * @param name The name of the script * @param context The servlet context * * @return The script object * * @throws IOException if an I/O error occurs * @throws ScriptException if compilation fails */ protected Script loadScript(final String name, final ServletContext context) throws IOException, ScriptException { final Script script = scripts.compute(name, (key, oldValue) -> { if (oldValue == null) { return new Script(SCRIPT_ENGINE_MANAGER, context, key); } oldValue.checkNewContent(); return oldValue; }); try { script.checkExceptions(); } catch (IOException e) { LOG.error("Unable to load script: {}", script.name, e); throw e; } catch (ScriptException e) { LOG.error("Unable to compile script: {}", script.name, e); throw e; } return script; } /** * Loads and initializes the filters. * *@param props The properties defining the filters *@return An array of the loaded filters */ protected static ScriptContextFilter[] loadFilters(Properties props) { ArrayList list = new ArrayList<>(); for (Enumeration e = props.propertyNames(); e.hasMoreElements();) { final String propName = e.nextElement().toString().trim(); if (propName.startsWith(FILTERS_BASE) && propName.endsWith("class")) { String name = propName.substring(FILTERS_BASE.length(), propName.indexOf(".", FILTERS_BASE.length())); String clazz = props.getProperty(propName).trim(); try { Class cls = Class.forName(clazz).asSubclass(ScriptContextFilter.class); ScriptContextFilter f = cls.getDeclaredConstructor().newInstance(); f.init(name, props); list.add(f); LOG.info("Loaded {} filter: {}", name, clazz); } catch (Exception ex) { LOG.error("Unable to load {} filter: {}", name, clazz); } } } return list.toArray(new ScriptContextFilter[0]); } // These methods seem necessary as some scripting engines are not able to // access Action's protected methods. Ugly? yes... any suggestions? /** * Saves a token. * * @param req The request object */ public void saveToken(HttpServletRequest req) { super.saveToken(req); } /** * Checks to see if the request is cancelled. * * @param req The request object * @return True if cancelled */ public boolean isCancelled(HttpServletRequest req) { return super.isCancelled(req); } /** * Checks to see if the token is valid. * * @param req The request object * @return True if valid */ public boolean isTokenValid(HttpServletRequest req) { return super.isTokenValid(req); } /** * Resets the token. * * @param req The request object */ public void resetToken(HttpServletRequest req) { super.resetToken(req); } /** * Gets the locale. * * @param req The request object * @return The locale value */ public Locale getLocale(HttpServletRequest req) { return super.getLocale(req); } /** * Saves the messages to the request. * * @param req The request object * @param mes The action messages */ public void saveMessages(HttpServletRequest req, ActionMessages mes) { super.saveMessages(req, mes); } /** * Saves the errors to the request. * * @param req The request object * @param errs The action errors * * @deprecated Use saveErrors(HttpServletRequest, ActionMessages) instead. * This will be removed after Struts 1.2. */ @Deprecated public void saveErrors(HttpServletRequest req, ActionErrors errs) { super.saveErrors(req, errs); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy