001/* 002 * Copyright 2002-2012 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; 025 026/** 027 * <p>Nested-path tag, to support and assist with nested beans or bean properties 028 * in the model. Exports a "nestedPath" variable of type String in request scope, 029 * visible to the current page and also included pages, if any. 030 * 031 * <p>The BindTag will auto-detect the current nested path and automatically 032 * prepend it to its own path to form a complete path to the bean or bean property. 033 * 034 * <p>This tag will also prepend any existing nested path that is currently set. 035 * Thus, you can nest multiple nested-path tags. 036 * 037 * <p>Thanks to Seth Ladd for the suggestion and the original implementation! 038 * 039 * @author Juergen Hoeller 040 * @since 1.1 041 */ 042@SuppressWarnings("serial") 043public class NestedPathTag extends TagSupport implements TryCatchFinally { 044 045 /** 046 * Name of the exposed variable within the scope of this tag: "nestedPath". 047 */ 048 public static final String NESTED_PATH_VARIABLE_NAME = "nestedPath"; 049 050 051 private String path; 052 053 /** Caching a previous nested path, so that it may be reset */ 054 private String previousNestedPath; 055 056 057 /** 058 * Set the path that this tag should apply. 059 * <p>E.g. "customer" to allow bind paths like "address.street" 060 * rather than "customer.address.street". 061 * @see BindTag#setPath 062 */ 063 public void setPath(String path) { 064 if (path == null) { 065 path = ""; 066 } 067 if (path.length() > 0 && !path.endsWith(PropertyAccessor.NESTED_PROPERTY_SEPARATOR)) { 068 path += PropertyAccessor.NESTED_PROPERTY_SEPARATOR; 069 } 070 this.path = path; 071 } 072 073 /** 074 * Return the path that this tag applies to. 075 */ 076 public String getPath() { 077 return this.path; 078 } 079 080 081 @Override 082 public int doStartTag() throws JspException { 083 // Save previous nestedPath value, build and expose current nestedPath value. 084 // Use request scope to expose nestedPath to included pages too. 085 this.previousNestedPath = 086 (String) pageContext.getAttribute(NESTED_PATH_VARIABLE_NAME, PageContext.REQUEST_SCOPE); 087 String nestedPath = 088 (this.previousNestedPath != null ? this.previousNestedPath + getPath() : getPath()); 089 pageContext.setAttribute(NESTED_PATH_VARIABLE_NAME, nestedPath, PageContext.REQUEST_SCOPE); 090 091 return EVAL_BODY_INCLUDE; 092 } 093 094 /** 095 * Reset any previous nestedPath value. 096 */ 097 @Override 098 public int doEndTag() { 099 if (this.previousNestedPath != null) { 100 // Expose previous nestedPath value. 101 pageContext.setAttribute(NESTED_PATH_VARIABLE_NAME, this.previousNestedPath, PageContext.REQUEST_SCOPE); 102 } 103 else { 104 // Remove exposed nestedPath value. 105 pageContext.removeAttribute(NESTED_PATH_VARIABLE_NAME, PageContext.REQUEST_SCOPE); 106 } 107 108 return EVAL_PAGE; 109 } 110 111 @Override 112 public void doCatch(Throwable throwable) throws Throwable { 113 throw throwable; 114 } 115 116 @Override 117 public void doFinally() { 118 this.previousNestedPath = null; 119 } 120 121}