Source code: org/apache/struts/taglib/TagUtils.java
1 /*
2 * $Id: TagUtils.java 105982 2004-11-20 17:20:35Z craigmcc $
3 *
4 * Copyright 1999-2004 The Apache Software Foundation.
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18
19 package org.apache.struts.taglib;
20
21 import java.io.IOException;
22 import java.lang.reflect.InvocationTargetException;
23 import java.lang.reflect.Method;
24 import java.net.MalformedURLException;
25 import java.net.URLEncoder;
26 import java.util.HashMap;
27 import java.util.Iterator;
28 import java.util.Locale;
29 import java.util.Map;
30
31 import javax.servlet.http.HttpServletRequest;
32 import javax.servlet.http.HttpServletResponse;
33 import javax.servlet.http.HttpSession;
34 import javax.servlet.jsp.JspException;
35 import javax.servlet.jsp.JspWriter;
36 import javax.servlet.jsp.PageContext;
37 import javax.servlet.jsp.tagext.BodyContent;
38
39 import org.apache.commons.beanutils.PropertyUtils;
40 import org.apache.commons.logging.Log;
41 import org.apache.commons.logging.LogFactory;
42 import org.apache.struts.Globals;
43 import org.apache.struts.action.ActionErrors;
44 import org.apache.struts.action.ActionMessage;
45 import org.apache.struts.action.ActionMessages;
46 import org.apache.struts.config.ForwardConfig;
47 import org.apache.struts.config.ModuleConfig;
48 import org.apache.struts.taglib.html.Constants;
49 import org.apache.struts.util.MessageResources;
50 import org.apache.struts.util.ModuleUtils;
51 import org.apache.struts.util.RequestUtils;
52
53 /**
54 * Provides helper methods for JSP tags.
55 *
56 * @version $Rev: 105982 $
57 * @since Struts 1.2
58 */
59 public class TagUtils {
60
61 /**
62 * The Singleton instance.
63 */
64 private static final TagUtils instance = new TagUtils();
65
66 /**
67 * Commons logging instance.
68 */
69 private static final Log log = LogFactory.getLog(TagUtils.class);
70
71 /**
72 * The message resources for this package.
73 * TODO We need to move the relevant messages out of this properties file.
74 */
75 private static final MessageResources messages =
76 MessageResources.getMessageResources("org.apache.struts.taglib.LocalStrings");
77
78 /**
79 * Java 1.4 encode method to use instead of deprecated 1.3 version.
80 */
81 private static Method encode = null;
82
83 /**
84 * Maps lowercase JSP scope names to their PageContext integer constant
85 * values.
86 */
87 private static final Map scopes = new HashMap();
88
89 /**
90 * Initialize the scope names map and the encode variable with the
91 * Java 1.4 method if available.
92 */
93 static {
94
95 try {
96 // get version of encode method with two String args
97 Class[] args = new Class[]{String.class, String.class};
98 encode = URLEncoder.class.getMethod("encode", args);
99 } catch (NoSuchMethodException e) {
100 log.debug("Could not find Java 1.4 encode method. Using deprecated version.", e);
101 }
102
103 scopes.put("page", new Integer(PageContext.PAGE_SCOPE));
104 scopes.put("request", new Integer(PageContext.REQUEST_SCOPE));
105 scopes.put("session", new Integer(PageContext.SESSION_SCOPE));
106 scopes.put("application", new Integer(PageContext.APPLICATION_SCOPE));
107 }
108
109 /**
110 * Constructor for TagUtils.
111 */
112 protected TagUtils() {
113 super();
114 }
115
116 /**
117 * Returns the Singleton instance of TagUtils.
118 */
119 public static TagUtils getInstance() {
120 return instance;
121 }
122
123 /**
124 * Compute a set of query parameters that will be dynamically added to
125 * a generated URL. The returned Map is keyed by parameter name, and the
126 * values are either null (no value specified), a String (single value
127 * specified), or a String[] array (multiple values specified). Parameter
128 * names correspond to the corresponding attributes of the
129 * <code><html:link></code> tag. If no query parameters are
130 * identified, return <code>null</code>.
131 *
132 * @param pageContext PageContext we are operating in
133
134 * @param paramId Single-value request parameter name (if any)
135 * @param paramName Bean containing single-value parameter value
136 * @param paramProperty Property (of bean named by <code>paramName</code>
137 * containing single-value parameter value
138 * @param paramScope Scope containing bean named by
139 * <code>paramName</code>
140 *
141 * @param name Bean containing multi-value parameters Map (if any)
142 * @param property Property (of bean named by <code>name</code>
143 * containing multi-value parameters Map
144 * @param scope Scope containing bean named by
145 * <code>name</code>
146 *
147 * @param transaction Should we add our transaction control token?
148 * @return Map of query parameters
149 * @exception JspException if we cannot look up the required beans
150 * @exception JspException if a class cast exception occurs on a
151 * looked-up bean or property
152 */
153 public Map computeParameters(
154 PageContext pageContext,
155 String paramId,
156 String paramName,
157 String paramProperty,
158 String paramScope,
159 String name,
160 String property,
161 String scope,
162 boolean transaction)
163 throws JspException {
164
165 // Short circuit if no parameters are specified
166 if ((paramId == null) && (name == null) && !transaction) {
167 return (null);
168 }
169
170 // Locate the Map containing our multi-value parameters map
171 Map map = null;
172 try {
173 if (name != null) {
174 map =
175 (Map) TagUtils.getInstance().lookup(
176 pageContext,
177 name,
178 property,
179 scope);
180 }
181 } catch (ClassCastException e) {
182 saveException(pageContext, e);
183 throw new JspException(
184 messages.getMessage("parameters.multi", name, property, scope));
185
186 } catch (JspException e) {
187 saveException(pageContext, e);
188 throw e;
189 }
190
191 // Create a Map to contain our results from the multi-value parameters
192 Map results = null;
193 if (map != null) {
194 results = new HashMap(map);
195 } else {
196 results = new HashMap();
197 }
198
199 // Add the single-value parameter (if any)
200 if ((paramId != null) && (paramName != null)) {
201
202 Object paramValue = null;
203 try {
204 paramValue =
205 TagUtils.getInstance().lookup(
206 pageContext,
207 paramName,
208 paramProperty,
209 paramScope);
210
211 } catch (JspException e) {
212 saveException(pageContext, e);
213 throw e;
214 }
215
216 if (paramValue != null) {
217
218 String paramString = null;
219 if (paramValue instanceof String) {
220 paramString = (String) paramValue;
221 } else {
222 paramString = paramValue.toString();
223 }
224
225 Object mapValue = results.get(paramId);
226 if (mapValue == null) {
227 results.put(paramId, paramString);
228
229 } else if (mapValue instanceof String) {
230 String newValues[] = new String[2];
231 newValues[0] = (String) mapValue;
232 newValues[1] = paramString;
233 results.put(paramId, newValues);
234
235 } else {
236 String oldValues[] = (String[]) mapValue;
237 String newValues[] = new String[oldValues.length + 1];
238 System.arraycopy(oldValues, 0, newValues, 0, oldValues.length);
239 newValues[oldValues.length] = paramString;
240 results.put(paramId, newValues);
241 }
242
243 }
244
245 }
246
247 // Add our transaction control token (if requested)
248 if (transaction) {
249 HttpSession session = pageContext.getSession();
250 String token = null;
251 if (session != null) {
252 token = (String) session.getAttribute(Globals.TRANSACTION_TOKEN_KEY);
253 }
254
255 if (token != null) {
256 results.put(Constants.TOKEN_KEY, token);
257 }
258 }
259
260 // Return the completed Map
261 return (results);
262
263 }
264
265 public String computeURL(
266 PageContext pageContext,
267 String forward,
268 String href,
269 String page,
270 String action,
271 String module,
272 Map params,
273 String anchor,
274 boolean redirect)
275 throws MalformedURLException {
276 return this.computeURLWithCharEncoding(
277 pageContext,
278 forward,
279 href,
280 page,
281 action,
282 module,
283 params,
284 anchor,
285 redirect,
286 false);
287 }
288
289 /**
290 * Compute a hyperlink URL based on the <code>forward</code>,
291 * <code>href</code>, <code>action</code> or <code>page</code> parameter
292 * that is not null.
293 * The returned URL will have already been passed to
294 * <code>response.encodeURL()</code> for adding a session identifier.
295 *
296 * @param pageContext PageContext for the tag making this call
297 *
298 * @param forward Logical forward name for which to look up
299 * the context-relative URI (if specified)
300 * @param href URL to be utilized unmodified (if specified)
301 * @param page Module-relative page for which a URL should
302 * be created (if specified)
303 * @param action Logical action name for which to look up
304 * the context-relative URI (if specified)
305 *
306 * @param params Map of parameters to be dynamically included (if any)
307 * @param anchor Anchor to be dynamically included (if any)
308 *
309 * @param redirect Is this URL for a <code>response.sendRedirect()</code>?
310 * @return URL with session identifier
311 * @exception java.net.MalformedURLException if a URL cannot be created
312 * for the specified parameters
313 */
314 public String computeURLWithCharEncoding(
315 PageContext pageContext,
316 String forward,
317 String href,
318 String page,
319 String action,
320 String module,
321 Map params,
322 String anchor,
323 boolean redirect,
324 boolean useLocalEncoding)
325 throws MalformedURLException {
326
327 return computeURLWithCharEncoding(
328 pageContext,
329 forward,
330 href,
331 page,
332 action,
333 module,
334 params,
335 anchor,
336 redirect,
337 true,
338 useLocalEncoding);
339 }
340
341 public String computeURL(
342 PageContext pageContext,
343 String forward,
344 String href,
345 String page,
346 String action,
347 String module,
348 Map params,
349 String anchor,
350 boolean redirect,
351 boolean encodeSeparator)
352 throws MalformedURLException {
353 return computeURLWithCharEncoding(
354 pageContext,
355 forward,
356 href,
357 page,
358 action,
359 module,
360 params,
361 anchor,
362 redirect,
363 encodeSeparator,
364 false
365 );
366 }
367
368 /**
369 * Compute a hyperlink URL based on the <code>forward</code>,
370 * <code>href</code>, <code>action</code> or <code>page</code> parameter
371 * that is not null.
372 * The returned URL will have already been passed to
373 * <code>response.encodeURL()</code> for adding a session identifier.
374 *
375 * @param pageContext PageContext for the tag making this call
376 *
377 * @param forward Logical forward name for which to look up
378 * the context-relative URI (if specified)
379 * @param href URL to be utilized unmodified (if specified)
380 * @param page Module-relative page for which a URL should
381 * be created (if specified)
382 * @param action Logical action name for which to look up
383 * the context-relative URI (if specified)
384 *
385 * @param params Map of parameters to be dynamically included (if any)
386 * @param anchor Anchor to be dynamically included (if any)
387 *
388 * @param redirect Is this URL for a <code>response.sendRedirect()</code>?
389 * @param encodeSeparator This is only checked if redirect is set to false (never
390 * encoded for a redirect). If true, query string parameter separators are encoded
391 * as >amp;, else & is used.
392 * @param useLocalEncoding If set to true, urlencoding is done on the bytes of
393 * character encoding from ServletResponse#getCharacterEncoding. Use UTF-8
394 * otherwise.
395 * @return URL with session identifier
396 * @exception java.net.MalformedURLException if a URL cannot be created
397 * for the specified parameters
398 */
399 public String computeURLWithCharEncoding(
400 PageContext pageContext,
401 String forward,
402 String href,
403 String page,
404 String action,
405 String module,
406 Map params,
407 String anchor,
408 boolean redirect,
409 boolean encodeSeparator,
410 boolean useLocalEncoding)
411 throws MalformedURLException {
412 String charEncoding = "UTF-8";
413 if(useLocalEncoding){
414 charEncoding = pageContext.getResponse().getCharacterEncoding();
415 }
416
417 // TODO All the computeURL() methods need refactoring!
418
419 // Validate that exactly one specifier was included
420 int n = 0;
421 if (forward != null) {
422 n++;
423 }
424 if (href != null) {
425 n++;
426 }
427 if (page != null) {
428 n++;
429 }
430 if (action != null) {
431 n++;
432 }
433 if (n != 1) {
434 throw new MalformedURLException(messages.getMessage("computeURL.specifier"));
435 }
436
437 // Look up the module configuration for this request
438 ModuleConfig moduleConfig = instance.getModuleConfig(module, pageContext);
439
440 // Calculate the appropriate URL
441 StringBuffer url = new StringBuffer();
442 HttpServletRequest request = (HttpServletRequest) pageContext.getRequest();
443 if (forward != null) {
444 ForwardConfig forwardConfig = moduleConfig.findForwardConfig(forward);
445 if (forwardConfig == null) {
446 throw new MalformedURLException(messages.getMessage("computeURL.forward", forward));
447 }
448 if (forwardConfig.getRedirect()) {
449 redirect = true;
450 }
451 if (forwardConfig.getPath().startsWith("/")) {
452 url.append(request.getContextPath());
453 url.append(RequestUtils.forwardURL(request, forwardConfig, moduleConfig));
454 } else {
455 url.append(forwardConfig.getPath());
456 }
457 } else if (href != null) {
458 url.append(href);
459 } else if (action != null) {
460 url.append(instance.getActionMappingURL(action, module, pageContext, false));
461
462 } else /* if (page != null) */ {
463 url.append(request.getContextPath());
464 url.append(this.pageURL(request, page, moduleConfig));
465 }
466
467 // Add anchor if requested (replacing any existing anchor)
468 if (anchor != null) {
469 String temp = url.toString();
470 int hash = temp.indexOf('#');
471 if (hash >= 0) {
472 url.setLength(hash);
473 }
474 url.append('#');
475 url.append(this.encodeURL(anchor, charEncoding));
476 }
477
478 // Add dynamic parameters if requested
479 if ((params != null) && (params.size() > 0)) {
480
481 // Save any existing anchor
482 String temp = url.toString();
483 int hash = temp.indexOf('#');
484 if (hash >= 0) {
485 anchor = temp.substring(hash + 1);
486 url.setLength(hash);
487 temp = url.toString();
488 } else {
489 anchor = null;
490 }
491
492 // Define the parameter separator
493 String separator = null;
494 if (redirect) {
495 separator = "&";
496 } else if (encodeSeparator) {
497 separator = "&";
498 } else {
499 separator = "&";
500 }
501
502 // Add the required request parameters
503 boolean question = temp.indexOf('?') >= 0;
504 Iterator keys = params.keySet().iterator();
505 while (keys.hasNext()) {
506 String key = (String) keys.next();
507 Object value = params.get(key);
508 if (value == null) {
509 if (!question) {
510 url.append('?');
511 question = true;
512 } else {
513 url.append(separator);
514 }
515 url.append(this.encodeURL(key, charEncoding));
516 url.append('='); // Interpret null as "no value"
517 } else if (value instanceof String) {
518 if (!question) {
519 url.append('?');
520 question = true;
521 } else {
522 url.append(separator);
523 }
524 url.append(this.encodeURL(key, charEncoding));
525 url.append('=');
526 url.append(this.encodeURL((String) value, charEncoding));
527 } else if (value instanceof String[]) {
528 String values[] = (String[]) value;
529 for (int i = 0; i < values.length; i++) {
530 if (!question) {
531 url.append('?');
532 question = true;
533 } else {
534 url.append(separator);
535 }
536 url.append(this.encodeURL(key, charEncoding));
537 url.append('=');
538 url.append(this.encodeURL(values[i], charEncoding));
539 }
540 } else /* Convert other objects to a string */ {
541 if (!question) {
542 url.append('?');
543 question = true;
544 } else {
545 url.append(separator);
546 }
547 url.append(this.encodeURL(key, charEncoding));
548 url.append('=');
549 url.append(this.encodeURL(value.toString(), charEncoding));
550 }
551 }
552
553 // Re-add the saved anchor (if any)
554 if (anchor != null) {
555 url.append('#');
556 url.append(this.encodeURL(anchor, charEncoding));
557 }
558
559 }
560
561 // Perform URL rewriting to include our session ID (if any)
562 // but only if url is not an external URL
563 if (( href == null ) && ( pageContext.getSession() != null )) {
564 HttpServletResponse response = (HttpServletResponse) pageContext.getResponse();
565 if (redirect) {
566 return (response.encodeRedirectURL(url.toString()));
567 } else {
568 return (response.encodeURL(url.toString()));
569 }
570 } else {
571 return (url.toString());
572 }
573
574 }
575
576
577 /**
578 * URLencodes a string assuming the character encoding is UTF-8.
579 *
580 * @param url
581 * @return String The encoded url in UTF-8
582 */
583 public String encodeURL(String url) {
584 return encodeURL(url, "UTF-8");
585 }
586
587 /**
588 * Use the new URLEncoder.encode() method from Java 1.4 if available, else
589 * use the old deprecated version. This method uses reflection to find the
590 * appropriate method; if the reflection operations throw exceptions, this
591 * will return the url encoded with the old URLEncoder.encode() method.
592 * @param enc The character encoding the urlencode is performed on.
593 * @return String The encoded url.
594 */
595 public String encodeURL(String url, String enc) {
596 try {
597
598 if(enc==null || enc.length()==0){
599 enc = "UTF-8";
600 }
601
602 // encode url with new 1.4 method and UTF-8 encoding
603 if (encode != null) {
604 return (String) encode.invoke(null, new Object[]{url, enc});
605 }
606
607 } catch (IllegalAccessException e) {
608 log.debug("Could not find Java 1.4 encode method. Using deprecated version.", e);
609 } catch (InvocationTargetException e) {
610 log.debug("Could not find Java 1.4 encode method. Using deprecated version.", e);
611 }
612
613 return URLEncoder.encode(url);
614 }
615
616 /**
617 * Filter the specified string for characters that are senstive to
618 * HTML interpreters, returning the string with these characters replaced
619 * by the corresponding character entities.
620 *
621 * @param value The string to be filtered and returned
622 */
623 public String filter(String value) {
624
625 if (value == null || value.length() == 0) {
626 return value;
627 }
628
629 StringBuffer result = null;
630 String filtered = null;
631 for (int i = 0; i < value.length(); i++) {
632 filtered = null;
633 switch (value.charAt(i)) {
634 case '<':
635 filtered = "<";
636 break;
637 case '>':
638 filtered = ">";
639 break;
640 case '&':
641 filtered = "&";
642 break;
643 case '"':
644 filtered = """;
645 break;
646 case '\'':
647 filtered = "'";
648 break;
649 }
650
651 if (result == null) {
652 if (filtered != null) {
653 result = new StringBuffer(value.length() + 50);
654 if (i > 0) {
655 result.append(value.substring(0, i));
656 }
657 result.append(filtered);
658 }
659 } else {
660 if (filtered == null) {
661 result.append(value.charAt(i));
662 } else {
663 result.append(filtered);
664 }
665 }
666 }
667
668 return result == null ? value : result.toString();
669 }
670
671 /**
672 * Retrieves the value from request scope and if it isn't already an
673 * <code>ErrorMessages</code> some classes are converted to one.
674 *
675 * @param pageContext The PageContext for the current page
676 * @param paramName Key for parameter value
677 * @return ActionErrors from request scope
678 * @exception JspException
679 * @deprecated Use getActionMessages() instead. This will be removed
680 * after Struts 1.2.
681 */
682 public ActionErrors getActionErrors(PageContext pageContext, String paramName)
683 throws JspException {
684
685 ActionErrors errors = new ActionErrors();
686
687 Object value = pageContext.findAttribute(paramName);
688 if (value != null) {
689 try {
690 if (value instanceof String) {
691 errors.add(
692 ActionMessages.GLOBAL_MESSAGE,
693 new ActionMessage((String) value));
694
695 } else if (value instanceof String[]) {
696 String keys[] = (String[]) value;
697 for (int i = 0; i < keys.length; i++) {
698 errors.add(
699 ActionMessages.GLOBAL_MESSAGE,
700 new ActionMessage(keys[i]));
701 }
702
703 } else if (value instanceof ActionErrors) {
704 errors = (ActionErrors) value;
705
706 } else {
707 throw new JspException(
708 messages.getMessage(
709 "actionErrors.errors",
710 value.getClass().getName()));
711 }
712
713 } catch (JspException e) {
714 throw e;
715
716 } catch (Exception e) {
717 log.debug(e, e);
718 }
719 }
720 return errors;
721 }
722
723 /**
724 * Return the form action converted into an action mapping path. The
725 * value of the <code>action</code> property is manipulated as follows in
726 * computing the name of the requested mapping:
727 * <ul>
728 * <li>Any filename extension is removed (on the theory that extension
729 * mapping is being used to select the controller servlet).</li>
730 * <li>If the resulting value does not start with a slash, then a
731 * slash is prepended.</li>
732 * </ul>
733 */
734 public String getActionMappingName(String action) {
735
736 String value = action;
737 int question = action.indexOf("?");
738 if (question >= 0) {
739 value = value.substring(0, question);
740 }
741
742 int slash = value.lastIndexOf("/");
743 int period = value.lastIndexOf(".");
744 if ((period >= 0) && (period > slash)) {
745 value = value.substring(0, period);
746 }
747
748 return value.startsWith("/") ? value : ("/" + value);
749 }
750
751
752 /**
753 * Return the form action converted into a server-relative URL.
754 */
755 public String getActionMappingURL(String action, PageContext pageContext) {
756 return getActionMappingURL(action,null,pageContext,false);
757 }
758
759
760 /**
761 * Return the form action converted into a server-relative URL.
762 */
763 public String getActionMappingURL(String action, String module, PageContext pageContext, boolean contextRelative) {
764
765 HttpServletRequest request = (HttpServletRequest) pageContext.getRequest();
766
767 String contextPath = request.getContextPath();
768 StringBuffer value = new StringBuffer();
769 // Avoid setting two slashes at the beginning of an action:
770 // the length of contextPath should be more than 1
771 // in case of non-root context, otherwise length==1 (the slash)
772 if (contextPath.length() > 1) value.append(contextPath);
773
774 ModuleConfig moduleConfig = ModuleUtils.getInstance().getModuleConfig(module, request, pageContext.getServletContext());
775
776 if ((moduleConfig != null) && (!contextRelative)) {
777 value.append(moduleConfig.getPrefix());
778 }
779
780 // Use our servlet mapping, if one is specified
781 String servletMapping =
782 (String) pageContext.getAttribute(
783 Globals.SERVLET_KEY,
784 PageContext.APPLICATION_SCOPE);
785
786 if (servletMapping != null) {
787
788 String queryString = null;
789 int question = action.indexOf("?");
790 if (question >= 0) {
791 queryString = action.substring(question);
792 }
793
794 String actionMapping = getActionMappingName(action);
795 if (servletMapping.startsWith("*.")) {
796 value.append(actionMapping);
797 value.append(servletMapping.substring(1));
798
799 } else if (servletMapping.endsWith("/*")) {
800 value.append(
801 servletMapping.substring(0, servletMapping.length() - 2));
802 value.append(actionMapping);
803
804 } else if (servletMapping.equals("/")) {
805 value.append(actionMapping);
806 }
807 if (queryString != null) {
808 value.append(queryString);
809 }
810 }
811
812 // Otherwise, assume extension mapping is in use and extension is
813 // already included in the action property
814 else {
815 if (!action.startsWith("/")) {
816 value.append("/");
817 }
818 value.append(action);
819 }
820
821 return value.toString();
822 }
823
824 /**
825 * Retrieves the value from request scope and if it isn't already an
826 * <code>ActionMessages</code>, some classes are converted to one.
827 *
828 * @param pageContext The PageContext for the current page
829 * @param paramName Key for parameter value
830 * @return ActionErrors in page context.
831 * @throws JspException
832 */
833 public ActionMessages getActionMessages(
834 PageContext pageContext,
835 String paramName)
836 throws JspException {
837
838 ActionMessages am = new ActionMessages();
839
840 Object value = pageContext.findAttribute(paramName);
841 if (value != null) {
842 try {
843 if (value instanceof String) {
844 am.add(
845 ActionMessages.GLOBAL_MESSAGE,
846 new ActionMessage((String) value));
847
848 } else if (value instanceof String[]) {
849 String keys[] = (String[]) value;
850 for (int i = 0; i < keys.length; i++) {
851 am.add(
852 ActionMessages.GLOBAL_MESSAGE,
853 new ActionMessage(keys[i]));
854 }
855
856 } else if (value instanceof ActionErrors) {
857 ActionMessages m = (ActionMessages) value;
858 am.add(m);
859
860 } else if (value instanceof ActionMessages) {
861 am = (ActionMessages) value;
862
863 } else {
864 throw new JspException(
865 messages.getMessage(
866 "actionMessages.errors",
867 value.getClass().getName()));
868 }
869
870 } catch (JspException e) {
871 throw e;
872
873 } catch (Exception e) {
874 log.warn("Unable to retieve ActionMessage for paramName : "+paramName,e);
875 }
876 }
877 return am;
878 }
879
880 /**
881 * Return the ModuleConfig object if it exists, null if otherwise.
882 * @param pageContext The page context.
883 * @return the ModuleConfig object
884 */
885 public ModuleConfig getModuleConfig(PageContext pageContext) {
886 return getModuleConfig(
887 null,
888 pageContext);
889 }
890
891 /**
892 * Return the ModuleConfig object for the given prefix if it exists, null if otherwise.
893 * @param module The module prefix
894 * @param pageContext The page context.
895 * @return the ModuleConfig object
896 */
897 public ModuleConfig getModuleConfig(String module, PageContext pageContext) {
898 return ModuleUtils.getInstance().getModuleConfig(
899 module,
900 (HttpServletRequest) pageContext.getRequest(),
901 pageContext.getServletContext());
902 }
903
904 /**
905 * Converts the scope name into its corresponding PageContext constant value.
906 * @param scopeName Can be "page", "request", "session", or "application" in any
907 * case.
908 * @return The constant representing the scope (ie. PageContext.REQUEST_SCOPE).
909 * @throws JspException if the scopeName is not a valid name.
910 */
911 public int getScope(String scopeName) throws JspException {
912 Integer scope = (Integer) scopes.get(scopeName.toLowerCase());
913
914 if (scope == null) {
915 throw new JspException(messages.getMessage("lookup.scope", scope));
916 }
917
918 return scope.intValue();
919 }
920
921 /**
922 * Look up and return current user locale, based on the specified parameters.
923 *
924 * @param pageContext The PageContext associated with this request
925 * @param locale Name of the session attribute for our user's Locale. If this is
926 * <code>null</code>, the default locale key is used for the lookup.
927 * @return current user locale
928 */
929 public Locale getUserLocale(PageContext pageContext, String locale) {
930 return RequestUtils.getUserLocale(
931 (HttpServletRequest) pageContext.getRequest(),
932 locale);
933 }
934
935 /**
936 * Returns true if the custom tags are in XHTML mode.
937 */
938 public boolean isXhtml(PageContext pageContext) {
939 String xhtml =
940 (String) pageContext.getAttribute(
941 Globals.XHTML_KEY,
942 PageContext.PAGE_SCOPE);
943
944 return "true".equalsIgnoreCase(xhtml);
945 }
946
947 /**
948 * Locate and return the specified bean, from an optionally specified
949 * scope, in the specified page context. If no such bean is found,
950 * return <code>null</code> instead. If an exception is thrown, it will
951 * have already been saved via a call to <code>saveException()</code>.
952 *
953 * @param pageContext Page context to be searched
954 * @param name Name of the bean to be retrieved
955 * @param scopeName Scope to be searched (page, request, session, application)
956 * or <code>null</code> to use <code>findAttribute()</code> instead
957 * @return JavaBean in the specified page context
958 * @exception JspException if an invalid scope name
959 * is requested
960 */
961 public Object lookup(PageContext pageContext, String name, String scopeName)
962 throws JspException {
963
964 if (scopeName == null) {
965 return pageContext.findAttribute(name);
966 }
967
968 try {
969 return pageContext.getAttribute(name, instance.getScope(scopeName));
970
971 } catch (JspException e) {
972 saveException(pageContext, e);
973 throw e;
974 }
975
976 }
977
978 /**
979 * Locate and return the specified property of the specified bean, from
980 * an optionally specified scope, in the specified page context. If an
981 * exception is thrown, it will have already been saved via a call to
982 * <code>saveException()</code>.
983 *
984 * @param pageContext Page context to be searched
985 * @param name Name of the bean to be retrieved
986 * @param property Name of the property to be retrieved, or
987 * <code>null</code> to retrieve the bean itself
988 * @param scope Scope to be searched (page, request, session, application)
989 * or <code>null</code> to use <code>findAttribute()</code> instead
990 * @return property of specified JavaBean
991 *
992 * @exception JspException if an invalid scope name
993 * is requested
994 * @exception JspException if the specified bean is not found
995 * @exception JspException if accessing this property causes an
996 * IllegalAccessException, IllegalArgumentException,
997 * InvocationTargetException, or NoSuchMethodException
998 */
999 public Object lookup(
1000 PageContext pageContext,
1001 String name,
1002 String property,
1003 String scope)
1004 throws JspException {
1005
1006 // Look up the requested bean, and return if requested
1007 Object bean = lookup(pageContext, name, scope);
1008 if (bean == null) {
1009 JspException e = null;
1010 if (scope == null) {
1011 e = new JspException(messages.getMessage("lookup.bean.any", name));
1012 } else {
1013 e =
1014 new JspException(
1015 messages.getMessage("lookup.bean", name, scope));
1016 }
1017 saveException(pageContext, e);
1018 throw e;
1019 }
1020
1021 if (property == null) {
1022 return bean;
1023 }
1024
1025 // Locate and return the specified property
1026 try {
1027 return PropertyUtils.getProperty(bean, property);
1028
1029 } catch (IllegalAccessException e) {
1030 saveException(pageContext, e);
1031 throw new JspException(
1032 messages.getMessage("lookup.access", property, name));
1033
1034 } catch (IllegalArgumentException e) {
1035 saveException(pageContext, e);
1036 throw new JspException(
1037 messages.getMessage("lookup.argument", property, name));
1038
1039 } catch (InvocationTargetException e) {
1040 Throwable t = e.getTargetException();
1041 if (t == null) {
1042 t = e;
1043 }
1044 saveException(pageContext, t);
1045 throw new JspException(
1046 messages.getMessage("lookup.target", property, name));
1047
1048 } catch (NoSuchMethodException e) {
1049 saveException(pageContext, e);
1050 throw new JspException(
1051 messages.getMessage("lookup.method", property, name));
1052 }
1053
1054 }
1055
1056 /**
1057 * Look up and return a message string, based on the specified parameters.
1058 *
1059 * @param pageContext The PageContext associated with this request
1060 * @param bundle Name of the servlet context attribute for our
1061 * message resources bundle
1062 * @param locale Name of the session attribute for our user's Locale
1063 * @param key Message key to be looked up and returned
1064 * @return message string
1065 *
1066 * @exception JspException if a lookup error occurs (will have been
1067 * saved in the request already)
1068 */
1069 public String message(
1070 PageContext pageContext,
1071 String bundle,
1072 String locale,
1073 String key)
1074 throws JspException {
1075
1076 return message(pageContext, bundle, locale, key, null);
1077
1078 }
1079
1080 /**
1081 * Look up and return a message string, based on the specified parameters.
1082 *
1083 * @param pageContext The PageContext associated with this request
1084 * @param bundle Name of the servlet context attribute for our
1085 * message resources bundle
1086 * @param locale Name of the session attribute for our user's Locale
1087 * @param key Message key to be looked up and returned
1088 * @param args Replacement parameters for this message
1089 * @return message string
1090 * @exception JspException if a lookup error occurs (will have been
1091 * saved in the request already)
1092 */
1093 public String message(
1094 PageContext pageContext,
1095 String bundle,
1096 String locale,
1097 String key,
1098 Object args[])
1099 throws JspException {
1100
1101 MessageResources resources =
1102 retrieveMessageResources(pageContext, bundle, false);
1103
1104 Locale userLocale = getUserLocale(pageContext, locale);
1105 String message = null;
1106 if (args == null) {
1107 message = resources.getMessage(userLocale, key);
1108 } else {
1109 message = resources.getMessage(userLocale, key, args);
1110 }
1111 if ((message == null) && log.isDebugEnabled()) {
1112 // log missing key to ease debugging
1113 log.debug(resources.getMessage("message.resources", key, bundle, locale));
1114 }
1115 return message;
1116 }
1117
1118 /**
1119 * <p>Return the context-relative URL that corresponds to the specified
1120 * <code>page</code> attribute value, calculated based on the
1121 * <code>pagePattern</code> property of the current module's
1122 * {@link ModuleConfig}.</p>
1123 *
1124 * @param request The servlet request we are processing
1125 * @param page The module-relative URL to be substituted in
1126 * to the <code>pagePattern</code> pattern for the current module
1127 * (<strong>MUST</strong> start with a slash)
1128 * @return context-relative URL
1129 */
1130 public String pageURL(HttpServletRequest request, String page, ModuleConfig moduleConfig) {
1131
1132 StringBuffer sb = new StringBuffer();
1133 String pagePattern = moduleConfig.getControllerConfig().getPagePattern();
1134
1135 if (pagePattern == null) {
1136 sb.append(moduleConfig.getPrefix());
1137 sb.append(page);
1138
1139 } else {
1140 boolean dollar = false;
1141 for (int i = 0; i < pagePattern.length(); i++) {
1142 char ch = pagePattern.charAt(i);
1143 if (dollar) {
1144 switch (ch) {
1145 case 'M':
1146 sb.append(moduleConfig.getPrefix());
1147 break;
1148 case 'P':
1149 sb.append(page);
1150 break;
1151 case '$':
1152 sb.append('$');
1153 break;
1154 default :
1155 ; // Silently swallow
1156 }
1157 dollar = false;
1158 continue;
1159
1160 } else if (ch == '$') {
1161 dollar = true;
1162
1163 } else {
1164 sb.append(ch);
1165 }
1166 }
1167 }
1168
1169 return sb.toString();
1170 }
1171
1172 /**
1173 * Return true if a message string for the specified message key
1174 * is present for the specified Locale.
1175 *
1176 * @param pageContext The PageContext associated with this request
1177 * @param bundle Name of the servlet context attribute for our
1178 * message resources bundle
1179 * @param locale Name of the session attribute for our user's Locale
1180 * @param key Message key to be looked up and returned
1181 * @return true if a message string for message key exists
1182 * @exception JspException if a lookup error occurs (will have been
1183 * saved in the request already)
1184 */
1185 public boolean present(
1186 PageContext pageContext,
1187 String bundle,
1188 String locale,
1189 String key)
1190 throws JspException {
1191
1192 MessageResources resources =
1193 retrieveMessageResources(pageContext, bundle, true);
1194
1195 Locale userLocale = getUserLocale(pageContext, locale);
1196
1197 return resources.isPresent(userLocale, key);
1198 }
1199
1200 /**
1201 * Returns the appropriate MessageResources object for the current module and
1202 * the given bundle.
1203 *
1204 * @param pageContext Search the context's scopes for the resources.
1205 * @param bundle The bundle name to look for. If this is <code>null</code>, the
1206 * default bundle name is used.
1207 * @return MessageResources The bundle's resources stored in some scope.
1208 * @throws JspException if the MessageResources object could not be found.
1209 */
1210 private MessageResources retrieveMessageResources(
1211 PageContext pageContext,
1212 String bundle,
1213 boolean checkPageScope)
1214 throws JspException {
1215
1216 MessageResources resources = null;
1217
1218 if (bundle == null) {
1219 bundle = Globals.MESSAGES_KEY;
1220 }
1221
1222 if (checkPageScope) {
1223 resources =
1224 (MessageResources) pageContext.getAttribute(
1225 bundle,
1226 PageContext.PAGE_SCOPE);
1227 }
1228
1229 if (resources == null) {
1230 resources =
1231 (MessageResources) pageContext.getAttribute(
1232 bundle,
1233 PageContext.REQUEST_SCOPE);
1234 }
1235
1236 if (resources == null) {
1237 ModuleConfig moduleConfig = getModuleConfig(pageContext);
1238 resources =
1239 (MessageResources) pageContext.getAttribute(
1240 bundle + moduleConfig.getPrefix(),
1241 PageContext.APPLICATION_SCOPE);
1242 }
1243
1244 if (resources == null) {
1245 resources =
1246 (MessageResources) pageContext.getAttribute(
1247 bundle,
1248 PageContext.APPLICATION_SCOPE);
1249 }
1250
1251 if (resources == null) {
1252 JspException e =
1253 new JspException(messages.getMessage("message.bundle", bundle));
1254 saveException(pageContext, e);
1255 throw e;
1256 }
1257
1258 return resources;
1259 }
1260
1261 /**
1262 * Save the specified exception as a request attribute for later use.
1263 *
1264 * @param pageContext The PageContext for the current page
1265 * @param exception The exception to be saved
1266 */
1267 public void saveException(PageContext pageContext, Throwable exception) {
1268
1269 pageContext.setAttribute(
1270 Globals.EXCEPTION_KEY,
1271 exception,
1272 PageContext.REQUEST_SCOPE);
1273
1274 }
1275
1276 /**
1277 * Write the specified text as the response to the writer associated with
1278 * this page. <strong>WARNING</strong> - If you are writing body content
1279 * from the <code>doAfterBody()</code> method of a custom tag class that
1280 * implements <code>BodyTag</code>, you should be calling
1281 * <code>writePrevious()</code> instead.
1282 *
1283 * @param pageContext The PageContext object for this page
1284 * @param text The text to be written
1285 *
1286 * @exception JspException if an input/output error occurs (already saved)
1287 */
1288 public void write(PageContext pageContext, String text)
1289 throws JspException {
1290
1291 JspWriter writer = pageContext.getOut();
1292
1293 try {
1294 writer.print(text);
1295
1296 } catch (IOException e) {
1297 TagUtils.getInstance().saveException(pageContext, e);
1298 throw new JspException
1299 (messages.getMessage("write.io", e.toString()));
1300 }
1301
1302 }
1303
1304
1305 /**
1306 * Write the specified text as the response to the writer associated with
1307 * the body content for the tag within which we are currently nested.
1308 *
1309 * @param pageContext The PageContext object for this page
1310 * @param text The text to be written
1311 *
1312 * @exception JspException if an input/output error occurs (already saved)
1313 */
1314 public void writePrevious(PageContext pageContext, String text)
1315 throws JspException {
1316
1317 JspWriter writer = pageContext.getOut();
1318 if (writer instanceof BodyContent) {
1319 writer = ((BodyContent) writer).getEnclosingWriter();
1320 }
1321
1322 try {
1323 writer.print(text);
1324
1325 } catch (IOException e) {
1326 TagUtils.getInstance().saveException(pageContext, e);
1327 throw new JspException
1328 (messages.getMessage("write.io", e.toString()));
1329 }
1330
1331 }
1332
1333}