Author: rjung Date: Wed Jan 28 13:43:11 2015 New Revision: 1655312 URL: http://svn.apache.org/r1655312 Log: Enhance our naming BeanFactory.
If a bean property exists which the Introspector presents us with a type that we don't have a string conversion for, but the bean actually has a method to set the property from a string, allow to provide this information to the BeanFactory. New attribute "forceString" taking a comma separated list of items as values. Each item is either a bean property name (e.g. "foo") meaning that there is a setter function "setFoo(String)" for that property. Or the item is of the form "foo=method" meaning that property "foo" can be set by calling "method(String)". This should make writing a custom bean factory obsolete in quite a few cases. Concrete use case was tibco TibjmsConnectionFactory which has an attribute SSLIdentity detected by Introspector as byte[] but which can be set by setSSLIdentity(String). Existing BeanFactory throws NamingException. Modified: tomcat/trunk/java/org/apache/naming/factory/BeanFactory.java tomcat/trunk/webapps/docs/jndi-resources-howto.xml Modified: tomcat/trunk/java/org/apache/naming/factory/BeanFactory.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/naming/factory/BeanFactory.java?rev=1655312&r1=1655311&r2=1655312&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/naming/factory/BeanFactory.java (original) +++ tomcat/trunk/java/org/apache/naming/factory/BeanFactory.java Wed Jan 28 13:43:11 2015 @@ -20,9 +20,12 @@ package org.apache.naming.factory; import java.beans.BeanInfo; import java.beans.Introspector; import java.beans.PropertyDescriptor; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Enumeration; +import java.util.HashMap; import java.util.Hashtable; +import java.util.Map; import javax.naming.Context; import javax.naming.Name; @@ -143,22 +146,72 @@ public class BeanFactory Object bean = beanClass.newInstance(); + RefAddr ra = ref.get("forceString"); + Map<String, Method> forced = new HashMap<String, Method>(); + String value; + + if (ra != null) { + value = (String)ra.getContent(); + Class<?> paramTypes[] = new Class[1]; + paramTypes[0] = String.class; + String setterName; + int index; + + for (String param: value.split(",")) { + param = param.trim(); + index = param.indexOf('='); + if (index>= 0) { + setterName = param.substring(index + 1).trim(); + param = param.substring(0, index).trim(); + } else { + setterName = "set" + + param.substring(0, 1).toUpperCase() + + param.substring(1); + } + try { + forced.put(param, + beanClass.getMethod(setterName, paramTypes)); + } catch (NoSuchMethodException|SecurityException ex) { + throw new NamingException + ("Forced String setter " + setterName + + " not found for property " + param); + } + } + } + Enumeration<RefAddr> e = ref.getAll(); + while (e.hasMoreElements()) { - RefAddr ra = e.nextElement(); + ra = e.nextElement(); String propName = ra.getType(); if (propName.equals(Constants.FACTORY) || propName.equals("scope") || propName.equals("auth") || + propName.equals("forceString") || propName.equals("singleton")) { continue; } - String value = (String)ra.getContent(); + value = (String)ra.getContent(); Object[] valueArray = new Object[1]; + Method method = forced.get(propName); + if (method != null) { + valueArray[0] = value; + try { + method.invoke(bean, valueArray); + } catch (IllegalAccessException| + IllegalArgumentException| + InvocationTargetException ex) { + throw new NamingException + ("Forced String setter " + method.getName() + + " threw exception for property " + propName); + } + continue; + } + int i = 0; for (i = 0; i<pda.length; i++) { @@ -195,8 +248,9 @@ public class BeanFactory valueArray[0] = Boolean.valueOf(value); } else { throw new NamingException - ("String conversion for property type '" - + propType.getName() + "' not available"); + ("String conversion for property " + propName + + " of type '" + propType.getName() + + "' not available"); } Method setProp = pda[i].getWriteMethod(); Modified: tomcat/trunk/webapps/docs/jndi-resources-howto.xml URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/jndi-resources-howto.xml?rev=1655312&r1=1655311&r2=1655312&view=diff ============================================================================== --- tomcat/trunk/webapps/docs/jndi-resources-howto.xml (original) +++ tomcat/trunk/webapps/docs/jndi-resources-howto.xml Wed Jan 28 13:43:11 2015 @@ -328,6 +328,95 @@ writer.println("foo = " + bean.getFoo() <code>foo</code> property (although we could have), the bean will contain whatever default value is set up by its constructor.</p> + <p>Some beans have properties with types that can not automatically be + converted from a string value. Setting such properties using the Tomcat + BeanFactory will fail with a NamingException. In cases were those beans + provide methods to set the properties from a string value, the Tomcat + BeanFactory can be configured to use these methods. The configuration is + done with the <code>forceString</code> attribute.</p> + + <p>Assume our bean looks like this:</p> + +<source><![CDATA[package com.mycompany; + +public class MyBean { + + private byte foo[] = null; + + public byte[] getFoo() { + return (this.foo); + } + + public void setFoo(byte foo[]) { + this.foo = foo; + } + + public void setFoo(String value) { + this.foo = value.getBytes(); + } + + private byte bar[] = null; + + public byte[] getBar() { + return (this.bar); + } + + public void setBar(byte bar[]) { + this.bar = bar; + } + + public void init(String value) { + this.bar = value.getBytes(); + } + + } +}]]></source> + + <p>The bean has two properties, both are of type <code>byte[]</code>. + The first property <code>foo</code> has a setter taking a string argument. + By default the Tomcat BeanFactory would try to use the automatically + detected setter with the same argument type as the property type and + then throw a NamingException, because it is not prepared to convert + the given string attribute value to <code>byte[]</code>. + We can tell the Tomcat BeanFactory to use the other setter like that:</p> + +<source><![CDATA[<Context ...> + ... + <Resource name="bean/MyBeanFactory" auth="Container" + type="com.mycompany.MyBean" + factory="org.apache.naming.factory.BeanFactory" + forceString="foo" + foo="xyz"/> + ... +</Context>]]></source> + + <p>The bean property <code>bar</code> can also be set from a string, + but one has to use the non-standard method name <code>init</code>. + To set <code>foo</code> and <code>bar</code> use the following + configuration:</p> + +<source><![CDATA[<Context ...> + ... + <Resource name="bean/MyBeanFactory" auth="Container" + type="com.mycompany.MyBean" + factory="org.apache.naming.factory.BeanFactory" + forceString="foo,bar=init" + foo="xyz" + bar="123"/> + ... +</Context>]]></source> + + <p>Multiple property descriptions can be combined in + <code>forceString</code> by concatenation with comma as a separator. + Each property description consists of either only the property name + in which case the BeanFactory calls the setter method. Or it consist + of <code>name=method</code> in which case the property named + <code>name</code> is set by calling method <code>method</code>. + For properties of types <code>String</code> or of primitive type + or of their associated primitive wrapper classes using + <code>forceString</code> is not needed. The correct setter will be + automatically detected and argument conversion will be applied.</p> + </subsection> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org