/*
* $Id: NestedPropertyHelper.java 471754 2006-11-06 14:55:09Z husted $
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.struts.taglib.nested;
import org.apache.struts.taglib.html.Constants;
import org.apache.struts.taglib.html.FormTag;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.tagext.Tag;
import java.util.StringTokenizer;
/**
* <p>A simple helper class that does everything that needs to be done to get
* the nested tag extension to work. The tags will pass in their relative
* properties and this class will leverage the accessibility of the request
* object to calculate the nested references and manage them from a central
* place.</p>
*
* <p>The helper method {@link #setNestedProperties} takes a reference to the
* tag itself so all the simpler tags can have their references managed from a
* central location. From here, the reference to a provided name is also
* preserved for use.</p>
*
* <p>With all tags keeping track of themselves, we only have to seek to the
* next level, or parent tag, were a tag will append a dot and it's own
* property.</p>
*
* @version $Rev: 471754 $ $Date: 2004-10-16 12:38:42 -0400 (Sat, 16 Oct 2004)
* $
* @since Struts 1.1
*/
public class NestedPropertyHelper {
/* key that the tags can rely on to set the details against */
public static final String NESTED_INCLUDES_KEY = "<nested-includes-key/>";
/**
* Returns the current nesting property from the request object.
*
* @param request object to fetch the property reference from
* @return String of the bean name to nest against
*/
public static final String getCurrentProperty(HttpServletRequest request) {
// get the old one if any
NestedReference nr =
(NestedReference) request.getAttribute(NESTED_INCLUDES_KEY);
// return null or the property
return (nr == null) ? null : nr.getNestedProperty();
}
/**
* <p>Returns the bean name from the request object that the properties
* are nesting against.</p>
*
* <p>The requirement of the tag itself could be removed in the future,
* but is required if support for the <html:form> tag is maintained.</p>
*
* @param request object to fetch the bean reference from
* @param nested tag from which to start the search from
* @return the string of the bean name to be nesting against
*/
public static final String getCurrentName(HttpServletRequest request,
NestedNameSupport nested) {
// get the old one if any
NestedReference nr =
(NestedReference) request.getAttribute(NESTED_INCLUDES_KEY);
// return null or the property
if (nr != null) {
return nr.getBeanName();
} else {
// need to look for a form tag...
Tag tag = (Tag) nested;
Tag formTag = null;
// loop all parent tags until we get one that can be nested against
do {
tag = tag.getParent();
if ((tag != null) && tag instanceof FormTag) {
formTag = tag;
}
} while ((formTag == null) && (tag != null));
if (formTag == null) {
return "";
}
// return the form's name
return ((FormTag) formTag).getBeanName();
}
}
/**
* Get the adjusted property. Apply the provided property, to the property
* already stored in the request object.
*
* @param request to pull the reference from
* @param property to retrieve the evaluated nested property with
* @return String of the final nested property reference.
*/
public static final String getAdjustedProperty(HttpServletRequest request,
String property) {
// get the old one if any
String parent = getCurrentProperty(request);
return calculateRelativeProperty(property, parent);
}
/**
* Sets the provided property into the request object for reference by the
* other nested tags.
*
* @param request object to set the new property into
* @param property String to set the property to
*/
public static final void setProperty(HttpServletRequest request,
String property) {
// get the old one if any
NestedReference nr = referenceInstance(request);
nr.setNestedProperty(property);
}
/**
* Sets the provided name into the request object for reference by the
* other nested tags.
*
* @param request object to set the new name into
* @param name String to set the name to
*/
public static final void setName(HttpServletRequest request, String name) {
// get the old one if any
NestedReference nr = referenceInstance(request);
nr.setBeanName(name);
}
/**
* Deletes the nested reference from the request object.
*
* @param request object to remove the reference from
*/
public static final void deleteReference(HttpServletRequest request) {
// delete the reference
request.removeAttribute(NESTED_INCLUDES_KEY);
}
/**
* Helper method that will set all the relevant nesting properties for the
* provided tag reference depending on the implementation.
*
* @param request object to pull references from
* @param tag to set the nesting values into
*/
public static void setNestedProperties(HttpServletRequest request,
NestedPropertySupport tag) {
boolean adjustProperty = true;
/* if the tag implements NestedNameSupport, set the name for the tag also */
if (tag instanceof NestedNameSupport) {
NestedNameSupport nameTag = (NestedNameSupport) tag;
if ((nameTag.getName() == null)
|| Constants.BEAN_KEY.equals(nameTag.getName())) {
nameTag.setName(getCurrentName(request, (NestedNameSupport) tag));
} else {
adjustProperty = false;
}
}
/* get and set the relative property, adjust if required */
String property = tag.getProperty();
if (adjustProperty) {
property = getAdjustedProperty(request, property);
}
tag.setProperty(property);
}
/**
* Pulls the current nesting reference from the request object, and if
* there isn't one there, then it will create one and set it.
*
* @param request object to manipulate the reference into
* @return current nesting reference as stored in the request object
*/
private static final NestedReference referenceInstance(
HttpServletRequest request) {
/* get the old one if any */
NestedReference nr =
(NestedReference) request.getAttribute(NESTED_INCLUDES_KEY);
// make a new one if required
if (nr == null) {
nr = new NestedReference();
request.setAttribute(NESTED_INCLUDES_KEY, nr);
}
// return the reference
return nr;
}
/* This property, providing the property to be appended, and the parent tag
* to append the property to, will calculate the stepping of the property
* and return the qualified nested property
*
* @param property the property which is to be appended nesting style
* @param parent the "dot notated" string representing the structure
* @return qualified nested property that the property param is to the parent
*/
private static String calculateRelativeProperty(String property,
String parent) {
if (parent == null) {
parent = "";
}
if (property == null) {
property = "";
}
/* Special case... reference my parent's nested property.
Otherwise impossible for things like indexed properties */
if ("./".equals(property) || "this/".equals(property)) {
return parent;
}
/* remove the stepping from the property */
String stepping;
/* isolate a parent reference */
if (property.endsWith("/")) {
stepping = property;
property = "";
} else {
stepping = property.substring(0, property.lastIndexOf('/') + 1);
/* isolate the property */
property =
property.substring(property.lastIndexOf('/') + 1,
property.length());
}
if (stepping.startsWith("/")) {
/* return from root */
return property;
} else {
/* tokenize the nested property */
StringTokenizer proT = new StringTokenizer(parent, ".");
int propCount = proT.countTokens();
/* tokenize the stepping */
StringTokenizer strT = new StringTokenizer(stepping, "/");
int count = strT.countTokens();
if (count >= propCount) {
/* return from root */
return property;
} else {
/* append the tokens up to the token difference */
count = propCount - count;
StringBuffer result = new StringBuffer();
for (int i = 0; i < count; i++) {
result.append(proT.nextToken());
result.append('.');
}
result.append(property);
/* parent reference will have a dot on the end. Leave it off */
if (result.charAt(result.length() - 1) == '.') {
return result.substring(0, result.length() - 1);
} else {
return result.toString();
}
}
}
}
}