001: /*
002: * Copyright 2002-2006 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.web.servlet.tags;
018:
019: import java.io.IOException;
020: import java.util.Collection;
021:
022: import javax.servlet.jsp.JspException;
023: import javax.servlet.jsp.JspTagException;
024:
025: import org.springframework.context.MessageSource;
026: import org.springframework.context.MessageSourceResolvable;
027: import org.springframework.context.NoSuchMessageException;
028: import org.springframework.util.ObjectUtils;
029: import org.springframework.util.StringUtils;
030: import org.springframework.web.util.ExpressionEvaluationUtils;
031: import org.springframework.web.util.HtmlUtils;
032: import org.springframework.web.util.JavaScriptUtils;
033: import org.springframework.web.util.TagUtils;
034:
035: /**
036: * Custom JSP tag to look up a message in the scope of this page.
037: * Messages are looked up using the ApplicationContext, and thus should
038: * support internationalization.
039: *
040: * <p>Regards a HTML escaping setting, either on this tag instance,
041: * the page level, or the web.xml level. Can also apply JavaScript escaping.
042: *
043: * <p>If "code" isn't set or cannot be resolved, "text" will be used as default
044: * message. Thus, this tag can also be used for HTML escaping of any texts.
045: *
046: * @author Rod Johnson
047: * @author Juergen Hoeller
048: * @see #setCode
049: * @see #setText
050: * @see #setHtmlEscape
051: * @see #setJavaScriptEscape
052: * @see HtmlEscapeTag#setDefaultHtmlEscape
053: * @see org.springframework.web.util.WebUtils#HTML_ESCAPE_CONTEXT_PARAM
054: */
055: public class MessageTag extends HtmlEscapingAwareTag {
056:
057: /**
058: * Default separator for splitting an arguments String: a comma (",")
059: */
060: public static final String DEFAULT_ARGUMENT_SEPARATOR = ",";
061:
062: private Object message;
063:
064: private String code;
065:
066: private Object arguments;
067:
068: private String argumentSeparator = DEFAULT_ARGUMENT_SEPARATOR;
069:
070: private String text;
071:
072: private String var;
073:
074: private String scope = TagUtils.SCOPE_PAGE;
075:
076: private boolean javaScriptEscape = false;
077:
078: /**
079: * Set the MessageSourceResolvable for this tag.
080: * Accepts a direct MessageSourceResolvable instance as well as a JSP
081: * expression language String that points to a MessageSourceResolvable.
082: * <p>If a MessageSourceResolvable is specified, it effectively overrides
083: * any code, arguments or text specified on this tag.
084: */
085: public void setMessage(Object message) {
086: this .message = message;
087: }
088:
089: /**
090: * Set the message code for this tag.
091: */
092: public void setCode(String code) {
093: this .code = code;
094: }
095:
096: /**
097: * Set optional message arguments for this tag, as a comma-delimited
098: * String (each String argument can contain JSP EL), an Object array
099: * (used as argument array), or a single Object (used as single argument).
100: */
101: public void setArguments(Object arguments) {
102: this .arguments = arguments;
103: }
104:
105: /**
106: * Set the separator to use for splitting an arguments String.
107: * Default is a comma (",");
108: * @see #setArguments
109: */
110: public void setArgumentSeparator(String argumentSeparator) {
111: this .argumentSeparator = argumentSeparator;
112: }
113:
114: /**
115: * Set the message text for this tag.
116: */
117: public void setText(String text) {
118: this .text = text;
119: }
120:
121: /**
122: * Set PageContext attribute name under which to expose
123: * a variable that contains the resolved message.
124: * @see #setScope
125: * @see javax.servlet.jsp.PageContext#setAttribute
126: */
127: public void setVar(String var) {
128: this .var = var;
129: }
130:
131: /**
132: * Set the scope to export the variable to.
133: * Default is SCOPE_PAGE ("page").
134: * @see #setVar
135: * @see org.springframework.web.util.TagUtils#SCOPE_PAGE
136: * @see javax.servlet.jsp.PageContext#setAttribute
137: */
138: public void setScope(String scope) {
139: this .scope = scope;
140: }
141:
142: /**
143: * Set JavaScript escaping for this tag, as boolean value.
144: * Default is "false".
145: */
146: public void setJavaScriptEscape(String javaScriptEscape)
147: throws JspException {
148: this .javaScriptEscape = ExpressionEvaluationUtils
149: .evaluateBoolean("javaScriptEscape", javaScriptEscape,
150: pageContext);
151: }
152:
153: /**
154: * Resolves the message, escapes it if demanded,
155: * and writes it to the page (or exposes it as variable).
156: * @see #resolveMessage()
157: * @see org.springframework.web.util.HtmlUtils#htmlEscape(String)
158: * @see org.springframework.web.util.JavaScriptUtils#javaScriptEscape(String)
159: * @see #writeMessage(String)
160: */
161: protected final int doStartTagInternal() throws JspException,
162: IOException {
163: try {
164: // Resolve the unescaped message.
165: String msg = resolveMessage();
166:
167: // HTML and/or JavaScript escape, if demanded.
168: msg = isHtmlEscape() ? HtmlUtils.htmlEscape(msg) : msg;
169: msg = this .javaScriptEscape ? JavaScriptUtils
170: .javaScriptEscape(msg) : msg;
171:
172: // Expose as variable, if demanded, else write to the page.
173: String resolvedVar = ExpressionEvaluationUtils
174: .evaluateString("var", this .var, pageContext);
175: if (resolvedVar != null) {
176: String resolvedScope = ExpressionEvaluationUtils
177: .evaluateString("scope", this .scope,
178: pageContext);
179: pageContext.setAttribute(resolvedVar, msg, TagUtils
180: .getScope(resolvedScope));
181: } else {
182: writeMessage(msg);
183: }
184:
185: return EVAL_BODY_INCLUDE;
186: } catch (NoSuchMessageException ex) {
187: throw new JspTagException(
188: getNoSuchMessageExceptionDescription(ex));
189: }
190: }
191:
192: /**
193: * Resolve the specified message into a concrete message String.
194: * The returned message String should be unescaped.
195: */
196: protected String resolveMessage() throws JspException,
197: NoSuchMessageException {
198: MessageSource messageSource = getMessageSource();
199: if (messageSource == null) {
200: throw new JspTagException(
201: "No corresponding MessageSource found");
202: }
203:
204: // Evaluate the specified MessageSourceResolvable, if any.
205: MessageSourceResolvable resolvedMessage = null;
206: if (this .message instanceof MessageSourceResolvable) {
207: resolvedMessage = (MessageSourceResolvable) this .message;
208: } else if (this .message != null) {
209: String expr = this .message.toString();
210: resolvedMessage = (MessageSourceResolvable) ExpressionEvaluationUtils
211: .evaluate("message", expr,
212: MessageSourceResolvable.class, pageContext);
213: }
214:
215: if (resolvedMessage != null) {
216: // We have a given MessageSourceResolvable.
217: return messageSource.getMessage(resolvedMessage,
218: getRequestContext().getLocale());
219: }
220:
221: String resolvedCode = ExpressionEvaluationUtils.evaluateString(
222: "code", this .code, pageContext);
223: String resolvedText = ExpressionEvaluationUtils.evaluateString(
224: "text", this .text, pageContext);
225:
226: if (resolvedCode != null) {
227: // We have a code that we need to resolve.
228: Object[] argumentsArray = resolveArguments(this .arguments);
229: if (resolvedText != null) {
230: // We have a fallback text to consider.
231: return messageSource.getMessage(resolvedCode,
232: argumentsArray, resolvedText,
233: getRequestContext().getLocale());
234: } else {
235: // We have no fallback text to consider.
236: return messageSource
237: .getMessage(resolvedCode, argumentsArray,
238: getRequestContext().getLocale());
239: }
240: }
241:
242: // All we have is a specified literal text.
243: return resolvedText;
244: }
245:
246: /**
247: * Resolve the given arguments Object into an arguments array.
248: * @param arguments the specified arguments Object
249: * @return the resolved arguments as array
250: * @throws JspException if argument conversion failed
251: * @see #setArguments
252: */
253: protected Object[] resolveArguments(Object arguments)
254: throws JspException {
255: if (arguments instanceof String) {
256: String[] stringArray = StringUtils
257: .delimitedListToStringArray((String) arguments,
258: this .argumentSeparator);
259: if (stringArray.length == 1) {
260: Object argument = ExpressionEvaluationUtils.evaluate(
261: "argument", stringArray[0], pageContext);
262: if (argument != null && argument.getClass().isArray()) {
263: return ObjectUtils.toObjectArray(argument);
264: } else {
265: return new Object[] { argument };
266: }
267: } else {
268: Object[] argumentsArray = new Object[stringArray.length];
269: for (int i = 0; i < stringArray.length; i++) {
270: argumentsArray[i] = ExpressionEvaluationUtils
271: .evaluate("argument[" + i + "]",
272: stringArray[i], pageContext);
273: }
274: return argumentsArray;
275: }
276: } else if (arguments instanceof Object[]) {
277: return (Object[]) arguments;
278: } else if (arguments instanceof Collection) {
279: return ((Collection) arguments).toArray();
280: } else if (arguments != null) {
281: // Assume a single argument object.
282: return new Object[] { arguments };
283: } else {
284: return null;
285: }
286: }
287:
288: /**
289: * Write the message to the page.
290: * <p>Can be overridden in subclasses, e.g. for testing purposes.
291: * @param msg the message to write
292: * @throws IOException if writing failed
293: */
294: protected void writeMessage(String msg) throws IOException {
295: pageContext.getOut().write(String.valueOf(msg));
296: }
297:
298: /**
299: * Use the application context itself for default message resolution.
300: */
301: protected MessageSource getMessageSource() {
302: return getRequestContext().getWebApplicationContext();
303: }
304:
305: /**
306: * Return default exception message.
307: */
308: protected String getNoSuchMessageExceptionDescription(
309: NoSuchMessageException ex) {
310: return ex.getMessage();
311: }
312:
313: }
|