001/* 002 * Copyright 2002-2018 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; 018 019import java.beans.PropertyEditor; 020 021import javax.servlet.jsp.JspTagException; 022import javax.servlet.jsp.PageContext; 023 024import org.springframework.lang.Nullable; 025import org.springframework.util.Assert; 026import org.springframework.validation.Errors; 027import org.springframework.web.servlet.support.BindStatus; 028 029/** 030 * The {@code <bind>} tag supports evaluation of binding errors for a certain 031 * bean or bean property. Exposes a "status" variable of type 032 * {@link org.springframework.web.servlet.support.BindStatus}, 033 * to both Java expressions and JSP EL expressions. 034 * 035 * <p>Can be used to bind to any bean or bean property in the model. 036 * The specified path determines whether the tag exposes the status of the 037 * bean itself (showing object-level errors), a specific bean property 038 * (showing field errors), or a matching set of bean properties 039 * (showing all corresponding field errors). 040 * 041 * <p>The {@link org.springframework.validation.Errors} object that has 042 * been bound using this tag is exposed to collaborating tags, as well 043 * as the bean property that this errors object applies to. Nested tags 044 * such as the {@link TransformTag} can access those exposed properties. 045 * 046 * <table> 047 * <caption>Attribute Summary</caption> 048 * <thead> 049 * <tr> 050 * <th class="colFirst">Attribute</th> 051 * <th class="colOne">Required?</th> 052 * <th class="colOne">Runtime Expression?</th> 053 * <th class="colLast">Description</th> 054 * </tr> 055 * </thead> 056 * <tbody> 057 * <tr class="altColor"> 058 * <td><p>htmlEscape</p></td> 059 * <td><p>false</p></td> 060 * <td><p>true</p></td> 061 * <td><p>Set HTML escaping for this tag, as boolean value. Overrides the default 062 * HTML escaping setting for the current page.</p></td> 063 * </tr> 064 * <tr class="rowColor"> 065 * <td><p>ignoreNestedPath</p></td> 066 * <td><p>false</p></td> 067 * <td><p>true</p></td> 068 * <td><p>Set whether to ignore a nested path, if any. 069 * Default is to not ignore.</p></td> 070 * </tr> 071 * <tr class="altColor"> 072 * <td><p>path</p></td> 073 * <td><p>true</p></td> 074 * <td><p>true</p></td> 075 * <td><p>The path to the bean or bean property to bind status information for. 076 * For instance account.name, company.address.zipCode or just employee. The status 077 * object will exported to the page scope, specifically for this bean or bean 078 * property</p></td> 079 * </tr> 080 * </tbody> 081 * </table> 082 * 083 * @author Rod Johnson 084 * @author Juergen Hoeller 085 * @see #setPath 086 */ 087@SuppressWarnings("serial") 088public class BindTag extends HtmlEscapingAwareTag implements EditorAwareTag { 089 090 /** 091 * Name of the exposed variable within the scope of this tag: "status". 092 */ 093 public static final String STATUS_VARIABLE_NAME = "status"; 094 095 096 private String path = ""; 097 098 private boolean ignoreNestedPath = false; 099 100 @Nullable 101 private BindStatus status; 102 103 @Nullable 104 private Object previousPageStatus; 105 106 @Nullable 107 private Object previousRequestStatus; 108 109 110 /** 111 * Set the path that this tag should apply. Can be a bean (e.g. "person") 112 * to get global errors, or a bean property (e.g. "person.name") to get 113 * field errors (also supporting nested fields and "person.na*" mappings). 114 * "person.*" will return all errors for the specified bean, both global 115 * and field errors. 116 * @see org.springframework.validation.Errors#getGlobalErrors 117 * @see org.springframework.validation.Errors#getFieldErrors 118 */ 119 public void setPath(String path) { 120 this.path = path; 121 } 122 123 /** 124 * Return the path that this tag applies to. 125 */ 126 public String getPath() { 127 return this.path; 128 } 129 130 /** 131 * Set whether to ignore a nested path, if any. 132 * Default is to not ignore. 133 */ 134 public void setIgnoreNestedPath(boolean ignoreNestedPath) { 135 this.ignoreNestedPath = ignoreNestedPath; 136 } 137 138 /** 139 * Return whether to ignore a nested path, if any. 140 */ 141 public boolean isIgnoreNestedPath() { 142 return this.ignoreNestedPath; 143 } 144 145 146 @Override 147 protected final int doStartTagInternal() throws Exception { 148 String resolvedPath = getPath(); 149 if (!isIgnoreNestedPath()) { 150 String nestedPath = (String) this.pageContext.getAttribute( 151 NestedPathTag.NESTED_PATH_VARIABLE_NAME, PageContext.REQUEST_SCOPE); 152 // only prepend if not already an absolute path 153 if (nestedPath != null && !resolvedPath.startsWith(nestedPath) && 154 !resolvedPath.equals(nestedPath.substring(0, nestedPath.length() - 1))) { 155 resolvedPath = nestedPath + resolvedPath; 156 } 157 } 158 159 try { 160 this.status = new BindStatus(getRequestContext(), resolvedPath, isHtmlEscape()); 161 } 162 catch (IllegalStateException ex) { 163 throw new JspTagException(ex.getMessage()); 164 } 165 166 // Save previous status values, for re-exposure at the end of this tag. 167 this.previousPageStatus = this.pageContext.getAttribute(STATUS_VARIABLE_NAME, PageContext.PAGE_SCOPE); 168 this.previousRequestStatus = this.pageContext.getAttribute(STATUS_VARIABLE_NAME, PageContext.REQUEST_SCOPE); 169 170 // Expose this tag's status object as PageContext attribute, 171 // making it available for JSP EL. 172 this.pageContext.removeAttribute(STATUS_VARIABLE_NAME, PageContext.PAGE_SCOPE); 173 this.pageContext.setAttribute(STATUS_VARIABLE_NAME, this.status, PageContext.REQUEST_SCOPE); 174 175 return EVAL_BODY_INCLUDE; 176 } 177 178 @Override 179 public int doEndTag() { 180 // Reset previous status values. 181 if (this.previousPageStatus != null) { 182 this.pageContext.setAttribute(STATUS_VARIABLE_NAME, this.previousPageStatus, PageContext.PAGE_SCOPE); 183 } 184 if (this.previousRequestStatus != null) { 185 this.pageContext.setAttribute(STATUS_VARIABLE_NAME, this.previousRequestStatus, PageContext.REQUEST_SCOPE); 186 } 187 else { 188 this.pageContext.removeAttribute(STATUS_VARIABLE_NAME, PageContext.REQUEST_SCOPE); 189 } 190 return EVAL_PAGE; 191 } 192 193 194 /** 195 * Return the current BindStatus. 196 */ 197 private BindStatus getStatus() { 198 Assert.state(this.status != null, "No current BindStatus"); 199 return this.status; 200 } 201 202 /** 203 * Retrieve the property that this tag is currently bound to, 204 * or {@code null} if bound to an object rather than a specific property. 205 * Intended for cooperating nesting tags. 206 * @return the property that this tag is currently bound to, 207 * or {@code null} if none 208 */ 209 @Nullable 210 public final String getProperty() { 211 return getStatus().getExpression(); 212 } 213 214 /** 215 * Retrieve the Errors instance that this tag is currently bound to. 216 * Intended for cooperating nesting tags. 217 * @return the current Errors instance, or {@code null} if none 218 */ 219 @Nullable 220 public final Errors getErrors() { 221 return getStatus().getErrors(); 222 } 223 224 @Override 225 @Nullable 226 public final PropertyEditor getEditor() { 227 return getStatus().getEditor(); 228 } 229 230 231 @Override 232 public void doFinally() { 233 super.doFinally(); 234 this.status = null; 235 this.previousPageStatus = null; 236 this.previousRequestStatus = null; 237 } 238 239}