Java tutorial
/** * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. * * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS * graphic logo is a trademark of OpenMRS Inc. */ package org.openmrs.web.taglib; import java.io.IOException; import java.util.Collection; import javax.servlet.jsp.JspException; import javax.servlet.jsp.JspTagException; import org.openmrs.api.context.Context; import org.openmrs.util.LocaleUtility; import org.openmrs.util.OpenmrsUtil; import org.openmrs.web.taglib.behavior.TagMessageWriterBehavior; import org.springframework.context.MessageSource; import org.springframework.context.MessageSourceResolvable; import org.springframework.context.NoSuchMessageException; import org.springframework.util.StringUtils; import org.springframework.web.servlet.tags.MessageTag; import org.springframework.web.util.HtmlUtils; import org.springframework.web.util.JavaScriptUtils; import org.springframework.web.util.TagUtils; /** * Custom JSP tag to look up a message in the scope of the page. This is based on the Spring message tag. It extends functionality * of Spring's message tag by adding the ability to place text inside the tag body and specifying the locale of the text. Any text, * placed between start and end tags will be treated as the default message within the locale specified in within the locale * attribute (or the system default locale if the locale attribute is not specified). * <p> * As spring:message tag, this also retrieves the message from bundle with the given code, but (in addition) this tag uses its body * text when the code cannot be resolved. If the body value also is not specified, the value of the text attribute is used. If there * is no message in the bundle and no text attribute value or tag body text, then the message code is displayed, if it is set; * otherwise null. If both text attribute and body text are present (shouldn't happen, but could by mistake), then this tag uses the * body text before checking the text attribute. With this tag user can define default text for a locale other than the system * default by using locale attribute. If locale attribute is not specified, then the system default locale (e.g., "en") will be * used. HTML escaping is also supported by this tag. * </p> * <p> * This tag also supports customization of messages writing behavior. To change its default behavior, set the TagWriterBehavior * property with a custom implementation of the {@link TagMessageWriterBehavior} interface. * </p> * <p> * For example, if you put the following: * </p> * <code> * <openmrs:message code="wrong.code" >Some text </openmrs:message> * </code> * <p> * and there is no message that can be resolved by given code, then output will be "Some text". You may also specify the fallback in * spring-ish style using text attribute as follows: * </p> * <code> * <openmrs:message code="wrong.code" text="Some other text" /> * </code> * <p> * Using locale attribute of tag you can specify the locale of fallback text provided by message tag. You could write the tag like: * </p> * <openmrs:message code="foo.greeting" locale="fr">Bonjour</openmrs:message> * <p> * meaning that the message supplied with the tag is in the "fr" locale. If the user is viewing in English, then the message within * the English message bundle with the code foo.greeting would be displayed. * </p> */ public class OpenmrsMessageTag extends OpenmrsHtmlEscapingAwareTag { /** */ private static final long serialVersionUID = 1L; /** Default separator for splitting an arguments String: a comma (",") */ public static final String DEFAULT_ARGUMENT_SEPARATOR = ","; /** Default behavior used for writing resolved messages into output */ public static final TagMessageWriterBehavior DEFAULT_WRITER_BEHAVIOUR = new DefaultTagWriterBehavior(); /** Tag writing behavior instance used for customization of resolved messages rendering */ public static TagMessageWriterBehavior tagWriterBehavior = DEFAULT_WRITER_BEHAVIOUR; private Object message; private String code; private Object arguments; private String argumentSeparator = DEFAULT_ARGUMENT_SEPARATOR; private String text; private String var; private String scope = TagUtils.SCOPE_PAGE; /** Specifies the locale for the tag's message. Default to system default locale. */ private String locale = LocaleUtility.getDefaultLocale().getLanguage(); private boolean javaScriptEscape = false; /** * @see MessageTag#setMessage(MessageSourceResolvable) */ public void setMessage(Object message) { this.message = message; } /** * Set the message code for this tag. */ public void setCode(String code) { this.code = code; } /** * @see MessageTag#setArguments(Object) */ public void setArguments(Object arguments) { this.arguments = arguments; } /** * @see MessageTag#setArgumentSeparator(String) */ public void setArgumentSeparator(String argumentSeparator) { this.argumentSeparator = argumentSeparator; } /** * Set the message text for this tag. */ public void setText(String text) { this.text = text; } /** * @see MessageTag#setVar(String) */ public void setVar(String var) { this.var = var; } /** * @see MessageTag#setScope(String) */ public void setScope(String scope) { this.scope = scope; } /** * Sets the locale of the supplied message. If no text passed in or given text can not be recognized as locale, then default * value of locale attribute of this tag will not be changed * * @param locale * the locale string to set */ public void setLocale(String locale) { if (StringUtils.hasText(locale) && LocaleUtility.fromSpecification(locale) != null) { this.locale = locale; } } /** * Set JavaScript escaping for this tag, as boolean value. Default is "false". */ public void setJavaScriptEscape(String javaScriptEscape) throws JspException { this.javaScriptEscape = Boolean.valueOf(javaScriptEscape); } /** * @see MessageTag#doStartTagInternal() * @should evaluate specified message resolvable * @should resolve message by code * @should resolve message in locale that different from default * @should return code if no message resolved * @should use body content as fallback if no message resolved * @should use text attribute as fallback if no message resolved * @should use body content in prior to text attribute as fallback if no message resolved * @should ignore fallbacks if tag locale differs from context locale */ @Override protected int doEndTagInternal() throws JspException, IOException { try { // Resolve the unescaped message. String msg = resolveMessage(); // HTML and/or JavaScript escape, if demanded. msg = isHtmlEscape() ? HtmlUtils.htmlEscape(msg) : msg; msg = this.javaScriptEscape ? JavaScriptUtils.javaScriptEscape(msg) : msg; // Expose as variable, if demanded, else write to the page. String resolvedVar = this.var; if (resolvedVar != null) { pageContext.setAttribute(resolvedVar, msg, TagUtils.getScope(this.scope)); } else { writeMessage(msg); } return EVAL_PAGE; } catch (NoSuchMessageException ex) { throw new JspTagException(getNoSuchMessageExceptionDescription(ex)); } } /** * Resolve the specified message or code or text or tag body into a concrete message string. The returned message string should * be unescaped. */ protected String resolveMessage() throws JspException { MessageSource messageSource = getMessageSource(); if (messageSource == null) { throw new JspTagException("No corresponding MessageSource to resolve message with found"); } // first, evaluate the specified MessageSourceResolvable, if any MessageSourceResolvable resolvedMessage = null; if (this.message instanceof MessageSourceResolvable) { resolvedMessage = (MessageSourceResolvable) this.message; } else if (this.message != null) { String expr = this.message.toString(); throw new JspException("Attribute value \"" + expr + "\" is neither a JSP EL expression nor " + "assignable to result class [" + MessageSourceResolvable.class.getName() + "]"); } if (resolvedMessage != null) { // we have a given MessageSourceResolvable. return messageSource.getMessage(resolvedMessage, getRequestContext().getLocale()); } String resolvedCode = this.code; String bodyText = null; String resolvedText = null; // if locale specified with tag attribute is the same as context locale if (OpenmrsUtil.nullSafeEquals(this.locale, Context.getLocale().getLanguage())) { // we need to evaluate fallback values in this case resolvedText = this.text; if (getBodyContent() != null) { bodyText = getBodyContent().getString(); } } // by default message code is used as fallback String message = resolvedCode; if (resolvedCode != null || resolvedText != null || bodyText != null) { // we have either a code or default text or body that we need to resolve. Object[] argumentsArray = resolveArguments(this.arguments); if (bodyText != null) { // we have a fallback body text to consider. message = messageSource.getMessage(resolvedCode, argumentsArray, bodyText, getRequestContext().getLocale()); } else if (resolvedText != null) { // we have a fallback value of text attribute to consider. message = messageSource.getMessage(resolvedCode, argumentsArray, resolvedText, getRequestContext().getLocale()); } else { // we have no fallback text to consider. try { message = messageSource.getMessage(resolvedCode, argumentsArray, getRequestContext().getLocale()); } catch (NoSuchMessageException e) { // do nothing, use resolved code as fallback } } } // all we have is a specified literal text. return message; } /** * @see MessageTag#resolveArguments(Object) */ protected Object[] resolveArguments(Object arguments) throws JspException { if (arguments instanceof String) { return StringUtils.delimitedListToStringArray((String) arguments, this.argumentSeparator); } else if (arguments instanceof Object[]) { return (Object[]) arguments; } else if (arguments instanceof Collection) { return ((Collection<?>) arguments).toArray(); } else if (arguments != null) { // Assume a single argument object. return new Object[] { arguments }; } else { return null; } } /** * Writes the message to the page. Before actual writing occurs, it calls method on static instance of tag writer behavior to * customize the message which will be written. * * @param message * the message to write * @throws IOException * if writing error occurs */ protected void writeMessage(String message) throws IOException { pageContext.getOut().write(tagWriterBehavior.renderMessage(String.valueOf(message), code, locale, text)); } /** * Use the current RequestContext's application context as MessageSource. */ protected MessageSource getMessageSource() { return getRequestContext().getMessageSource(); } /** * Return default exception message. */ protected String getNoSuchMessageExceptionDescription(NoSuchMessageException ex) { return ex.getMessage(); } /** * Sets tag writer behavior to customize rendering of resolved messages * * @param tagWriterBehavior * the tagWriterBehavior to set */ public static void setTagWriterBehavior(TagMessageWriterBehavior tagWriterBehavior) { OpenmrsMessageTag.tagWriterBehavior = tagWriterBehavior; } /** * Very simple implementation of {@link TagMessageWriterBehavior} interface that is used by this class by default. It actually * does not customize passed in message during output rendering. */ static final class DefaultTagWriterBehavior implements TagMessageWriterBehavior { /** * Hidden constructor. */ private DefaultTagWriterBehavior() { } /** * Just returns the resolved message message that is originally passed in. * * @see org.openmrs.web.taglib.behavior.TagMessageWriterBehavior#renderMessage(java.lang.String) */ @Override public String renderMessage(String resolvedText, String code, String locale, String fallbackText) { return resolvedText; } } }