View Javadoc

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