1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.apache.struts.scripting;
22
23
24 import java.util.ArrayList;
25 import java.util.Enumeration;
26 import java.util.Hashtable;
27 import java.util.List;
28 import java.util.Locale;
29 import java.util.Map;
30 import java.util.Properties;
31 import java.util.StringTokenizer;
32
33
34 import java.io.File;
35 import java.io.FileReader;
36 import java.io.IOException;
37 import java.io.InputStream;
38
39
40 import org.apache.commons.logging.Log;
41 import org.apache.commons.logging.LogFactory;
42
43
44 import org.apache.struts.action.Action;
45 import org.apache.struts.action.ActionErrors;
46 import org.apache.struts.action.ActionForm;
47 import org.apache.struts.action.ActionForward;
48 import org.apache.struts.action.ActionMapping;
49 import org.apache.struts.action.ActionMessages;
50
51
52 import javax.servlet.ServletContext;
53 import javax.servlet.http.HttpServletRequest;
54 import javax.servlet.http.HttpServletResponse;
55 import javax.servlet.http.HttpSession;
56 import org.apache.bsf.BSFException;
57 import org.apache.bsf.BSFManager;
58 import org.apache.bsf.util.IOUtils;
59
60
61 /**
62 * This Action uses scripts to perform its action. The scripting framework is
63 * Apache's Bean Scripting Framework which allows the scripts to be written
64 * many of the popular scripting languages including JavaScript, Perl, Python,
65 * and even VBA. <br />
66 * <br />
67 * To determine what script will be executed, the "parameter" attribute of the
68 * action mapping should contain the name of the script relative to the web
69 * application root directory (i.e. http://server/app). <br />
70 * <br />
71 * Before the script completes, the next ActionForward needs to be specified.
72 * This can be done one of two ways:
73 * <ol>
74 * <li> Set <code>struts.forwardName</code> to the name of the forward</li>
75 *
76 * <li> Set <code>struts.forward</code> to the actual ActionForward object
77 * </li>
78 * </ol>
79 * A number of pre-defined variables are available to the script:
80 * <ul>
81 * <li> <code>request</code> - The HTTP request</li>
82 * <li> <code>response</code> - The HTTP response</li>
83 * <li> <code>session</code> - The session</li>
84 * <li> <code>application</code> - The servlet context</li>
85 * <li> <code>struts</code> - A grouping of all Struts-related objects</li>
86 *
87 * <li> <code>log</code> - A logging instance</li>
88 * </ul>
89 * You can add your own variables by creating a BSFManagerFilter and
90 * configuring it in struts-scripting.properties:
91 * <ul>
92 * <li> <code>struts-scripting.filters.FILTER_NAME.class=FILTER_CLASS</code>
93 * - The class implementing BSFManagerFilter where FILTER_NAME is the name
94 * you are calling the filter.</li>
95 * <li> <code>
96 * struts-scripting.filters.FILTER_NAME.PROPERTY_NAME=PROPERTY_VALUE
97 * </code> - A property to be used by the filter.</li>
98 * </ul>
99 * <br />
100 * <br />
101 * To use other scripting engines other than BeanShell, create a file called
102 * <code>struts-scripting.properties</code> and add two properties for each
103 * engine:
104 * <ul>
105 * <li> <code>struts-scripting.engine.ENGINE_NAME.class</code> - The class of
106 * the BSF engine where ENGINE_NAME is the name you are calling the engine.
107 * </li>
108 * <li> <code>struts-scripting.engine.ENGINE_NAME.extensions</code> - A
109 * comma-delimited list of file extensions that will be used to identify the
110 * engine to use to execute the script.</li>
111 * </ul>
112 * This code was originally based off code from JPublish, but has since been
113 * almost completely rewritten.
114 */
115 public class ScriptAction extends Action {
116
117 /** The logging instance. */
118 protected static final Log LOG = LogFactory.getLog(ScriptAction.class);
119
120 /** The default path to the properties file. */
121 protected static final String PROPS_PATH = "/struts-scripting.properties";
122
123 /** The base property for alternate BSF engines. */
124 protected static final String ENGINE_BASE = "struts-scripting.engine.";
125
126 /** The base property for classes that put new variables in the context. */
127 protected static final String FILTERS_BASE = "struts-scripting.filters.";
128
129 /** A list of initialized filters. */
130 private static BSFManagerFilter[] filters = null;
131
132 /** Holds the "compiled" scripts and their information. */
133 private Map scripts = new Hashtable();
134
135 static {
136 Properties props = new Properties();
137 try {
138 InputStream in =
139 ScriptAction.class.getClassLoader()
140 .getResourceAsStream(PROPS_PATH);
141 if (in == null) {
142 in =
143 ScriptAction.class.getClassLoader().getResourceAsStream(
144 "/struts-bsf.properties");
145 if (in != null) {
146 LOG.warn("The struts-bsf.properties file has been "
147 + "deprecated. Please use "
148 + "struts-scripting.properties instead.");
149 } else {
150 LOG.warn("struts-scripting.properties not found, using "
151 + "default engine mappings.");
152 }
153 }
154
155 if (in != null) {
156 props.load(in);
157 }
158 } catch (Exception ex) {
159 LOG.warn("Unable to load struts-scripting.properties, using "
160 + " default engine mappings.");
161 }
162 int pos = ENGINE_BASE.length();
163 for (Enumeration e = props.propertyNames(); e.hasMoreElements();) {
164 String name = (String) e.nextElement();
165 if (name.startsWith(ENGINE_BASE) && name.endsWith(".class")) {
166 String type = name.substring(pos, name.indexOf('.', pos));
167 String cls = props.getProperty(name);
168 String ext = props.getProperty(ENGINE_BASE + type
169 + ".extensions", "");
170 String[] exts = split(ext, ",");
171 if (LOG.isInfoEnabled()) {
172 LOG.info("Loading BSF engine name:" + type + " class:"
173 + cls + " ext:" + ext);
174 }
175 BSFManager.registerScriptingEngine(type, cls, exts);
176 }
177 }
178 filters = loadFilters(props);
179 }
180
181
182 /**
183 * Executes the script.
184 *
185 *@param mapping The action mapping
186 *@param form The action form
187 *@param request The request object
188 *@param response The response object
189 *@return The action forward
190 *@exception Exception If something goes wrong
191 */
192 public ActionForward execute(ActionMapping mapping,
193 ActionForm form,
194 HttpServletRequest request,
195 HttpServletResponse response)
196 throws Exception {
197
198 BSFManager bsfManager = new BSFManager();
199
200 String scriptName = null;
201 try {
202 scriptName = parseScriptName(mapping.getParameter(), bsfManager);
203 } catch (Exception ex) {
204 LOG.error("Unable to parse " + mapping.getParameter(), ex);
205 throw new Exception("Unable to parse " + mapping.getParameter());
206 }
207 if (scriptName == null) {
208 LOG.error("No script specified in the parameter attribute");
209 throw new Exception("No script specified");
210 }
211
212 if (LOG.isDebugEnabled()) {
213 LOG.debug("Executing script: " + scriptName);
214 }
215
216 HttpSession session = request.getSession();
217 ServletContext application = getServlet().getServletContext();
218
219 Script script = loadScript(scriptName, application);
220
221 bsfManager.declareBean("request", request,
222 HttpServletRequest.class);
223
224 bsfManager.declareBean("response", response,
225 HttpServletResponse.class);
226
227 if (session == null) {
228 LOG.debug("HTTP session is null");
229 } else {
230 bsfManager.declareBean("session", session, HttpSession.class);
231 }
232
233 bsfManager.declareBean("application", application,
234 ServletContext.class);
235
236 bsfManager.declareBean("log", LOG, Log.class);
237 StrutsInfo struts = new StrutsInfo(this, mapping, form,
238 getResources(request));
239 bsfManager.declareBean("struts", struts, StrutsInfo.class);
240
241 for (int x = 0; x < filters.length; x++) {
242 filters[x].apply(bsfManager);
243 }
244
245 bsfManager.exec(script.lang, script.file.getCanonicalPath(), 0, 0,
246 script.string);
247
248 ActionForward af = struts.getForward();
249 return af;
250 }
251
252
253 /**
254 * Parses the script name and puts any url parameters in the context.
255 *
256 *@param url The script url consisting of a path and optional
257 * parameters
258 *@param manager The BSF manager to declare new parameters in
259 *@return The name of the script to execute
260 *@exception Exception If something goes wrong
261 */
262 protected String parseScriptName(String url, BSFManager manager)
263 throws Exception {
264 if (LOG.isDebugEnabled()) {
265 LOG.debug("Parsing " + url);
266 }
267 String name = null;
268 if (url != null) {
269 String[] parsed = split(url, "?");
270 name = parsed[0];
271 if (parsed.length == 2) {
272 if (LOG.isDebugEnabled()) {
273 LOG.debug("Found a query string");
274 }
275 String[] args = split(parsed[1], "&");
276 for (int x = 0; x < args.length; x++) {
277 String[] param = split(args[x], "=");
278 Object o = manager.lookupBean(param[0]);
279 if (o != null) {
280 LOG.warn("BSF variable " + param[0]
281 + " already exists");
282 param[0] = "_" + param[0];
283 }
284 manager.declareBean(param[0], param[1], String.class);
285 if (LOG.isDebugEnabled()) {
286 LOG.debug("Registering param " + param[0]
287 + " with value " + param[1]);
288 }
289 }
290 } else {
291 if (LOG.isDebugEnabled()) {
292 LOG.debug("No query string:" + parsed.length);
293 }
294 }
295 }
296 return name;
297 }
298
299
300 /**
301 * Loads the script from cache if possible. Reloads if the script has been
302 * recently modified.
303 *
304 *@param name The name of the script
305 *@param context The servlet context
306 *@return The script object
307 */
308 protected Script loadScript(String name, ServletContext context) {
309
310 Script script = (Script) scripts.get(name);
311 if (script == null) {
312 script = new Script();
313 script.file = new File(context.getRealPath(name));
314 try {
315 script.lang =
316 BSFManager.getLangFromFilename(script.file.getName());
317 } catch (BSFException ex) {
318 LOG.warn(ex, ex);
319 }
320 }
321
322 boolean reloadScript = false;
323 long scriptLastModified = script.file.lastModified();
324 if (scriptLastModified > script.timeLastLoaded) {
325 if (LOG.isDebugEnabled()) {
326 LOG.debug("Loading updated or new script: "
327 + script.file.getName());
328 }
329 reloadScript = true;
330 }
331
332 if (reloadScript || script.string == null) {
333 synchronized (this) {
334 script.timeLastLoaded = System.currentTimeMillis();
335 FileReader reader = null;
336 try {
337 reader = new FileReader(script.file);
338 script.string = IOUtils.getStringFromReader(reader);
339 } catch (IOException ex) {
340 LOG.error("Unable to load script: " + script.file, ex);
341 } finally {
342 if (reader != null) {
343 try {
344 reader.close();
345 } catch (IOException ex) {
346 LOG.debug(ex, ex);
347 }
348 }
349 }
350 }
351 }
352
353 return script;
354 }
355
356
357 /**
358 * Loads and initializes the filters.
359 *
360 *@param props The properties defining the filters
361 *@return An array of the loaded filters
362 */
363 protected static BSFManagerFilter[] loadFilters(Properties props) {
364 ArrayList list = new ArrayList();
365 for (Enumeration e = props.propertyNames(); e.hasMoreElements();) {
366 String prop = (String) e.nextElement();
367 if (prop.startsWith(FILTERS_BASE) && prop.endsWith("class")) {
368 String type = prop.substring(FILTERS_BASE.length(),
369 prop.indexOf(".", FILTERS_BASE.length()));
370 String claz = props.getProperty(prop);
371 try {
372 Class cls = Class.forName(claz);
373 BSFManagerFilter f = (BSFManagerFilter) cls.newInstance();
374 f.init(type, props);
375 list.add(f);
376 if (LOG.isInfoEnabled()) {
377 LOG.info("Loaded " + type + " filter: " + claz);
378 }
379 } catch (Exception ex) {
380 LOG.error("Unable to load " + type + " filter: " + claz);
381 }
382 }
383 }
384 BSFManagerFilter[] filters = new BSFManagerFilter[list.size()];
385 filters = (BSFManagerFilter[]) list.toArray(filters);
386 return filters;
387 }
388
389
390 /**
391 * Splits a line with the given delimiter.
392 *
393 *@param line The line to split
394 *@param delimiter The string to split with
395 *@return An array of substrings
396 */
397 protected static String[] split(String line, String delimiter) {
398 if (line == null || "".equals(line)) {
399 return new String[]{};
400 }
401
402 List lst = new ArrayList();
403 for (Enumeration e = new StringTokenizer(line, delimiter);
404 e.hasMoreElements();) {
405 lst.add(e.nextElement());
406 }
407 String[] ret = new String[lst.size()];
408 return (String[]) lst.toArray(ret);
409 }
410
411
412
413
414
415 /**
416 * Saves a token.
417 *
418 *@param req The request object
419 */
420 public void saveToken(HttpServletRequest req) {
421 super.saveToken(req);
422 }
423
424
425 /**
426 * Checks to see if the request is cancelled.
427 *
428 *@param req The request object
429 *@return True if cancelled
430 */
431 public boolean isCancelled(HttpServletRequest req) {
432 return super.isCancelled(req);
433 }
434
435
436 /**
437 * Checks to see if the token is valid.
438 *
439 *@param req The request object
440 *@return True if valid
441 */
442 public boolean isTokenValid(HttpServletRequest req) {
443 return super.isTokenValid(req);
444 }
445
446
447 /**
448 * Resets the token.
449 *
450 *@param req The request object
451 */
452 public void resetToken(HttpServletRequest req) {
453 super.resetToken(req);
454 }
455
456
457 /**
458 * Gets the locale.
459 *
460 *@param req The request object
461 *@return The locale value
462 */
463 public Locale getLocale(HttpServletRequest req) {
464 return super.getLocale(req);
465 }
466
467
468 /**
469 * Saves the messages to the request.
470 *
471 *@param req The request object
472 *@param mes The action messages
473 */
474 public void saveMessages(HttpServletRequest req, ActionMessages mes) {
475 super.saveMessages(req, mes);
476 }
477
478
479 /**
480 * Saves the errors to the request.
481 *
482 *@param req The request object
483 *@param errs The action errors
484 *@deprecated Use saveErrors(HttpServletRequest, ActionMessages) instead.
485 * This will be removed after Struts 1.2.
486 */
487 public void saveErrors(HttpServletRequest req, ActionErrors errs) {
488 super.saveErrors(req, errs);
489 }
490
491
492 /** Represents a saved script. */
493 class Script {
494
495 /** The script file. */
496 public File file;
497
498 /** The language the script is in. */
499 public String lang = null;
500
501 /** The time when the script was last used. */
502 public long timeLastLoaded = 0;
503
504 /** The contents of the script file. */
505 public String string = null;
506 }
507 }