1 /*
2 * $Id: NestedPropertyHelper.java 471754 2006-11-06 14:55:09Z husted $
3 *
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
19 * under the License.
20 */
21 package org.apache.struts.taglib.nested;
22
23 import org.apache.struts.taglib.html.Constants;
24 import org.apache.struts.taglib.html.FormTag;
25
26 import javax.servlet.http.HttpServletRequest;
27 import javax.servlet.jsp.tagext.Tag;
28
29 import java.util.StringTokenizer;
30
31 /**
32 * <p>A simple helper class that does everything that needs to be done to get
33 * the nested tag extension to work. The tags will pass in their relative
34 * properties and this class will leverage the accessibility of the request
35 * object to calculate the nested references and manage them from a central
36 * place.</p>
37 *
38 * <p>The helper method {@link #setNestedProperties} takes a reference to the
39 * tag itself so all the simpler tags can have their references managed from a
40 * central location. From here, the reference to a provided name is also
41 * preserved for use.</p>
42 *
43 * <p>With all tags keeping track of themselves, we only have to seek to the
44 * next level, or parent tag, were a tag will append a dot and it's own
45 * property.</p>
46 *
47 * @version $Rev: 471754 $ $Date: 2004-10-16 12:38:42 -0400 (Sat, 16 Oct 2004)
48 * $
49 * @since Struts 1.1
50 */
51 public class NestedPropertyHelper {
52 /* key that the tags can rely on to set the details against */
53 public static final String NESTED_INCLUDES_KEY = "<nested-includes-key/>";
54
55 /**
56 * Returns the current nesting property from the request object.
57 *
58 * @param request object to fetch the property reference from
59 * @return String of the bean name to nest against
60 */
61 public static final String getCurrentProperty(HttpServletRequest request) {
62 // get the old one if any
63 NestedReference nr =
64 (NestedReference) request.getAttribute(NESTED_INCLUDES_KEY);
65
66 // return null or the property
67 return (nr == null) ? null : nr.getNestedProperty();
68 }
69
70 /**
71 * <p>Returns the bean name from the request object that the properties
72 * are nesting against.</p>
73 *
74 * <p>The requirement of the tag itself could be removed in the future,
75 * but is required if support for the <html:form> tag is maintained.</p>
76 *
77 * @param request object to fetch the bean reference from
78 * @param nested tag from which to start the search from
79 * @return the string of the bean name to be nesting against
80 */
81 public static final String getCurrentName(HttpServletRequest request,
82 NestedNameSupport nested) {
83 // get the old one if any
84 NestedReference nr =
85 (NestedReference) request.getAttribute(NESTED_INCLUDES_KEY);
86
87 // return null or the property
88 if (nr != null) {
89 return nr.getBeanName();
90 } else {
91 // need to look for a form tag...
92 Tag tag = (Tag) nested;
93 Tag formTag = null;
94
95 // loop all parent tags until we get one that can be nested against
96 do {
97 tag = tag.getParent();
98
99 if ((tag != null) && tag instanceof FormTag) {
100 formTag = tag;
101 }
102 } while ((formTag == null) && (tag != null));
103
104 if (formTag == null) {
105 return "";
106 }
107
108 // return the form's name
109 return ((FormTag) formTag).getBeanName();
110 }
111 }
112
113 /**
114 * Get the adjusted property. Apply the provided property, to the property
115 * already stored in the request object.
116 *
117 * @param request to pull the reference from
118 * @param property to retrieve the evaluated nested property with
119 * @return String of the final nested property reference.
120 */
121 public static final String getAdjustedProperty(HttpServletRequest request,
122 String property) {
123 // get the old one if any
124 String parent = getCurrentProperty(request);
125
126 return calculateRelativeProperty(property, parent);
127 }
128
129 /**
130 * Sets the provided property into the request object for reference by the
131 * other nested tags.
132 *
133 * @param request object to set the new property into
134 * @param property String to set the property to
135 */
136 public static final void setProperty(HttpServletRequest request,
137 String property) {
138 // get the old one if any
139 NestedReference nr = referenceInstance(request);
140
141 nr.setNestedProperty(property);
142 }
143
144 /**
145 * Sets the provided name into the request object for reference by the
146 * other nested tags.
147 *
148 * @param request object to set the new name into
149 * @param name String to set the name to
150 */
151 public static final void setName(HttpServletRequest request, String name) {
152 // get the old one if any
153 NestedReference nr = referenceInstance(request);
154
155 nr.setBeanName(name);
156 }
157
158 /**
159 * Deletes the nested reference from the request object.
160 *
161 * @param request object to remove the reference from
162 */
163 public static final void deleteReference(HttpServletRequest request) {
164 // delete the reference
165 request.removeAttribute(NESTED_INCLUDES_KEY);
166 }
167
168 /**
169 * Helper method that will set all the relevant nesting properties for the
170 * provided tag reference depending on the implementation.
171 *
172 * @param request object to pull references from
173 * @param tag to set the nesting values into
174 */
175 public static void setNestedProperties(HttpServletRequest request,
176 NestedPropertySupport tag) {
177 boolean adjustProperty = true;
178
179 /* if the tag implements NestedNameSupport, set the name for the tag also */
180 if (tag instanceof NestedNameSupport) {
181 NestedNameSupport nameTag = (NestedNameSupport) tag;
182
183 if ((nameTag.getName() == null)
184 || Constants.BEAN_KEY.equals(nameTag.getName())) {
185 nameTag.setName(getCurrentName(request, (NestedNameSupport) tag));
186 } else {
187 adjustProperty = false;
188 }
189 }
190
191 /* get and set the relative property, adjust if required */
192 String property = tag.getProperty();
193
194 if (adjustProperty) {
195 property = getAdjustedProperty(request, property);
196 }
197
198 tag.setProperty(property);
199 }
200
201 /**
202 * Pulls the current nesting reference from the request object, and if
203 * there isn't one there, then it will create one and set it.
204 *
205 * @param request object to manipulate the reference into
206 * @return current nesting reference as stored in the request object
207 */
208 private static final NestedReference referenceInstance(
209 HttpServletRequest request) {
210 /* get the old one if any */
211 NestedReference nr =
212 (NestedReference) request.getAttribute(NESTED_INCLUDES_KEY);
213
214 // make a new one if required
215 if (nr == null) {
216 nr = new NestedReference();
217 request.setAttribute(NESTED_INCLUDES_KEY, nr);
218 }
219
220 // return the reference
221 return nr;
222 }
223
224 /* This property, providing the property to be appended, and the parent tag
225 * to append the property to, will calculate the stepping of the property
226 * and return the qualified nested property
227 *
228 * @param property the property which is to be appended nesting style
229 * @param parent the "dot notated" string representing the structure
230 * @return qualified nested property that the property param is to the parent
231 */
232 private static String calculateRelativeProperty(String property,
233 String parent) {
234 if (parent == null) {
235 parent = "";
236 }
237
238 if (property == null) {
239 property = "";
240 }
241
242 /* Special case... reference my parent's nested property.
243 Otherwise impossible for things like indexed properties */
244 if ("./".equals(property) || "this/".equals(property)) {
245 return parent;
246 }
247
248 /* remove the stepping from the property */
249 String stepping;
250
251 /* isolate a parent reference */
252 if (property.endsWith("/")) {
253 stepping = property;
254 property = "";
255 } else {
256 stepping = property.substring(0, property.lastIndexOf('/') + 1);
257
258 /* isolate the property */
259 property =
260 property.substring(property.lastIndexOf('/') + 1,
261 property.length());
262 }
263
264 if (stepping.startsWith("/")) {
265 /* return from root */
266 return property;
267 } else {
268 /* tokenize the nested property */
269 StringTokenizer proT = new StringTokenizer(parent, ".");
270 int propCount = proT.countTokens();
271
272 /* tokenize the stepping */
273 StringTokenizer strT = new StringTokenizer(stepping, "/");
274 int count = strT.countTokens();
275
276 if (count >= propCount) {
277 /* return from root */
278 return property;
279 } else {
280 /* append the tokens up to the token difference */
281 count = propCount - count;
282
283 StringBuffer result = new StringBuffer();
284
285 for (int i = 0; i < count; i++) {
286 result.append(proT.nextToken());
287 result.append('.');
288 }
289
290 result.append(property);
291
292 /* parent reference will have a dot on the end. Leave it off */
293 if (result.charAt(result.length() - 1) == '.') {
294 return result.substring(0, result.length() - 1);
295 } else {
296 return result.toString();
297 }
298 }
299 }
300 }
301 }