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.form; 018 019import java.beans.PropertyEditor; 020import javax.servlet.ServletRequest; 021import javax.servlet.http.HttpServletRequest; 022import javax.servlet.jsp.JspException; 023import javax.servlet.jsp.PageContext; 024 025import org.springframework.beans.PropertyAccessor; 026import org.springframework.util.StringUtils; 027import org.springframework.web.servlet.support.BindStatus; 028import org.springframework.web.servlet.support.RequestDataValueProcessor; 029import org.springframework.web.servlet.tags.EditorAwareTag; 030import org.springframework.web.servlet.tags.NestedPathTag; 031 032/** 033 * Base tag for all data-binding aware JSP form tags. 034 * 035 * <p>Provides the common {@link #setPath path} and {@link #setId id} properties. 036 * Provides sub-classes with utility methods for accessing the {@link BindStatus} 037 * of their bound value and also for {@link #writeOptionalAttribute interacting} 038 * with the {@link TagWriter}. 039 * 040 * @author Rob Harrop 041 * @author Juergen Hoeller 042 * @since 2.0 043 */ 044@SuppressWarnings("serial") 045public abstract class AbstractDataBoundFormElementTag extends AbstractFormTag implements EditorAwareTag { 046 047 /** 048 * Name of the exposed path variable within the scope of this tag: "nestedPath". 049 * Same value as {@link org.springframework.web.servlet.tags.NestedPathTag#NESTED_PATH_VARIABLE_NAME}. 050 */ 051 protected static final String NESTED_PATH_VARIABLE_NAME = NestedPathTag.NESTED_PATH_VARIABLE_NAME; 052 053 054 /** 055 * The property path from the {@link FormTag#setModelAttribute form object}. 056 */ 057 private String path; 058 059 /** 060 * The value of the '{@code id}' attribute. 061 */ 062 private String id; 063 064 /** 065 * The {@link BindStatus} of this tag. 066 */ 067 private BindStatus bindStatus; 068 069 070 /** 071 * Set the property path from the {@link FormTag#setModelAttribute form object}. 072 * May be a runtime expression. 073 */ 074 public void setPath(String path) { 075 this.path = path; 076 } 077 078 /** 079 * Get the {@link #evaluate resolved} property path for the 080 * {@link FormTag#setModelAttribute form object}. 081 */ 082 protected final String getPath() throws JspException { 083 String resolvedPath = (String) evaluate("path", this.path); 084 return (resolvedPath != null ? resolvedPath : ""); 085 } 086 087 /** 088 * Set the value of the '{@code id}' attribute. 089 * <p>May be a runtime expression; defaults to the value of {@link #getName()}. 090 * Note that the default value may not be valid for certain tags. 091 */ 092 @Override 093 public void setId(String id) { 094 this.id = id; 095 } 096 097 /** 098 * Get the value of the '{@code id}' attribute. 099 */ 100 @Override 101 public String getId() { 102 return this.id; 103 } 104 105 106 /** 107 * Writes the default set of attributes to the supplied {@link TagWriter}. 108 * Further abstract sub-classes should override this method to add in 109 * any additional default attributes but <strong>must</strong> remember 110 * to call the {@code super} method. 111 * <p>Concrete sub-classes should call this method when/if they want 112 * to render default attributes. 113 * @param tagWriter the {@link TagWriter} to which any attributes are to be written 114 */ 115 protected void writeDefaultAttributes(TagWriter tagWriter) throws JspException { 116 writeOptionalAttribute(tagWriter, "id", resolveId()); 117 writeOptionalAttribute(tagWriter, "name", getName()); 118 } 119 120 /** 121 * Determine the '{@code id}' attribute value for this tag, 122 * autogenerating one if none specified. 123 * @see #getId() 124 * @see #autogenerateId() 125 */ 126 protected String resolveId() throws JspException { 127 Object id = evaluate("id", getId()); 128 if (id != null) { 129 String idString = id.toString(); 130 return (StringUtils.hasText(idString) ? idString : null); 131 } 132 return autogenerateId(); 133 } 134 135 /** 136 * Autogenerate the '{@code id}' attribute value for this tag. 137 * <p>The default implementation simply delegates to {@link #getName()}, 138 * deleting invalid characters (such as "[" or "]"). 139 */ 140 protected String autogenerateId() throws JspException { 141 return StringUtils.deleteAny(getName(), "[]"); 142 } 143 144 /** 145 * Get the value for the HTML '{@code name}' attribute. 146 * <p>The default implementation simply delegates to 147 * {@link #getPropertyPath()} to use the property path as the name. 148 * For the most part this is desirable as it links with the server-side 149 * expectation for data binding. However, some subclasses may wish to change 150 * the value of the '{@code name}' attribute without changing the bind path. 151 * @return the value for the HTML '{@code name}' attribute 152 */ 153 protected String getName() throws JspException { 154 return getPropertyPath(); 155 } 156 157 /** 158 * Get the {@link BindStatus} for this tag. 159 */ 160 protected BindStatus getBindStatus() throws JspException { 161 if (this.bindStatus == null) { 162 // HTML escaping in tags is performed by the ValueFormatter class. 163 String nestedPath = getNestedPath(); 164 String pathToUse = (nestedPath != null ? nestedPath + getPath() : getPath()); 165 if (pathToUse.endsWith(PropertyAccessor.NESTED_PROPERTY_SEPARATOR)) { 166 pathToUse = pathToUse.substring(0, pathToUse.length() - 1); 167 } 168 this.bindStatus = new BindStatus(getRequestContext(), pathToUse, false); 169 } 170 return this.bindStatus; 171 } 172 173 /** 174 * Get the value of the nested path that may have been exposed by the 175 * {@link NestedPathTag}. 176 */ 177 protected String getNestedPath() { 178 return (String) this.pageContext.getAttribute(NESTED_PATH_VARIABLE_NAME, PageContext.REQUEST_SCOPE); 179 } 180 181 /** 182 * Build the property path for this tag, including the nested path 183 * but <i>not</i> prefixed with the name of the form attribute. 184 * @see #getNestedPath() 185 * @see #getPath() 186 */ 187 protected String getPropertyPath() throws JspException { 188 String expression = getBindStatus().getExpression(); 189 return (expression != null ? expression : ""); 190 } 191 192 /** 193 * Get the bound value. 194 * @see #getBindStatus() 195 */ 196 protected final Object getBoundValue() throws JspException { 197 return getBindStatus().getValue(); 198 } 199 200 /** 201 * Get the {@link PropertyEditor}, if any, in use for value bound to this tag. 202 */ 203 protected PropertyEditor getPropertyEditor() throws JspException { 204 return getBindStatus().getEditor(); 205 } 206 207 /** 208 * Exposes the {@link PropertyEditor} for {@link EditorAwareTag}. 209 * <p>Use {@link #getPropertyEditor()} for internal rendering purposes. 210 */ 211 @Override 212 public final PropertyEditor getEditor() throws JspException { 213 return getPropertyEditor(); 214 } 215 216 /** 217 * Get a display String for the given value, converted by a PropertyEditor 218 * that the BindStatus may have registered for the value's Class. 219 */ 220 protected String convertToDisplayString(Object value) throws JspException { 221 PropertyEditor editor = (value != null ? getBindStatus().findEditor(value.getClass()) : null); 222 return getDisplayString(value, editor); 223 } 224 225 /** 226 * Process the given form field through a {@link RequestDataValueProcessor} 227 * instance if one is configured or otherwise returns the same value. 228 */ 229 protected final String processFieldValue(String name, String value, String type) { 230 RequestDataValueProcessor processor = getRequestContext().getRequestDataValueProcessor(); 231 ServletRequest request = this.pageContext.getRequest(); 232 if (processor != null && request instanceof HttpServletRequest) { 233 value = processor.processFormFieldValue((HttpServletRequest) request, name, value, type); 234 } 235 return value; 236 } 237 238 /** 239 * Disposes of the {@link BindStatus} instance. 240 */ 241 @Override 242 public void doFinally() { 243 super.doFinally(); 244 this.bindStatus = null; 245 } 246 247}