001/* 002 * Copyright 2002-2013 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; 020import javax.servlet.jsp.JspTagException; 021import javax.servlet.jsp.PageContext; 022 023import org.springframework.validation.Errors; 024import org.springframework.web.servlet.support.BindStatus; 025 026/** 027 * Bind tag, supporting evaluation of binding errors for a certain 028 * bean or bean property. Exposes a "status" variable of type 029 * {@link org.springframework.web.servlet.support.BindStatus}, 030 * to both Java expressions and JSP EL expressions. 031 * 032 * <p>Can be used to bind to any bean or bean property in the model. 033 * The specified path determines whether the tag exposes the status of the 034 * bean itself (showing object-level errors), a specific bean property 035 * (showing field errors), or a matching set of bean properties 036 * (showing all corresponding field errors). 037 * 038 * <p>The {@link org.springframework.validation.Errors} object that has 039 * been bound using this tag is exposed to collaborating tags, as well 040 * as the bean property that this errors object applies to. Nested tags 041 * such as the {@link TransformTag} can access those exposed properties. 042 * 043 * @author Rod Johnson 044 * @author Juergen Hoeller 045 * @see #setPath 046 */ 047@SuppressWarnings("serial") 048public class BindTag extends HtmlEscapingAwareTag implements EditorAwareTag { 049 050 /** 051 * Name of the exposed variable within the scope of this tag: "status". 052 */ 053 public static final String STATUS_VARIABLE_NAME = "status"; 054 055 056 private String path; 057 058 private boolean ignoreNestedPath = false; 059 060 private BindStatus status; 061 062 private Object previousPageStatus; 063 064 private Object previousRequestStatus; 065 066 067 /** 068 * Set the path that this tag should apply. Can be a bean (e.g. "person") 069 * to get global errors, or a bean property (e.g. "person.name") to get 070 * field errors (also supporting nested fields and "person.na*" mappings). 071 * "person.*" will return all errors for the specified bean, both global 072 * and field errors. 073 * @see org.springframework.validation.Errors#getGlobalErrors 074 * @see org.springframework.validation.Errors#getFieldErrors 075 */ 076 public void setPath(String path) { 077 this.path = path; 078 } 079 080 /** 081 * Return the path that this tag applies to. 082 */ 083 public String getPath() { 084 return this.path; 085 } 086 087 /** 088 * Set whether to ignore a nested path, if any. 089 * Default is to not ignore. 090 */ 091 public void setIgnoreNestedPath(boolean ignoreNestedPath) { 092 this.ignoreNestedPath = ignoreNestedPath; 093 } 094 095 /** 096 * Return whether to ignore a nested path, if any. 097 */ 098 public boolean isIgnoreNestedPath() { 099 return this.ignoreNestedPath; 100 } 101 102 103 @Override 104 protected final int doStartTagInternal() throws Exception { 105 String resolvedPath = getPath(); 106 if (!isIgnoreNestedPath()) { 107 String nestedPath = (String) pageContext.getAttribute( 108 NestedPathTag.NESTED_PATH_VARIABLE_NAME, PageContext.REQUEST_SCOPE); 109 // only prepend if not already an absolute path 110 if (nestedPath != null && !resolvedPath.startsWith(nestedPath) && 111 !resolvedPath.equals(nestedPath.substring(0, nestedPath.length() - 1))) { 112 resolvedPath = nestedPath + resolvedPath; 113 } 114 } 115 116 try { 117 this.status = new BindStatus(getRequestContext(), resolvedPath, isHtmlEscape()); 118 } 119 catch (IllegalStateException ex) { 120 throw new JspTagException(ex.getMessage()); 121 } 122 123 // Save previous status values, for re-exposure at the end of this tag. 124 this.previousPageStatus = pageContext.getAttribute(STATUS_VARIABLE_NAME, PageContext.PAGE_SCOPE); 125 this.previousRequestStatus = pageContext.getAttribute(STATUS_VARIABLE_NAME, PageContext.REQUEST_SCOPE); 126 127 // Expose this tag's status object as PageContext attribute, 128 // making it available for JSP EL. 129 pageContext.removeAttribute(STATUS_VARIABLE_NAME, PageContext.PAGE_SCOPE); 130 pageContext.setAttribute(STATUS_VARIABLE_NAME, this.status, PageContext.REQUEST_SCOPE); 131 132 return EVAL_BODY_INCLUDE; 133 } 134 135 @Override 136 public int doEndTag() { 137 // Reset previous status values. 138 if (this.previousPageStatus != null) { 139 pageContext.setAttribute(STATUS_VARIABLE_NAME, this.previousPageStatus, PageContext.PAGE_SCOPE); 140 } 141 if (this.previousRequestStatus != null) { 142 pageContext.setAttribute(STATUS_VARIABLE_NAME, this.previousRequestStatus, PageContext.REQUEST_SCOPE); 143 } 144 else { 145 pageContext.removeAttribute(STATUS_VARIABLE_NAME, PageContext.REQUEST_SCOPE); 146 } 147 return EVAL_PAGE; 148 } 149 150 151 /** 152 * Retrieve the property that this tag is currently bound to, 153 * or {@code null} if bound to an object rather than a specific property. 154 * Intended for cooperating nesting tags. 155 * @return the property that this tag is currently bound to, 156 * or {@code null} if none 157 */ 158 public final String getProperty() { 159 return this.status.getExpression(); 160 } 161 162 /** 163 * Retrieve the Errors instance that this tag is currently bound to. 164 * Intended for cooperating nesting tags. 165 * @return the current Errors instance, or {@code null} if none 166 */ 167 public final Errors getErrors() { 168 return this.status.getErrors(); 169 } 170 171 @Override 172 public final PropertyEditor getEditor() { 173 return this.status.getEditor(); 174 } 175 176 177 @Override 178 public void doFinally() { 179 super.doFinally(); 180 this.status = null; 181 this.previousPageStatus = null; 182 this.previousRequestStatus = null; 183 } 184 185}