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.nio.charset.UnsupportedCharsetException; 020import java.util.Map; 021 022import javax.servlet.ServletRequest; 023import javax.servlet.ServletResponse; 024import javax.servlet.http.HttpServletRequest; 025import javax.servlet.http.HttpServletResponse; 026import javax.servlet.jsp.JspException; 027import javax.servlet.jsp.PageContext; 028 029import org.springframework.beans.PropertyAccessor; 030import org.springframework.core.Conventions; 031import org.springframework.http.HttpMethod; 032import org.springframework.lang.Nullable; 033import org.springframework.util.Assert; 034import org.springframework.util.CollectionUtils; 035import org.springframework.util.ObjectUtils; 036import org.springframework.util.StringUtils; 037import org.springframework.web.servlet.support.RequestDataValueProcessor; 038import org.springframework.web.util.HtmlUtils; 039import org.springframework.web.util.UriUtils; 040 041/** 042 * The {@code <form>} tag renders an HTML 'form' tag and exposes a binding path to 043 * inner tags for binding. 044 * 045 * <p>Users should place the form object into the 046 * {@link org.springframework.web.servlet.ModelAndView ModelAndView} when 047 * populating the data for their view. The name of this form object can be 048 * configured using the {@link #setModelAttribute "modelAttribute"} property. 049 * 050 * <p> 051 * <table> 052 * <caption>Attribute Summary</caption> 053 * <thead> 054 * <tr> 055 * <th class="colFirst">Attribute</th> 056 * <th class="colOne">Required?</th> 057 * <th class="colOne">Runtime Expression?</th> 058 * <th class="colLast">Description</th> 059 * </tr> 060 * </thead> 061 * <tbody> 062 * <tr class="altColor"> 063 * <td><p>acceptCharset</p></td> 064 * <td><p>false</p></td> 065 * <td><p>true</p></td> 066 * <td><p>Specifies the list of character encodings for input data that is accepted 067 * by the server processing this form. The value is a space- and/or comma-delimited 068 * list of charset values. The client must interpret this list as an exclusive-or 069 * list, i.e., the server is able to accept any single character encoding per 070 * entity received.</p></td> 071 * </tr> 072 * <tr class="rowColor"> 073 * <td><p>action</p></td> 074 * <td><p>false</p></td> 075 * <td><p>true</p></td> 076 * <td><p>HTML Required Attribute</p></td> 077 * </tr> 078 * <tr class="altColor"> 079 * <td><p>cssClass</p></td> 080 * <td><p>false</p></td> 081 * <td><p>true</p></td> 082 * <td><p>HTML Optional Attribute</p></td> 083 * </tr> 084 * <tr class="rowColor"> 085 * <td><p>cssStyle</p></td> 086 * <td><p>false</p></td> 087 * <td><p>true</p></td> 088 * <td><p>HTML Optional Attribute</p></td> 089 * </tr> 090 * <tr class="altColor"> 091 * <td><p>dir</p></td> 092 * <td><p>false</p></td> 093 * <td><p>true</p></td> 094 * <td><p>HTML Standard Attribute</p></td> 095 * </tr> 096 * <tr class="rowColor"> 097 * <td><p>enctype</p></td> 098 * <td><p>false</p></td> 099 * <td><p>true</p></td> 100 * <td><p>HTML Optional Attribute</p></td> 101 * </tr> 102 * <tr class="altColor"> 103 * <td><p>htmlEscape</p></td> 104 * <td><p>false</p></td> 105 * <td><p>true</p></td> 106 * <td><p>Enable/disable HTML escaping of rendered values.</p></td> 107 * </tr> 108 * <tr class="rowColor"> 109 * <td><p>id</p></td> 110 * <td><p>false</p></td> 111 * <td><p>true</p></td> 112 * <td><p>HTML Standard Attribute</p></td> 113 * </tr> 114 * <tr class="altColor"> 115 * <td><p>lang</p></td> 116 * <td><p>false</p></td> 117 * <td><p>true</p></td> 118 * <td><p>HTML Standard Attribute</p></td> 119 * </tr> 120 * <tr class="rowColor"> 121 * <td><p>method</p></td> 122 * <td><p>false</p></td> 123 * <td><p>true</p></td> 124 * <td><p>HTML Optional Attribute</p></td> 125 * </tr> 126 * <tr class="altColor"> 127 * <td><p>methodParam</p></td> 128 * <td><p>false</p></td> 129 * <td><p>true</p></td> 130 * <td><p>The parameter name used for HTTP methods other then GET and POST. 131 * Default is '_method'.</p></td> 132 * </tr> 133 * <tr class="rowColor"> 134 * <td><p>modelAttribute</p></td> 135 * <td><p>false</p></td> 136 * <td><p>true</p></td> 137 * <td><p>Name of the model attribute under which the form object is exposed. 138 * Defaults to 'command'.</p></td> 139 * </tr> 140 * <tr class="altColor"> 141 * <td><p>name</p></td> 142 * <td><p>false</p></td> 143 * <td><p>true</p></td> 144 * <td><p>HTML Standard Attribute - added for backwards compatibility cases</p></td> 145 * </tr> 146 * <tr class="rowColor"> 147 * <td><p>onclick</p></td> 148 * <td><p>false</p></td> 149 * <td><p>true</p></td> 150 * <td><p>HTML Event Attribute</p></td> 151 * </tr> 152 * <tr class="altColor"> 153 * <td><p>ondblclick</p></td> 154 * <td><p>false</p></td> 155 * <td><p>true</p></td> 156 * <td><p>HTML Event Attribute</p></td> 157 * </tr> 158 * <tr class="rowColor"> 159 * <td><p>onkeydown</p></td> 160 * <td><p>false</p></td> 161 * <td><p>true</p></td> 162 * <td><p>HTML Event Attribute</p></td> 163 * </tr> 164 * <tr class="altColor"> 165 * <td><p>onkeypress</p></td> 166 * <td><p>false</p></td> 167 * <td><p>true</p></td> 168 * <td><p>HTML Event Attribute</p></td> 169 * </tr> 170 * <tr class="rowColor"> 171 * <td><p>onkeyup</p></td> 172 * <td><p>false</p></td> 173 * <td><p>true</p></td> 174 * <td><p>HTML Event Attribute</p></td> 175 * </tr> 176 * <tr class="altColor"> 177 * <td><p>onmousedown</p></td> 178 * <td><p>false</p></td> 179 * <td><p>true</p></td> 180 * <td><p>HTML Event Attribute</p></td> 181 * </tr> 182 * <tr class="rowColor"> 183 * <td><p>onmousemove</p></td> 184 * <td><p>false</p></td> 185 * <td><p>true</p></td> 186 * <td><p>HTML Event Attribute</p></td> 187 * </tr> 188 * <tr class="altColor"> 189 * <td><p>onmouseout</p></td> 190 * <td><p>false</p></td> 191 * <td><p>true</p></td> 192 * <td><p>HTML Event Attribute</p></td> 193 * </tr> 194 * <tr class="rowColor"> 195 * <td><p>onmouseover</p></td> 196 * <td><p>false</p></td> 197 * <td><p>true</p></td> 198 * <td><p>HTML Event Attribute</p></td> 199 * </tr> 200 * <tr class="altColor"> 201 * <td><p>onmouseup</p></td> 202 * <td><p>false</p></td> 203 * <td><p>true</p></td> 204 * <td><p>HTML Event Attribute</p></td> 205 * </tr> 206 * <tr class="rowColor"> 207 * <td><p>onreset</p></td> 208 * <td><p>false</p></td> 209 * <td><p>true</p></td> 210 * <td><p>HTML Event Attribute</p></td> 211 * </tr> 212 * <tr class="altColor"> 213 * <td><p>onsubmit</p></td> 214 * <td><p>false</p></td> 215 * <td><p>true</p></td> 216 * <td><p>HTML Event Attribute</p></td> 217 * </tr> 218 * <tr class="rowColor"> 219 * <td><p>servletRelativeAction</p></td> 220 * <td><p>false</p></td> 221 * <td><p>true</p></td> 222 * <td><p>Action reference to be appended to the current servlet path</p></td> 223 * </tr> 224 * <tr class="altColor"> 225 * <td><p>target</p></td> 226 * <td><p>false</p></td> 227 * <td><p>true</p></td> 228 * <td><p>HTML Optional Attribute</p></td> 229 * </tr> 230 * <tr class="rowColor"> 231 * <td><p>title</p></td> 232 * <td><p>false</p></td> 233 * <td><p>true</p></td> 234 * <td><p>HTML Standard Attribute</p></td> 235 * </tr> 236 * </tbody> 237 * </table> 238 * 239 * @author Rob Harrop 240 * @author Juergen Hoeller 241 * @author Scott Andrews 242 * @author Rossen Stoyanchev 243 * @since 2.0 244 */ 245@SuppressWarnings("serial") 246public class FormTag extends AbstractHtmlElementTag { 247 248 /** The default HTTP method using which form values are sent to the server: "post". */ 249 private static final String DEFAULT_METHOD = "post"; 250 251 /** The default attribute name: "command". */ 252 public static final String DEFAULT_COMMAND_NAME = "command"; 253 254 /** The name of the '{@code modelAttribute}' setting. */ 255 private static final String MODEL_ATTRIBUTE = "modelAttribute"; 256 257 /** 258 * The name of the {@link javax.servlet.jsp.PageContext} attribute under which the 259 * form object name is exposed. 260 */ 261 public static final String MODEL_ATTRIBUTE_VARIABLE_NAME = 262 Conventions.getQualifiedAttributeName(AbstractFormTag.class, MODEL_ATTRIBUTE); 263 264 /** Default method parameter, i.e. {@code _method}. */ 265 private static final String DEFAULT_METHOD_PARAM = "_method"; 266 267 private static final String FORM_TAG = "form"; 268 269 private static final String INPUT_TAG = "input"; 270 271 private static final String ACTION_ATTRIBUTE = "action"; 272 273 private static final String METHOD_ATTRIBUTE = "method"; 274 275 private static final String TARGET_ATTRIBUTE = "target"; 276 277 private static final String ENCTYPE_ATTRIBUTE = "enctype"; 278 279 private static final String ACCEPT_CHARSET_ATTRIBUTE = "accept-charset"; 280 281 private static final String ONSUBMIT_ATTRIBUTE = "onsubmit"; 282 283 private static final String ONRESET_ATTRIBUTE = "onreset"; 284 285 private static final String AUTOCOMPLETE_ATTRIBUTE = "autocomplete"; 286 287 private static final String NAME_ATTRIBUTE = "name"; 288 289 private static final String VALUE_ATTRIBUTE = "value"; 290 291 private static final String TYPE_ATTRIBUTE = "type"; 292 293 294 @Nullable 295 private TagWriter tagWriter; 296 297 private String modelAttribute = DEFAULT_COMMAND_NAME; 298 299 @Nullable 300 private String name; 301 302 @Nullable 303 private String action; 304 305 @Nullable 306 private String servletRelativeAction; 307 308 private String method = DEFAULT_METHOD; 309 310 @Nullable 311 private String target; 312 313 @Nullable 314 private String enctype; 315 316 @Nullable 317 private String acceptCharset; 318 319 @Nullable 320 private String onsubmit; 321 322 @Nullable 323 private String onreset; 324 325 @Nullable 326 private String autocomplete; 327 328 private String methodParam = DEFAULT_METHOD_PARAM; 329 330 /** Caching a previous nested path, so that it may be reset. */ 331 @Nullable 332 private String previousNestedPath; 333 334 335 /** 336 * Set the name of the form attribute in the model. 337 * <p>May be a runtime expression. 338 */ 339 public void setModelAttribute(String modelAttribute) { 340 this.modelAttribute = modelAttribute; 341 } 342 343 /** 344 * Get the name of the form attribute in the model. 345 */ 346 protected String getModelAttribute() { 347 return this.modelAttribute; 348 } 349 350 /** 351 * Set the value of the '{@code name}' attribute. 352 * <p>May be a runtime expression. 353 * <p>Name is not a valid attribute for form on XHTML 1.0. However, 354 * it is sometimes needed for backward compatibility. 355 */ 356 public void setName(String name) { 357 this.name = name; 358 } 359 360 /** 361 * Get the value of the '{@code name}' attribute. 362 */ 363 @Override 364 @Nullable 365 protected String getName() throws JspException { 366 return this.name; 367 } 368 369 /** 370 * Set the value of the '{@code action}' attribute. 371 * <p>May be a runtime expression. 372 */ 373 public void setAction(@Nullable String action) { 374 this.action = (action != null ? action : ""); 375 } 376 377 /** 378 * Get the value of the '{@code action}' attribute. 379 */ 380 @Nullable 381 protected String getAction() { 382 return this.action; 383 } 384 385 /** 386 * Set the value of the '{@code action}' attribute through a value 387 * that is to be appended to the current servlet path. 388 * <p>May be a runtime expression. 389 * @since 3.2.3 390 */ 391 public void setServletRelativeAction(@Nullable String servletRelativeAction) { 392 this.servletRelativeAction = servletRelativeAction; 393 } 394 395 /** 396 * Get the servlet-relative value of the '{@code action}' attribute. 397 * @since 3.2.3 398 */ 399 @Nullable 400 protected String getServletRelativeAction() { 401 return this.servletRelativeAction; 402 } 403 404 /** 405 * Set the value of the '{@code method}' attribute. 406 * <p>May be a runtime expression. 407 */ 408 public void setMethod(String method) { 409 this.method = method; 410 } 411 412 /** 413 * Get the value of the '{@code method}' attribute. 414 */ 415 protected String getMethod() { 416 return this.method; 417 } 418 419 /** 420 * Set the value of the '{@code target}' attribute. 421 * <p>May be a runtime expression. 422 */ 423 public void setTarget(String target) { 424 this.target = target; 425 } 426 427 /** 428 * Get the value of the '{@code target}' attribute. 429 */ 430 @Nullable 431 public String getTarget() { 432 return this.target; 433 } 434 435 /** 436 * Set the value of the '{@code enctype}' attribute. 437 * <p>May be a runtime expression. 438 */ 439 public void setEnctype(String enctype) { 440 this.enctype = enctype; 441 } 442 443 /** 444 * Get the value of the '{@code enctype}' attribute. 445 */ 446 @Nullable 447 protected String getEnctype() { 448 return this.enctype; 449 } 450 451 /** 452 * Set the value of the '{@code acceptCharset}' attribute. 453 * <p>May be a runtime expression. 454 */ 455 public void setAcceptCharset(String acceptCharset) { 456 this.acceptCharset = acceptCharset; 457 } 458 459 /** 460 * Get the value of the '{@code acceptCharset}' attribute. 461 */ 462 @Nullable 463 protected String getAcceptCharset() { 464 return this.acceptCharset; 465 } 466 467 /** 468 * Set the value of the '{@code onsubmit}' attribute. 469 * <p>May be a runtime expression. 470 */ 471 public void setOnsubmit(String onsubmit) { 472 this.onsubmit = onsubmit; 473 } 474 475 /** 476 * Get the value of the '{@code onsubmit}' attribute. 477 */ 478 @Nullable 479 protected String getOnsubmit() { 480 return this.onsubmit; 481 } 482 483 /** 484 * Set the value of the '{@code onreset}' attribute. 485 * <p>May be a runtime expression. 486 */ 487 public void setOnreset(String onreset) { 488 this.onreset = onreset; 489 } 490 491 /** 492 * Get the value of the '{@code onreset}' attribute. 493 */ 494 @Nullable 495 protected String getOnreset() { 496 return this.onreset; 497 } 498 499 /** 500 * Set the value of the '{@code autocomplete}' attribute. 501 * May be a runtime expression. 502 */ 503 public void setAutocomplete(String autocomplete) { 504 this.autocomplete = autocomplete; 505 } 506 507 /** 508 * Get the value of the '{@code autocomplete}' attribute. 509 */ 510 @Nullable 511 protected String getAutocomplete() { 512 return this.autocomplete; 513 } 514 515 /** 516 * Set the name of the request param for non-browser supported HTTP methods. 517 */ 518 public void setMethodParam(String methodParam) { 519 this.methodParam = methodParam; 520 } 521 522 /** 523 * Get the name of the request param for non-browser supported HTTP methods. 524 * @since 4.2.3 525 */ 526 protected String getMethodParam() { 527 return this.methodParam; 528 } 529 530 /** 531 * Determine if the HTTP method is supported by browsers (i.e. GET or POST). 532 */ 533 protected boolean isMethodBrowserSupported(String method) { 534 return ("get".equalsIgnoreCase(method) || "post".equalsIgnoreCase(method)); 535 } 536 537 538 /** 539 * Writes the opening part of the block '{@code form}' tag and exposes 540 * the form object name in the {@link javax.servlet.jsp.PageContext}. 541 * @param tagWriter the {@link TagWriter} to which the form content is to be written 542 * @return {@link javax.servlet.jsp.tagext.Tag#EVAL_BODY_INCLUDE} 543 */ 544 @Override 545 protected int writeTagContent(TagWriter tagWriter) throws JspException { 546 this.tagWriter = tagWriter; 547 548 tagWriter.startTag(FORM_TAG); 549 writeDefaultAttributes(tagWriter); 550 tagWriter.writeAttribute(ACTION_ATTRIBUTE, resolveAction()); 551 writeOptionalAttribute(tagWriter, METHOD_ATTRIBUTE, getHttpMethod()); 552 writeOptionalAttribute(tagWriter, TARGET_ATTRIBUTE, getTarget()); 553 writeOptionalAttribute(tagWriter, ENCTYPE_ATTRIBUTE, getEnctype()); 554 writeOptionalAttribute(tagWriter, ACCEPT_CHARSET_ATTRIBUTE, getAcceptCharset()); 555 writeOptionalAttribute(tagWriter, ONSUBMIT_ATTRIBUTE, getOnsubmit()); 556 writeOptionalAttribute(tagWriter, ONRESET_ATTRIBUTE, getOnreset()); 557 writeOptionalAttribute(tagWriter, AUTOCOMPLETE_ATTRIBUTE, getAutocomplete()); 558 559 tagWriter.forceBlock(); 560 561 if (!isMethodBrowserSupported(getMethod())) { 562 assertHttpMethod(getMethod()); 563 String inputName = getMethodParam(); 564 String inputType = "hidden"; 565 tagWriter.startTag(INPUT_TAG); 566 writeOptionalAttribute(tagWriter, TYPE_ATTRIBUTE, inputType); 567 writeOptionalAttribute(tagWriter, NAME_ATTRIBUTE, inputName); 568 writeOptionalAttribute(tagWriter, VALUE_ATTRIBUTE, processFieldValue(inputName, getMethod(), inputType)); 569 tagWriter.endTag(); 570 } 571 572 // Expose the form object name for nested tags... 573 String modelAttribute = resolveModelAttribute(); 574 this.pageContext.setAttribute(MODEL_ATTRIBUTE_VARIABLE_NAME, modelAttribute, PageContext.REQUEST_SCOPE); 575 576 // Save previous nestedPath value, build and expose current nestedPath value. 577 // Use request scope to expose nestedPath to included pages too. 578 this.previousNestedPath = 579 (String) this.pageContext.getAttribute(NESTED_PATH_VARIABLE_NAME, PageContext.REQUEST_SCOPE); 580 this.pageContext.setAttribute(NESTED_PATH_VARIABLE_NAME, 581 modelAttribute + PropertyAccessor.NESTED_PROPERTY_SEPARATOR, PageContext.REQUEST_SCOPE); 582 583 return EVAL_BODY_INCLUDE; 584 } 585 586 private String getHttpMethod() { 587 return (isMethodBrowserSupported(getMethod()) ? getMethod() : DEFAULT_METHOD); 588 } 589 590 private void assertHttpMethod(String method) { 591 for (HttpMethod httpMethod : HttpMethod.values()) { 592 if (httpMethod.name().equalsIgnoreCase(method)) { 593 return; 594 } 595 } 596 throw new IllegalArgumentException("Invalid HTTP method: " + method); 597 } 598 599 /** 600 * Autogenerated IDs correspond to the form object name. 601 */ 602 @Override 603 protected String autogenerateId() throws JspException { 604 return resolveModelAttribute(); 605 } 606 607 /** 608 * {@link #evaluate Resolves} and returns the name of the form object. 609 * @throws IllegalArgumentException if the form object resolves to {@code null} 610 */ 611 protected String resolveModelAttribute() throws JspException { 612 Object resolvedModelAttribute = evaluate(MODEL_ATTRIBUTE, getModelAttribute()); 613 if (resolvedModelAttribute == null) { 614 throw new IllegalArgumentException(MODEL_ATTRIBUTE + " must not be null"); 615 } 616 return (String) resolvedModelAttribute; 617 } 618 619 /** 620 * Resolve the value of the '{@code action}' attribute. 621 * <p>If the user configured an '{@code action}' value then the result of 622 * evaluating this value is used. If the user configured an 623 * '{@code servletRelativeAction}' value then the value is prepended 624 * with the context and servlet paths, and the result is used. Otherwise, the 625 * {@link org.springframework.web.servlet.support.RequestContext#getRequestUri() 626 * originating URI} is used. 627 * @return the value that is to be used for the '{@code action}' attribute 628 */ 629 protected String resolveAction() throws JspException { 630 String action = getAction(); 631 String servletRelativeAction = getServletRelativeAction(); 632 if (StringUtils.hasText(action)) { 633 action = getDisplayString(evaluate(ACTION_ATTRIBUTE, action)); 634 return processAction(action); 635 } 636 else if (StringUtils.hasText(servletRelativeAction)) { 637 String pathToServlet = getRequestContext().getPathToServlet(); 638 if (servletRelativeAction.startsWith("/") && 639 !servletRelativeAction.startsWith(getRequestContext().getContextPath())) { 640 servletRelativeAction = pathToServlet + servletRelativeAction; 641 } 642 servletRelativeAction = getDisplayString(evaluate(ACTION_ATTRIBUTE, servletRelativeAction)); 643 return processAction(servletRelativeAction); 644 } 645 else { 646 String requestUri = getRequestContext().getRequestUri(); 647 String encoding = this.pageContext.getResponse().getCharacterEncoding(); 648 try { 649 requestUri = UriUtils.encodePath(requestUri, encoding); 650 } 651 catch (UnsupportedCharsetException ex) { 652 // shouldn't happen - if it does, proceed with requestUri as-is 653 } 654 ServletResponse response = this.pageContext.getResponse(); 655 if (response instanceof HttpServletResponse) { 656 requestUri = ((HttpServletResponse) response).encodeURL(requestUri); 657 String queryString = getRequestContext().getQueryString(); 658 if (StringUtils.hasText(queryString)) { 659 requestUri += "?" + HtmlUtils.htmlEscape(queryString); 660 } 661 } 662 if (StringUtils.hasText(requestUri)) { 663 return processAction(requestUri); 664 } 665 else { 666 throw new IllegalArgumentException("Attribute 'action' is required. " + 667 "Attempted to resolve against current request URI but request URI was null."); 668 } 669 } 670 } 671 672 /** 673 * Process the action through a {@link RequestDataValueProcessor} instance 674 * if one is configured or otherwise returns the action unmodified. 675 */ 676 private String processAction(String action) { 677 RequestDataValueProcessor processor = getRequestContext().getRequestDataValueProcessor(); 678 ServletRequest request = this.pageContext.getRequest(); 679 if (processor != null && request instanceof HttpServletRequest) { 680 action = processor.processAction((HttpServletRequest) request, action, getHttpMethod()); 681 } 682 return action; 683 } 684 685 /** 686 * Closes the '{@code form}' block tag and removes the form object name 687 * from the {@link javax.servlet.jsp.PageContext}. 688 */ 689 @Override 690 public int doEndTag() throws JspException { 691 RequestDataValueProcessor processor = getRequestContext().getRequestDataValueProcessor(); 692 ServletRequest request = this.pageContext.getRequest(); 693 if (processor != null && request instanceof HttpServletRequest) { 694 writeHiddenFields(processor.getExtraHiddenFields((HttpServletRequest) request)); 695 } 696 Assert.state(this.tagWriter != null, "No TagWriter set"); 697 this.tagWriter.endTag(); 698 return EVAL_PAGE; 699 } 700 701 /** 702 * Writes the given values as hidden fields. 703 */ 704 private void writeHiddenFields(@Nullable Map<String, String> hiddenFields) throws JspException { 705 if (!CollectionUtils.isEmpty(hiddenFields)) { 706 Assert.state(this.tagWriter != null, "No TagWriter set"); 707 this.tagWriter.appendValue("<div>\n"); 708 for (Map.Entry<String, String> entry : hiddenFields.entrySet()) { 709 this.tagWriter.appendValue("<input type=\"hidden\" "); 710 this.tagWriter.appendValue("name=\"" + entry.getKey() + "\" value=\"" + entry.getValue() + "\" "); 711 this.tagWriter.appendValue("/>\n"); 712 } 713 this.tagWriter.appendValue("</div>"); 714 } 715 } 716 717 /** 718 * Clears the stored {@link TagWriter}. 719 */ 720 @Override 721 public void doFinally() { 722 super.doFinally(); 723 724 this.pageContext.removeAttribute(MODEL_ATTRIBUTE_VARIABLE_NAME, PageContext.REQUEST_SCOPE); 725 if (this.previousNestedPath != null) { 726 // Expose previous nestedPath value. 727 this.pageContext.setAttribute(NESTED_PATH_VARIABLE_NAME, this.previousNestedPath, PageContext.REQUEST_SCOPE); 728 } 729 else { 730 // Remove exposed nestedPath value. 731 this.pageContext.removeAttribute(NESTED_PATH_VARIABLE_NAME, PageContext.REQUEST_SCOPE); 732 } 733 this.tagWriter = null; 734 this.previousNestedPath = null; 735 } 736 737 738 /** 739 * Override resolve CSS class since error class is not supported. 740 */ 741 @Override 742 protected String resolveCssClass() throws JspException { 743 return ObjectUtils.getDisplayString(evaluate("cssClass", getCssClass())); 744 } 745 746 /** 747 * Unsupported for forms. 748 * @throws UnsupportedOperationException always 749 */ 750 @Override 751 public void setPath(String path) { 752 throw new UnsupportedOperationException("The 'path' attribute is not supported for forms"); 753 } 754 755 /** 756 * Unsupported for forms. 757 * @throws UnsupportedOperationException always 758 */ 759 @Override 760 public void setCssErrorClass(String cssErrorClass) { 761 throw new UnsupportedOperationException("The 'cssErrorClass' attribute is not supported for forms"); 762 } 763 764}