View Javadoc

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 }