1 package org.apache.velocity.tools.struts;
2
3 /*
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
19 * under the License.
20 */
21
22 import java.util.Locale;
23 import java.util.Iterator;
24
25 import javax.servlet.ServletContext;
26 import javax.servlet.http.HttpServletRequest;
27 import javax.servlet.http.HttpSession;
28
29 import org.apache.struts.Globals;
30 import org.apache.struts.action.ActionForm;
31 import org.apache.struts.action.ActionMessage;
32 import org.apache.struts.action.ActionMessages;
33 import org.apache.struts.config.ModuleConfig;
34 import org.apache.struts.config.ForwardConfig;
35 import org.apache.struts.config.ActionConfig;
36 import org.apache.struts.util.MessageResources;
37 import org.apache.struts.util.RequestUtils;
38 import org.apache.struts.taglib.TagUtils;
39 import org.apache.struts.util.ModuleUtils;
40
41 /**
42 * <p>A utility class to expose the Struts shared
43 * resources. All methods are static.</p>
44 *
45 * <p>This class is provided for use by Velocity view tools
46 * that need access to Struts resources. By having all Struts-
47 * specific code in this utility class, maintenance is simplified
48 * and reuse fostered.</p>
49 *
50 * <p>It is the aim, that sooner or later the functionality in
51 * this class is integrated into Struts itself. See
52 * <a href="http://nagoya.apache.org/bugzilla/show_bug.cgi?id=16814">Bug #16814</a>
53 * for more on that.</p>
54 *
55 * @author <a href="mailto:marinoj@centrum.is">Marino A. Jonsson</a>
56 * @author Nathan Bubna
57 * @author <a href="mailto:sidler@teamup.com">Gabe Sidler</a>
58 * based on code by <a href="mailto:ted@husted.org">Ted Husted</a>
59 *
60 * @version $Id: StrutsUtils.java 670093 2008-06-20 23:10:11Z nbubna $
61 */
62 public class StrutsUtils
63 {
64 public static final StrutsUtils INSTANCE = new StrutsUtils();
65
66 private StrutsUtils() {}
67
68 public StrutsUtils getInstance()
69 {
70 return INSTANCE;
71 }
72
73 /****************** Struts ServletContext Resources ****************/
74
75 /**
76 * Returns the message resources for this application or <code>null</code>
77 * if not found.
78 *
79 * @param app the servlet context
80 * @since VelocityTools 1.1
81 */
82 public static MessageResources getMessageResources(HttpServletRequest request,
83 ServletContext app)
84 {
85 return getMessageResources(request, app, null);
86 }
87
88
89 /**
90 * Returns the message resources with the specified bundle name for this application
91 * or <code>null</code> if not found.
92 *
93 * @param app the servlet context
94 * @param bundle The bundle name to look for. If this is <code>null</code>, the
95 * default bundle name is used.
96 * @since VelocityTools 1.1
97 */
98 public static MessageResources getMessageResources(HttpServletRequest request,
99 ServletContext app,
100 String bundle)
101 {
102 /* Identify the current module */
103 ModuleConfig moduleConfig = ModuleUtils.getInstance().getModuleConfig(request, app);
104
105 if (bundle == null) {
106 bundle = Globals.MESSAGES_KEY;
107 }
108
109 // First check request scope
110 MessageResources resources =
111 (MessageResources)request.getAttribute(bundle + moduleConfig.getPrefix());
112
113 if (resources == null) {
114 resources = (MessageResources) app.getAttribute(bundle + moduleConfig.getPrefix());
115 }
116 return resources;
117 }
118
119
120 /**
121 * Select the module to which the specified request belongs, and
122 * add return the corresponding ModuleConfig.
123 *
124 * @param urlPath The requested URL
125 * @param app The ServletContext for this web application
126 * @return The ModuleConfig for the given URL path
127 * @since VelocityTools 1.1
128 */
129 public static ModuleConfig selectModule(String urlPath,
130 ServletContext app)
131 {
132 /* Match against the list of sub-application prefixes */
133 String prefix = ModuleUtils.getInstance().getModuleName(urlPath, app);
134
135 /* Expose the resources for this sub-application */
136 ModuleConfig config = (ModuleConfig)
137 app.getAttribute(Globals.MODULE_KEY + prefix);
138
139 return config;
140 }
141
142
143 /********************** Struts Session Resources ******************/
144
145 /**
146 * Returns the <code>java.util.Locale</code> for the user. If a
147 * locale object is not found in the user's session, the system
148 * default locale is returned.
149 *
150 * @param request the servlet request
151 * @param session the HTTP session
152 */
153 public static Locale getLocale(HttpServletRequest request,
154 HttpSession session)
155 {
156 Locale locale = null;
157
158 if (session != null)
159 {
160 locale = (Locale)session.getAttribute(Globals.LOCALE_KEY);
161 }
162 if (locale == null)
163 {
164 locale = request.getLocale();
165 }
166 return locale;
167 }
168
169
170 /**
171 * Returns the transaction token stored in this session or
172 * <code>null</code> if not used.
173 *
174 * @param session the HTTP session
175 */
176 public static String getToken(HttpSession session)
177 {
178 if (session == null)
179 {
180 return null;
181 }
182 return (String)session.getAttribute(Globals.TRANSACTION_TOKEN_KEY);
183 }
184
185
186 /*********************** Struts Request Resources ****************/
187
188 /**
189 * Returns the Struts errors for this request or <code>null</code>
190 * if none exist. Since VelocityTools 1.2, this will also check
191 * the session (if there is one) for errors if there are no errors
192 * in the request.
193 *
194 * @param request the servlet request
195 * @since VelocityTools 1.1
196 */
197 public static ActionMessages getErrors(HttpServletRequest request)
198 {
199 ActionMessages errors = (ActionMessages)request.getAttribute(Globals.ERROR_KEY);
200 if (errors == null || errors.isEmpty())
201 {
202 // then check the session
203 HttpSession session = request.getSession(false);
204 if (session != null)
205 {
206 errors = (ActionMessages)session.getAttribute(Globals.ERROR_KEY);
207 }
208 }
209 return errors;
210 }
211
212 /**
213 * Returns the Struts messages for this request or <code>null</code>
214 * if none exist. Since VelocityTools 1.2, this will also check
215 * the session (if there is one) for messages if there are no messages
216 * in the request.
217 *
218 * @param request the servlet request
219 * @since VelocityTools 1.1
220 */
221 public static ActionMessages getMessages(HttpServletRequest request)
222 {
223 ActionMessages messages = (ActionMessages)request.getAttribute(Globals.MESSAGE_KEY);
224 if (messages == null || messages.isEmpty())
225 {
226 // then check the session
227 HttpSession session = request.getSession(false);
228 if (session != null)
229 {
230 messages = (ActionMessages)session.getAttribute(Globals.MESSAGE_KEY);
231 }
232 }
233 return messages;
234 }
235
236 /**
237 * Returns the <code>ActionForm</code> bean associated with
238 * this request of <code>null</code> if none exists.
239 *
240 * @param request the servlet request
241 * @param session the HTTP session
242 */
243 public static ActionForm getActionForm(HttpServletRequest request,
244 HttpSession session)
245 {
246 /* Is there a mapping associated with this request? */
247 ActionConfig mapping =
248 (ActionConfig)request.getAttribute(Globals.MAPPING_KEY);
249 if (mapping == null)
250 {
251 return null;
252 }
253
254 /* Is there a form bean associated with this mapping? */
255 String attribute = mapping.getAttribute();
256 if (attribute == null)
257 {
258 return null;
259 }
260
261 /* Look up the existing form bean */
262 if ("request".equals(mapping.getScope()))
263 {
264 return (ActionForm)request.getAttribute(attribute);
265 }
266 if (session != null)
267 {
268 return (ActionForm)session.getAttribute(attribute);
269 }
270 return null;
271 }
272
273 /**
274 * Returns the ActionForm name associated with
275 * this request of <code>null</code> if none exists.
276 *
277 * @param request the servlet request
278 * @param session the HTTP session
279 */
280 public static String getActionFormName(HttpServletRequest request,
281 HttpSession session)
282 {
283 /* Is there a mapping associated with this request? */
284 ActionConfig mapping =
285 (ActionConfig)request.getAttribute(Globals.MAPPING_KEY);
286 if (mapping == null)
287 {
288 return null;
289 }
290
291 return mapping.getAttribute();
292 }
293
294
295
296 /*************************** Utilities *************************/
297
298 /**
299 * Return the form action converted into an action mapping path. The
300 * value of the <code>action</code> property is manipulated as follows in
301 * computing the name of the requested mapping:
302 * <ul>
303 * <li>Any filename extension is removed (on the theory that extension
304 * mapping is being used to select the controller servlet).</li>
305 * <li>If the resulting value does not start with a slash, then a
306 * slash is prepended.</li>
307 * </ul>
308 */
309 public static String getActionMappingName(String action) {
310
311 String value = action;
312 int question = action.indexOf('?');
313 if (question >= 0) {
314 value = value.substring(0, question);
315 }
316
317 int slash = value.lastIndexOf('/');
318 int period = value.lastIndexOf('.');
319 if ((period >= 0) && (period > slash)) {
320 value = value.substring(0, period);
321 }
322
323 return value.startsWith("/") ? value : ("/" + value);
324 }
325
326 /**
327 * Returns the form action converted into a server-relative URI
328 * reference.
329 *
330 * @param application the servlet context
331 * @param request the servlet request
332 * @param action the name of an action as per struts-config.xml
333 */
334 public static String getActionMappingURL(ServletContext application,
335 HttpServletRequest request,
336 String action)
337 {
338 StringBuilder value = new StringBuilder(request.getContextPath());
339 ModuleConfig config =
340 (ModuleConfig)request.getAttribute(Globals.MODULE_KEY);
341 if (config != null)
342 {
343 value.append(config.getPrefix());
344 }
345
346 /* Use our servlet mapping, if one is specified */
347 String servletMapping =
348 (String)application.getAttribute(Globals.SERVLET_KEY);
349
350 if (servletMapping != null)
351 {
352 String queryString = null;
353 int question = action.indexOf('?');
354
355 if (question >= 0)
356 {
357 queryString = action.substring(question);
358 }
359
360 String actionMapping = TagUtils.getInstance().getActionMappingName(action);
361
362 if (servletMapping.startsWith("*."))
363 {
364 value.append(actionMapping);
365 value.append(servletMapping.substring(1));
366 }
367 else if (servletMapping.endsWith("/*"))
368 {
369 value.append(servletMapping.substring
370 (0, servletMapping.length() - 2));
371 value.append(actionMapping);
372 }
373
374 if (queryString != null)
375 {
376 value.append(queryString);
377 }
378 }
379 else
380 {
381 /* Otherwise, assume extension mapping is in use and extension is
382 * already included in the action property */
383 if (!action.startsWith("/"))
384 {
385 value.append("/");
386 }
387 value.append(action);
388 }
389
390 /* Return the completed value */
391 return value.toString();
392 }
393
394
395 /**
396 * Returns the action forward name converted into a server-relative URI
397 * reference.
398 *
399 * @param app the servlet context
400 * @param request the servlet request
401 * @param forward the name of a forward as per struts-config.xml
402 */
403 public static String getForwardURL(HttpServletRequest request,
404 ServletContext app,
405 String forward)
406 {
407 ModuleConfig moduleConfig = ModuleUtils.getInstance().getModuleConfig(request, app);
408 //TODO? beware of null module config if ActionServlet isn't init'ed?
409
410 ActionConfig actionConfig =
411 (ActionConfig)request.getAttribute(Globals.MAPPING_KEY);
412
413 // NOTE: ActionConfig.findForwardConfig only searches local forwards
414 ForwardConfig fc = null;
415 if(actionConfig != null)
416 {
417 fc = actionConfig.findForwardConfig(forward);
418 }
419
420 // No ActionConfig forward?
421 // Find the ForwardConfig in the global-forwards.
422 if(fc == null)
423 {
424 fc = moduleConfig.findForwardConfig(forward);
425
426 // ok, give up
427 if (fc == null)
428 {
429 return null;
430 }
431 }
432
433 StringBuilder url = new StringBuilder();
434 if (fc.getPath().startsWith("/"))
435 {
436 url.append(request.getContextPath());
437 url.append(RequestUtils.forwardURL(request, fc, moduleConfig));
438 }
439 else
440 {
441 url.append(fc.getPath());
442 }
443 return url.toString();
444 }
445
446
447 /**
448 * Returns a formatted error message. The error message is assembled from
449 * the following three pieces: First, value of message resource
450 * "errors.header" is prepended. Then, the list of error messages is
451 * rendered. Finally, the value of message resource "errors.footer"
452 * is appended.
453 *
454 * @param property the category of errors to markup and return
455 * @param request the servlet request
456 * @param session the HTTP session
457 * @param application the servlet context
458 *
459 * @return The formatted error message. If no error messages are queued,
460 * an empty string is returned.
461 */
462 public static String errorMarkup(String property,
463 HttpServletRequest request,
464 HttpSession session,
465 ServletContext application)
466 {
467 return errorMarkup(property, null, request, session, application);
468 }
469
470
471 /**
472 * Returns a formatted error message. The error message is assembled from
473 * the following three pieces: First, value of message resource
474 * "errors.header" is prepended. Then, the list of error messages is
475 * rendered. Finally, the value of message resource "errors.footer"
476 * is appended.
477 *
478 * @param property the category of errors to markup and return
479 * @param bundle the message resource bundle to use
480 * @param request the servlet request
481 * @param session the HTTP session
482 * @param application the servlet context
483 * @since VelocityTools 1.1
484 * @return The formatted error message. If no error messages are queued,
485 * an empty string is returned.
486 */
487 public static String errorMarkup(String property,
488 String bundle,
489 HttpServletRequest request,
490 HttpSession session,
491 ServletContext application)
492 {
493 ActionMessages errors = getErrors(request);
494 if (errors == null)
495 {
496 return "";
497 }
498
499 /* fetch the error messages */
500 Iterator reports = null;
501 if (property == null)
502 {
503 reports = errors.get();
504 }
505 else
506 {
507 reports = errors.get(property);
508 }
509
510 if (!reports.hasNext())
511 {
512 return "";
513 }
514
515 /* Render the error messages appropriately if errors have been queued */
516 StringBuilder results = new StringBuilder();
517 String header = null;
518 String footer = null;
519 String prefix = null;
520 String suffix = null;
521 Locale locale = getLocale(request, session);
522
523 MessageResources resources =
524 getMessageResources(request, application, bundle);
525 if (resources != null)
526 {
527 header = resources.getMessage(locale, "errors.header");
528 footer = resources.getMessage(locale, "errors.footer");
529 prefix = resources.getMessage(locale, "errors.prefix");
530 suffix = resources.getMessage(locale, "errors.suffix");
531 }
532 if (header == null)
533 {
534 header = "errors.header";
535 }
536 if (footer == null)
537 {
538 footer = "errors.footer";
539 }
540 /* prefix or suffix are optional, be quiet if they're missing */
541 if (prefix == null)
542 {
543 prefix = "";
544 }
545 if (suffix == null)
546 {
547 suffix = "";
548 }
549
550 results.append(header);
551 results.append("\r\n");
552
553 String message;
554 while (reports.hasNext())
555 {
556 message = null;
557 ActionMessage report = (ActionMessage)reports.next();
558 if (resources != null && report.isResource())
559 {
560 message = resources.getMessage(locale,
561 report.getKey(),
562 report.getValues());
563 }
564
565 results.append(prefix);
566
567 if (message != null)
568 {
569 results.append(message);
570 }
571 else
572 {
573 results.append(report.getKey());
574 }
575
576 results.append(suffix);
577 results.append("\r\n");
578 }
579
580 results.append(footer);
581 results.append("\r\n");
582
583 /* return result */
584 return results.toString();
585 }
586
587 }