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 javax.servlet.jsp.JspException; 020import javax.servlet.jsp.PageContext; 021import javax.servlet.jsp.tagext.TagSupport; 022import javax.servlet.jsp.tagext.TryCatchFinally; 023 024import org.springframework.beans.PropertyAccessor; 025import org.springframework.lang.Nullable; 026 027/** 028 * <p>The {@code <nestedPath>} tag supports and assists with nested beans or 029 * bean properties in the model. Exports a "nestedPath" variable of type String 030 * in request scope, visible to the current page and also included pages, if any. 031 * 032 * <p>The BindTag will auto-detect the current nested path and automatically 033 * prepend it to its own path to form a complete path to the bean or bean property. 034 * 035 * <p>This tag will also prepend any existing nested path that is currently set. 036 * Thus, you can nest multiple nested-path tags. 037 * 038 * <table> 039 * <caption>Attribute Summary</caption> 040 * <thead> 041 * <tr> 042 * <th>Attribute</th> 043 * <th>Required?</th> 044 * <th>Runtime Expression?</th> 045 * <th>Description</th> 046 * </tr> 047 * </thead> 048 * <tbody> 049 * <tr> 050 * <td>path</td> 051 * <td>true</td> 052 * <td>true</td> 053 * <td>Set the path that this tag should apply. E.g. 'customer' to allow bind 054 * paths like 'address.street' rather than 'customer.address.street'.</td> 055 * </tr> 056 * </tbody> 057 * </table> 058 * 059 * @author Juergen Hoeller 060 * @since 1.1 061 */ 062@SuppressWarnings("serial") 063public class NestedPathTag extends TagSupport implements TryCatchFinally { 064 065 /** 066 * Name of the exposed variable within the scope of this tag: "nestedPath". 067 */ 068 public static final String NESTED_PATH_VARIABLE_NAME = "nestedPath"; 069 070 071 @Nullable 072 private String path; 073 074 /** Caching a previous nested path, so that it may be reset. */ 075 @Nullable 076 private String previousNestedPath; 077 078 079 /** 080 * Set the path that this tag should apply. 081 * <p>E.g. "customer" to allow bind paths like "address.street" 082 * rather than "customer.address.street". 083 * @see BindTag#setPath 084 */ 085 public void setPath(@Nullable String path) { 086 if (path == null) { 087 path = ""; 088 } 089 if (path.length() > 0 && !path.endsWith(PropertyAccessor.NESTED_PROPERTY_SEPARATOR)) { 090 path += PropertyAccessor.NESTED_PROPERTY_SEPARATOR; 091 } 092 this.path = path; 093 } 094 095 /** 096 * Return the path that this tag applies to. 097 */ 098 @Nullable 099 public String getPath() { 100 return this.path; 101 } 102 103 104 @Override 105 public int doStartTag() throws JspException { 106 // Save previous nestedPath value, build and expose current nestedPath value. 107 // Use request scope to expose nestedPath to included pages too. 108 this.previousNestedPath = 109 (String) this.pageContext.getAttribute(NESTED_PATH_VARIABLE_NAME, PageContext.REQUEST_SCOPE); 110 String nestedPath = 111 (this.previousNestedPath != null ? this.previousNestedPath + getPath() : getPath()); 112 this.pageContext.setAttribute(NESTED_PATH_VARIABLE_NAME, nestedPath, PageContext.REQUEST_SCOPE); 113 114 return EVAL_BODY_INCLUDE; 115 } 116 117 /** 118 * Reset any previous nestedPath value. 119 */ 120 @Override 121 public int doEndTag() { 122 if (this.previousNestedPath != null) { 123 // Expose previous nestedPath value. 124 this.pageContext.setAttribute(NESTED_PATH_VARIABLE_NAME, this.previousNestedPath, PageContext.REQUEST_SCOPE); 125 } 126 else { 127 // Remove exposed nestedPath value. 128 this.pageContext.removeAttribute(NESTED_PATH_VARIABLE_NAME, PageContext.REQUEST_SCOPE); 129 } 130 131 return EVAL_PAGE; 132 } 133 134 @Override 135 public void doCatch(Throwable throwable) throws Throwable { 136 throw throwable; 137 } 138 139 @Override 140 public void doFinally() { 141 this.previousNestedPath = null; 142 } 143 144}