Author: laurieh Date: Wed Nov 16 19:40:50 2005 New Revision: 345179 URL: http://svn.apache.org/viewcvs?rev=345179&view=rev Log: Reworking DynaActionForm enhancement: - enhancement is now optional, and remains off by default - this makes cglib an optional dependency (only required when enhancement is enabled) - improved efficiency (thanks mainly to Niall's improvements) - enhancer functionality is now exposed so it can be re-used elsewhere if appropriate - enhancement is enabled by adding 'enhanced="true"' to the form-bean element in struts-config.xml
This should address most comments/concerns, with the exception of whether this code should move to 'extras', which will require a small amount of additional work. Added: struts/core/trunk/src/java/org/apache/struts/util/DynaBeanInterceptor.java Modified: struts/core/trunk/conf/java/struts-config_1_3.dtd struts/core/trunk/src/java/org/apache/struts/action/DynaActionFormClass.java struts/core/trunk/src/java/org/apache/struts/config/FormBeanConfig.java Modified: struts/core/trunk/conf/java/struts-config_1_3.dtd URL: http://svn.apache.org/viewcvs/struts/core/trunk/conf/java/struts-config_1_3.dtd?rev=345179&r1=345178&r2=345179&view=diff ============================================================================== --- struts/core/trunk/conf/java/struts-config_1_3.dtd (original) +++ struts/core/trunk/conf/java/struts-config_1_3.dtd Wed Nov 16 19:40:50 2005 @@ -125,11 +125,19 @@ type Fully qualified Java class name of the ActionForm subclass to use with this form bean. + + enhanced Flag indicating if the form bean should be dynamically + enhanced to include getters and setters for defined + properties. This is only valid when 'type' is a + dynamic form bean (an instance of + "org.apache.struts.action.DynaActionForm" or a sub-class, + and requires CGLIB when enabled. --> <!ELEMENT form-bean (icon?, display-name?, description?, set-property*, form-property*)> <!ATTLIST form-bean id ID #IMPLIED> <!ATTLIST form-bean className %ClassName; #IMPLIED> <!ATTLIST form-bean dynamic %Boolean; #IMPLIED> +<!ATTLIST form-bean enhanced %Boolean; #IMPLIED> <!ATTLIST form-bean extends %BeanName; #IMPLIED> <!ATTLIST form-bean name %BeanName; #REQUIRED> <!ATTLIST form-bean type %ClassName; #IMPLIED> Modified: struts/core/trunk/src/java/org/apache/struts/action/DynaActionFormClass.java URL: http://svn.apache.org/viewcvs/struts/core/trunk/src/java/org/apache/struts/action/DynaActionFormClass.java?rev=345179&r1=345178&r2=345179&view=diff ============================================================================== --- struts/core/trunk/src/java/org/apache/struts/action/DynaActionFormClass.java (original) +++ struts/core/trunk/src/java/org/apache/struts/action/DynaActionFormClass.java Wed Nov 16 19:40:50 2005 @@ -22,9 +22,8 @@ import java.io.Serializable; import java.util.HashMap; -import java.util.Map; -import java.lang.reflect.Method; +import net.sf.cglib.proxy.Enhancer; import org.apache.commons.beanutils.DynaBean; import org.apache.commons.beanutils.DynaClass; import org.apache.commons.beanutils.DynaProperty; @@ -33,13 +32,7 @@ import org.apache.struts.config.FormBeanConfig; import org.apache.struts.config.FormPropertyConfig; import org.apache.struts.util.RequestUtils; -import net.sf.cglib.proxy.InterfaceMaker; -import net.sf.cglib.proxy.Enhancer; -import net.sf.cglib.proxy.MethodInterceptor; -import net.sf.cglib.proxy.MethodProxy; -import net.sf.cglib.asm.Type; -import net.sf.cglib.core.Signature; -import net.sf.cglib.core.Constants; +import org.apache.struts.util.DynaBeanInterceptor; /** @@ -92,6 +85,12 @@ /** + * <p>The CGLIB <code>Enhancer</code> which we will use to dynamically + * add getters/setters if 'enhanced' is true in the form config. + */ + protected transient Enhancer enhancer = null; + + /** * <p>The form bean configuration information for this class.</p> */ protected FormBeanConfig config = null; @@ -193,9 +192,14 @@ public DynaBean newInstance() throws IllegalAccessException, InstantiationException { - FormPropertyConfig[] props = config.findFormPropertyConfigs(); - DynaActionForm dynaBean = (DynaActionForm) doCreate(props); + DynaActionForm dynaBean = null; + if (config.isEnhanced()) { + dynaBean = (DynaActionForm) getBeanEnhancer().create(); + } else { + dynaBean = (DynaActionForm) getBeanClass().newInstance(); + } dynaBean.setDynaActionFormClass(this); + FormPropertyConfig[] props = config.findFormPropertyConfigs(); for (int i = 0; i < props.length; i++) { dynaBean.set(props[i].getName(), props[i].initial()); } @@ -273,6 +277,24 @@ /** + * <p>Return the <code>Enhancer</code> we are using to construct new + * instances, re-introspecting our [EMAIL PROTECTED] FormBeanConfig} if necessary + * (that is, after being deserialized, since <code>enhancer</code> is + * marked transient).</p> + * + * @return The enhancer used to construct new instances. + */ + protected Enhancer getBeanEnhancer() { + + if (enhancer == null) { + introspect(config); + } + return (enhancer); + + } + + + /** * <p>Introspect our form bean configuration to identify the supported * properties.</p> * @@ -320,115 +342,14 @@ properties[i]); } + // Create CGLIB enhancer if enhancement is enabled + if (config.isEnhanced()) { + DynaBeanInterceptor interceptor = new DynaBeanInterceptor(); + enhancer = interceptor.createEnhancer(this, getBeanClass()); + } } // -------------------------------------------------------- Private Methods - private Object doCreate(FormPropertyConfig[] props) { - // Build an interface to implement consisting of getter/setter - // pairs for each property. Also create a lookup table so we - // can map method names back to the corresponding dynamic - // property on invocation. This allows us to correctly handle - // property names that don't comply with JavaBeans naming - // conventions. - Map properties = new HashMap(props.length * 2); - InterfaceMaker im = new InterfaceMaker(); - for (int i = 0; i < props.length; i++) { - String name = props[i].getName(); - Class type = props[i].getTypeClass(); - Type ttype = Type.getType(type); - - if (! name.matches("[\\w]+")) { - // Note: this allows leading digits, which is not legal - // for an identifier but is valid in a getter/setter - // method name. Since you can define such getter/setter - // directly, we support doing so dynamically too. - if (log.isWarnEnabled()) { - log.warn( - "Dyna property name '" + name + - "' in form bean " + config.getName() + - " is not a legal Java identifier. " + - "No property access methods generated."); - } - } else { - // Capitalize property name appropriately - String property; - if ((name.length() <= 1) || - ( Character.isLowerCase(name.charAt(0)) && - (! Character.isLowerCase(name.charAt(1)))) - ) { - property = name; - } else { - property = - Character.toUpperCase(name.charAt(0)) + - name.substring(1); - } - - // Create the getter/setter method pair - Signature getter = new Signature("get"+property, ttype, Constants.TYPES_EMPTY); - Signature setter = new Signature("set"+property, Type.VOID_TYPE, new Type[] { ttype }); - im.add(getter, Constants.TYPES_EMPTY); - im.add(setter, Constants.TYPES_EMPTY); - properties.put("get"+property, name); - properties.put("set"+property, name); - } - } - Class beanInterface = im.create(); - - // Now generate a proxy for the dyna bean that also implements - // the getter/setter methods defined above. We turn off the - // Factory interface to preven problems with BeanUtils.copyProperties - // when both source and target bean are enhanced (otherwise, the - // target bean's callbacks get overwritten with the source beans, - // leading to unexpected behaviour). - Enhancer e = new Enhancer(); - e.setSuperclass(beanClass); - e.setInterfaces(new Class[] { beanInterface }); - e.setCallback(new BeanInterceptor(properties)); - e.setUseFactory(false); - - // Return the generated/enhanced bean - return e.create(); - } - - private static class BeanInterceptor implements MethodInterceptor, Serializable { - private Map propertyLookup; - - public BeanInterceptor(Map propertyLookup) { - this.propertyLookup = propertyLookup; - } - - public Object intercept(Object obj, Method method, Object[] args, - MethodProxy proxy) throws Throwable { - - String methodNm = method.getName(); - String propertyNm = (String) propertyLookup.get(methodNm); - String targetNm = methodNm.substring(0, 3); // get/set - - if (propertyNm == null) { - // Not a dyna property access, just pass call along - return proxy.invokeSuper(obj, args); - } - - // Handle the dyna property access: - // - map getFoo(...) to get("foo", ...) - // - map setFoo(bar, ...) to set("foo", bar, ...) - Class[] targetArgTypes = new Class[args.length + 1]; - Object[] targetArgs = new Object[args.length + 1]; - targetArgTypes[0] = String.class; // for property name - targetArgs[0] = propertyNm; - - System.arraycopy(args, 0, targetArgs, 1, args.length); - - for (int i = 0; i < args.length; i++) { - targetArgTypes[i + 1] = args[i].getClass(); - } - - Method target = obj.getClass().getMethod(targetNm, targetArgTypes); - - // Return the result of mapped get/set - return target.invoke(obj, targetArgs); - } - } } Modified: struts/core/trunk/src/java/org/apache/struts/config/FormBeanConfig.java URL: http://svn.apache.org/viewcvs/struts/core/trunk/src/java/org/apache/struts/config/FormBeanConfig.java?rev=345179&r1=345178&r2=345179&view=diff ============================================================================== --- struts/core/trunk/src/java/org/apache/struts/config/FormBeanConfig.java (original) +++ struts/core/trunk/src/java/org/apache/struts/config/FormBeanConfig.java Wed Nov 16 19:40:50 2005 @@ -105,6 +105,20 @@ return (this.dynamic); } + /** + * Should the form bean class be dynamically enhanced to simplify + * property access in JSP and tag files? + */ + protected boolean enhance = false; + + public boolean isEnhanced() { + return (this.enhance); + } + + public void setEnhanced(boolean enhance) { + throwIfConfigured(); + this.enhance = enhance; + } /** * The name of the FormBeanConfig that this config inherits configuration Added: struts/core/trunk/src/java/org/apache/struts/util/DynaBeanInterceptor.java URL: http://svn.apache.org/viewcvs/struts/core/trunk/src/java/org/apache/struts/util/DynaBeanInterceptor.java?rev=345179&view=auto ============================================================================== --- struts/core/trunk/src/java/org/apache/struts/util/DynaBeanInterceptor.java (added) +++ struts/core/trunk/src/java/org/apache/struts/util/DynaBeanInterceptor.java Wed Nov 16 19:40:50 2005 @@ -0,0 +1,180 @@ +/* + * $Id$ + * + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.util; + +import java.io.Serializable; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +import net.sf.cglib.asm.Type; +import net.sf.cglib.core.Constants; +import net.sf.cglib.core.Signature; +import net.sf.cglib.proxy.Enhancer; +import net.sf.cglib.proxy.InterfaceMaker; +import net.sf.cglib.proxy.MethodInterceptor; +import net.sf.cglib.proxy.MethodProxy; +import org.apache.commons.beanutils.DynaBean; +import org.apache.commons.beanutils.DynaClass; +import org.apache.commons.beanutils.DynaProperty; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * DynaBeanInterceptor + * + * @since Struts 1.3 + * @version $Revision$ $Date$ + */ +public class DynaBeanInterceptor implements MethodInterceptor, Serializable { + + private Log log = LogFactory.getLog(DynaBeanInterceptor.class); + private Map propertyLookup = new HashMap(); + + /** + * Default Constructor. + */ + public DynaBeanInterceptor() { + } + + /** + * Creates an Enhancer for a DynaClass/DynaBean. + */ + public Enhancer createEnhancer(DynaClass dynaClass, Class beanClass) { + // Build an interface to implement consisting of getter/setter + // pairs for each property. Also create a lookup table so we + // can map method names back to the corresponding dynamic + // property on invocation. This allows us to correctly handle + // property names that don't comply with JavaBeans naming + // conventions. + DynaProperty[] dynaProperties = dynaClass.getDynaProperties(); + Map properties = new HashMap(dynaProperties.length * 2); + InterfaceMaker im = new InterfaceMaker(); + for (int i = 0; i < dynaProperties.length; i++) { + String name = dynaProperties[i].getName(); + Class type = dynaProperties[i].getType(); + Type ttype = Type.getType(type); + + if (! name.matches("[\\w]+")) { + // Note: this allows leading digits, which is not legal + // for an identifier but is valid in a getter/setter + // method name. Since you can define such getter/setter + // directly, we support doing so dynamically too. + if (log.isWarnEnabled()) { + log.warn( + "Dyna property name '" + name + + "' in DynaBean " + dynaClass.getName() + + " is not a legal Java identifier. " + + "No property access methods generated."); + } + } else { + // Capitalize property name appropriately + String property; + if ((name.length() <= 1) || + ( Character.isLowerCase(name.charAt(0)) && + (! Character.isLowerCase(name.charAt(1)))) + ) { + property = name; + } else { + property = + Character.toUpperCase(name.charAt(0)) + + name.substring(1); + } + + // Method names + String getterName = "get"+property; + String setterName = "set"+property; + + // Create the standard getter/setter method pair + Signature getter = new Signature(getterName, ttype, Constants.TYPES_EMPTY); + Signature setter = new Signature(setterName, Type.VOID_TYPE, new Type[] { ttype }); + im.add(getter, Constants.TYPES_EMPTY); + im.add(setter, Constants.TYPES_EMPTY); + + // Create the indexed getter/setter method pair + if (dynaProperties[i].isIndexed()) { + Type itype = Type.getType(Integer.class); + ttype = Type.getType((type.isArray() ? type.getComponentType() : Object.class)); + Signature indexGetter = new Signature(getterName, ttype, new Type[] { itype }); + Signature indexSetter = new Signature(setterName, Type.VOID_TYPE, new Type[] { ttype, itype }); + im.add(indexGetter, Constants.TYPES_EMPTY); + im.add(indexSetter, Constants.TYPES_EMPTY); + } + propertyLookup.put(getterName, name); + propertyLookup.put(setterName, name); + + } + } + Class beanInterface = im.create(); + + // Now generate a proxy for the dyna bean that also implements + // the getter/setter methods defined above. We turn off the + // Factory interface to prevent problems with BeanUtils.copyProperties + // when both source and target bean are enhanced (otherwise, the + // target bean's callbacks get overwritten with the source bean's, + // leading to unexpected behaviour). + Enhancer enhancer = new Enhancer(); + enhancer.setSuperclass(beanClass); + enhancer.setInterfaces(new Class[] { beanInterface }); + enhancer.setCallback(this); + enhancer.setUseFactory(false); + return enhancer; + } + + /** + * Intercepts a method call on the enhanced DynaBean. + */ + public Object intercept(Object obj, Method method, Object[] args, + MethodProxy proxy) throws Throwable { + + String methodNm = method.getName(); + String property = (String)propertyLookup.get(methodNm); + + if (property == null) { + // Not a dyna property access, just pass call along + return proxy.invokeSuper(obj, args); + } + + boolean getter = methodNm.startsWith("get"); + + DynaBean dynaBean = (DynaBean)obj; + if (getter) { + if (args.length == 0) { + return dynaBean.get(property); + } else if(args.length == 1 && args[0].getClass() == Integer.class) { + int index = ((Integer)args[0]).intValue(); + return dynaBean.get(property, index); + } else { + return proxy.invokeSuper(obj, args); + } + } else { + if (args.length == 1) { + dynaBean.set(property, args[0]); + return null; + } else if(args.length == 2 && args[1].getClass() == Integer.class) { + int index = ((Integer)args[1]).intValue(); + dynaBean.set(property, index, args[0]); + return null; + } else { + return proxy.invokeSuper(obj, args); + } + } + + } + +} --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]