001/* 002 * Copyright 2002-2012 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 * https://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 017package org.springframework.web.servlet.tags.form; 018 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.List; 022import javax.servlet.jsp.JspException; 023import javax.servlet.jsp.PageContext; 024import javax.servlet.jsp.tagext.BodyTag; 025 026import org.springframework.util.Assert; 027import org.springframework.util.ObjectUtils; 028import org.springframework.util.StringUtils; 029 030/** 031 * Form tag for displaying errors for a particular field or object. 032 * 033 * <p>This tag supports three main usage patterns: 034 * 035 * <ol> 036 * <li>Field only - set '{@code path}' to the field name (or path)</li> 037 * <li>Object errors only - omit '{@code path}'</li> 038 * <li>All errors - set '{@code path}' to '{@code *}'</li> 039 * </ol> 040 * 041 * @author Rob Harrop 042 * @author Juergen Hoeller 043 * @author Rick Evans 044 * @since 2.0 045 */ 046@SuppressWarnings("serial") 047public class ErrorsTag extends AbstractHtmlElementBodyTag implements BodyTag { 048 049 /** 050 * The key under which this tag exposes error messages in 051 * the {@link PageContext#PAGE_SCOPE page context scope}. 052 */ 053 public static final String MESSAGES_ATTRIBUTE = "messages"; 054 055 /** 056 * The HTML '{@code span}' tag. 057 */ 058 public static final String SPAN_TAG = "span"; 059 060 061 private String element = SPAN_TAG; 062 063 private String delimiter = "<br/>"; 064 065 /** 066 * Stores any value that existed in the 'errors messages' before the tag was started. 067 */ 068 private Object oldMessages; 069 070 private boolean errorMessagesWereExposed; 071 072 073 /** 074 * Set the HTML element must be used to render the error messages. 075 * <p>Defaults to an HTML '{@code <span/>}' tag. 076 */ 077 public void setElement(String element) { 078 Assert.hasText(element, "'element' cannot be null or blank"); 079 this.element = element; 080 } 081 082 /** 083 * Get the HTML element must be used to render the error messages. 084 */ 085 public String getElement() { 086 return this.element; 087 } 088 089 /** 090 * Set the delimiter to be used between error messages. 091 * <p>Defaults to an HTML '{@code <br/>}' tag. 092 */ 093 public void setDelimiter(String delimiter) { 094 this.delimiter = delimiter; 095 } 096 097 /** 098 * Return the delimiter to be used between error messages. 099 */ 100 public String getDelimiter() { 101 return this.delimiter; 102 } 103 104 105 /** 106 * Get the value for the HTML '{@code id}' attribute. 107 * <p>Appends '{@code .errors}' to the value returned by {@link #getPropertyPath()} 108 * or to the model attribute name if the {@code <form:errors/>} tag's 109 * '{@code path}' attribute has been omitted. 110 * @return the value for the HTML '{@code id}' attribute 111 * @see #getPropertyPath() 112 */ 113 @Override 114 protected String autogenerateId() throws JspException { 115 String path = getPropertyPath(); 116 if ("".equals(path) || "*".equals(path)) { 117 path = (String) this.pageContext.getAttribute( 118 FormTag.MODEL_ATTRIBUTE_VARIABLE_NAME, PageContext.REQUEST_SCOPE); 119 } 120 return StringUtils.deleteAny(path, "[]") + ".errors"; 121 } 122 123 /** 124 * Get the value for the HTML '{@code name}' attribute. 125 * <p>Simply returns {@code null} because the '{@code name}' attribute 126 * is not a validate attribute for the '{@code span}' element. 127 */ 128 @Override 129 protected String getName() throws JspException { 130 return null; 131 } 132 133 /** 134 * Should rendering of this tag proceed at all? 135 * <p>Only renders output when there are errors for the configured {@link #setPath path}. 136 * @return {@code true} only when there are errors for the configured {@link #setPath path} 137 */ 138 @Override 139 protected boolean shouldRender() throws JspException { 140 try { 141 return getBindStatus().isError(); 142 } 143 catch (IllegalStateException ex) { 144 // Neither BindingResult nor target object available. 145 return false; 146 } 147 } 148 149 @Override 150 protected void renderDefaultContent(TagWriter tagWriter) throws JspException { 151 tagWriter.startTag(getElement()); 152 writeDefaultAttributes(tagWriter); 153 String delimiter = ObjectUtils.getDisplayString(evaluate("delimiter", getDelimiter())); 154 String[] errorMessages = getBindStatus().getErrorMessages(); 155 for (int i = 0; i < errorMessages.length; i++) { 156 String errorMessage = errorMessages[i]; 157 if (i > 0) { 158 tagWriter.appendValue(delimiter); 159 } 160 tagWriter.appendValue(getDisplayString(errorMessage)); 161 } 162 tagWriter.endTag(); 163 } 164 165 /** 166 * Exposes any bind status error messages under {@link #MESSAGES_ATTRIBUTE this key} 167 * in the {@link PageContext#PAGE_SCOPE}. 168 * <p>Only called if {@link #shouldRender()} returns {@code true}. 169 * @see #removeAttributes() 170 */ 171 @Override 172 protected void exposeAttributes() throws JspException { 173 List<String> errorMessages = new ArrayList<String>(); 174 errorMessages.addAll(Arrays.asList(getBindStatus().getErrorMessages())); 175 this.oldMessages = this.pageContext.getAttribute(MESSAGES_ATTRIBUTE, PageContext.PAGE_SCOPE); 176 this.pageContext.setAttribute(MESSAGES_ATTRIBUTE, errorMessages, PageContext.PAGE_SCOPE); 177 this.errorMessagesWereExposed = true; 178 } 179 180 /** 181 * Removes any bind status error messages that were previously stored under 182 * {@link #MESSAGES_ATTRIBUTE this key} in the {@link PageContext#PAGE_SCOPE}. 183 * @see #exposeAttributes() 184 */ 185 @Override 186 protected void removeAttributes() { 187 if (this.errorMessagesWereExposed) { 188 if (this.oldMessages != null) { 189 this.pageContext.setAttribute(MESSAGES_ATTRIBUTE, this.oldMessages, PageContext.PAGE_SCOPE); 190 this.oldMessages = null; 191 } 192 else { 193 this.pageContext.removeAttribute(MESSAGES_ATTRIBUTE, PageContext.PAGE_SCOPE); 194 } 195 } 196 } 197 198}