AMQ-4011: IntrospectionSupport does not use java bean propery editors which are not thread safe, cause leaks, and are jvm global causing issues for other apps.

git-svn-id: https://svn.apache.org/repos/asf/activemq/trunk@1387025 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Claus Ibsen 2012-09-18 07:50:35 +00:00
parent fef7f54a17
commit 1802116df8
9 changed files with 233 additions and 117 deletions

View File

@ -18,7 +18,12 @@ package org.apache.activemq.util;
import java.beans.PropertyEditorSupport;
@Deprecated
/**
* Used by xbean to set booleans.
* <p/>
* <b>Important: </b> Do not use this for other purposes than xbean, as property editors
* are not thread safe, and they are slow to use.
*/
public class BooleanEditor extends PropertyEditorSupport {
public String getJavaInitializationString() {

View File

@ -16,12 +16,9 @@
*/
package org.apache.activemq.util;
import java.beans.PropertyEditor;
import java.beans.PropertyEditorManager;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
@ -34,34 +31,13 @@ import java.util.Set;
import javax.net.ssl.SSLServerSocket;
import org.apache.activemq.command.ActiveMQDestination;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class IntrospectionSupport {
static {
// Add Spring and ActiveMQ specific property editors
String[] additionalPath = new String[] {
"org.springframework.beans.propertyeditors",
"org.apache.activemq.util" };
synchronized (PropertyEditorManager.class) {
List<String> list = new ArrayList<String>();
list.addAll(Arrays.asList(PropertyEditorManager.getEditorSearchPath()));
if (!list.contains(additionalPath[0])) {
list.add(additionalPath[0]);
}
if (!list.contains(additionalPath[1])) {
list.add(additionalPath[1]);
}
private static final Logger LOG = LoggerFactory.getLogger(IntrospectionSupport.class);
String[] newSearchPath = list.toArray(new String[list.size()]);
try {
PropertyEditorManager.setEditorSearchPath(newSearchPath);
} catch(java.security.AccessControlException ignore) {
// we might be in an applet...
}
}
}
private IntrospectionSupport() {
}
@ -86,7 +62,7 @@ public final class IntrospectionSupport {
String name = method.getName();
Class type = method.getReturnType();
Class params[] = method.getParameterTypes();
if ((name.startsWith("is") || name.startsWith("get")) && params.length == 0 && type != null && isSettableType(type)) {
if ((name.startsWith("is") || name.startsWith("get")) && params.length == 0 && type != null) {
try {
@ -208,33 +184,75 @@ public final class IntrospectionSupport {
}
}
private static Object convert(Object value, Class type) {
private static Object convert(Object value, Class to) {
if (value == null) {
// lets avoid NullPointerException when converting to boolean for null values
if (boolean.class.isAssignableFrom(to)) {
return Boolean.FALSE;
}
return null;
}
// eager same instance type test to avoid the overhead of invoking the type converter
// if already same type
if (to.isAssignableFrom(value.getClass())) {
return to.cast(value);
}
// special for String[] as we do not want to use a PropertyEditor for that
if (type.isAssignableFrom(String[].class)) {
if (to.isAssignableFrom(String[].class)) {
return StringArrayConverter.convertToStringArray(value);
}
PropertyEditor editor = PropertyEditorManager.findEditor(type);
if (editor != null) {
editor.setAsText(value.toString());
return editor.getValue();
// special for String to List<ActiveMQDestination> as we do not want to use a PropertyEditor for that
if (value.getClass().equals(String.class) && to.equals(List.class)) {
Object answer = StringToListOfActiveMQDestinationConverter.convertToActiveMQDestination(value);
if (answer != null) {
return answer;
}
}
TypeConversionSupport.Converter converter = TypeConversionSupport.lookupConverter(value.getClass(), to);
if (converter != null) {
return converter.convert(value);
} else {
throw new IllegalArgumentException("Cannot convert from " + value.getClass()
+ " to " + to + " with value " + value);
}
return null;
}
public static String convertToString(Object value, Class type) {
public static String convertToString(Object value, Class to) {
if (value == null) {
return null;
}
// already a String
if (value instanceof String) {
return (String) value;
}
// special for String[] as we do not want to use a PropertyEditor for that
if (value != null && value.getClass().isAssignableFrom(String[].class)) {
if (String[].class.isInstance(value)) {
String[] array = (String[]) value;
return StringArrayConverter.convertToString(array);
}
PropertyEditor editor = PropertyEditorManager.findEditor(type);
if (editor != null) {
editor.setValue(value);
return editor.getAsText();
// special for String to List<ActiveMQDestination> as we do not want to use a PropertyEditor for that
if (List.class.isInstance(value)) {
// if the list is a ActiveMQDestination, then return a comma list
String answer = StringToListOfActiveMQDestinationConverter.convertFromActiveMQDestination(value);
if (answer != null) {
return answer;
}
}
TypeConversionSupport.Converter converter = TypeConversionSupport.lookupConverter(value.getClass(), String.class);
if (converter != null) {
return (String) converter.convert(value);
} else {
throw new IllegalArgumentException("Cannot convert from " + value.getClass()
+ " to " + to + " with value " + value);
}
return null;
}
private static Method findSetterMethod(Class clazz, String name) {
@ -251,19 +269,6 @@ public final class IntrospectionSupport {
return null;
}
private static boolean isSettableType(Class clazz) {
// special for String[]
if (clazz.isAssignableFrom(String[].class)) {
return true;
}
if (PropertyEditorManager.findEditor(clazz) != null) {
return true;
}
return false;
}
public static String toString(Object target) {
return toString(target, Object.class, null);
}
@ -349,7 +354,7 @@ public final class IntrospectionSupport {
}
map.put(field.getName(), o);
} catch (Throwable e) {
e.printStackTrace();
LOG.debug("Error getting field " + field + " on class " + startClass + ". This exception is ignored.", e);
}
}

View File

@ -1,49 +0,0 @@
/**
* 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.activemq.util;
import java.beans.PropertyEditorSupport;
import java.util.ArrayList;
import org.apache.activemq.command.ActiveMQDestination;
import org.springframework.util.StringUtils;
/**
* Used to serialize lists of ActiveMQDestinations.
* @see org.apache.activemq.util.IntrospectionSupport
*/
@Deprecated
public class ListEditor extends PropertyEditorSupport {
public static final String DEFAULT_SEPARATOR = ",";
public String getAsText() {
return getValue().toString();
}
public void setAsText(String text) throws IllegalArgumentException {
text = text.substring(1, text.length() - 1);
String[] array = StringUtils.delimitedListToStringArray(text, ListEditor.DEFAULT_SEPARATOR, null);
ArrayList<ActiveMQDestination> list = new ArrayList<ActiveMQDestination>();
for (String item : array) {
list.add(ActiveMQDestination.createDestination(item.trim(), ActiveMQDestination.QUEUE_TYPE));
}
setValue(list);
}
}

View File

@ -21,10 +21,14 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Used by xbean to set integers.
* <p/>
* <b>Important: </b> Do not use this for other purposes than xbean, as property editors
* are not thread safe, and they are slow to use.
* <p/>
* Converts string values like "20 Mb", "1024kb", and "1g" to int values in
* bytes.
*/
@Deprecated
public class MemoryIntPropertyEditor extends PropertyEditorSupport {
public void setAsText(String text) throws IllegalArgumentException {

View File

@ -21,10 +21,14 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Used by xbean to set longs.
* <p/>
* <b>Important: </b> Do not use this for other purposes than xbean, as property editors
* are not thread safe, and they are slow to use.
* <p/>
* Converts string values like "20 Mb", "1024kb", and "1g" to long values in
* bytes.
*/
@Deprecated
public class MemoryPropertyEditor extends PropertyEditorSupport {
public void setAsText(String text) throws IllegalArgumentException {

View File

@ -0,0 +1,84 @@
/**
* 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.activemq.util;
import java.util.ArrayList;
import java.util.List;
import org.apache.activemq.command.ActiveMQDestination;
import org.springframework.util.StringUtils;
/**
* Special converter for String -> List<ActiveMQDestination> to be used instead of a
* {@link java.beans.PropertyEditor} which otherwise causes
* memory leaks as the JDK {@link java.beans.PropertyEditorManager}
* is a static class and has strong references to classes, causing
* problems in hot-deployment environments.
*/
public class StringToListOfActiveMQDestinationConverter {
public static List<ActiveMQDestination> convertToActiveMQDestination(Object value) {
if (value == null) {
return null;
}
// text must be enclosed with []
String text = value.toString();
if (text.startsWith("[") && text.endsWith("]")) {
text = text.substring(1, text.length() - 1);
String[] array = StringUtils.delimitedListToStringArray(text, ",", null);
List<ActiveMQDestination> list = new ArrayList<ActiveMQDestination>();
for (String item : array) {
list.add(ActiveMQDestination.createDestination(item.trim(), ActiveMQDestination.QUEUE_TYPE));
}
return list;
} else {
return null;
}
}
public static String convertFromActiveMQDestination(Object value) {
if (value == null) {
return null;
}
StringBuilder sb = new StringBuilder("[");
if (value instanceof List) {
List list = (List) value;
for (int i = 0; i < list.size(); i++) {
Object e = list.get(i);
if (e instanceof ActiveMQDestination) {
ActiveMQDestination destination = (ActiveMQDestination) e;
sb.append(destination);
if (i < list.size() - 1) {
sb.append(", ");
}
}
}
}
sb.append("]");
if (sb.length() > 2) {
return sb.toString();
} else {
return null;
}
}
}

View File

@ -16,12 +16,17 @@
*/
package org.apache.activemq.util;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.apache.activemq.command.ActiveMQDestination;
/**
* Type conversion support for ActiveMQ.
*/
public final class TypeConversionSupport {
private static class ConversionKey {
@ -45,7 +50,7 @@ public final class TypeConversionSupport {
}
}
interface Converter {
public interface Converter {
Object convert(Object value);
}
@ -138,16 +143,25 @@ public final class TypeConversionSupport {
return ActiveMQDestination.createDestination((String)value, ActiveMQDestination.QUEUE_TYPE);
}
});
CONVERSION_MAP.put(new ConversionKey(String.class, URI.class), new Converter() {
public Object convert(Object value) {
String text = value.toString();
try {
return new URI(text);
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
});
}
private TypeConversionSupport() {
}
public static Object convert(Object value, Class type) {
public static Object convert(Object value, Class to) {
if (value == null) {
// lets avoid NullPointerException when converting to boolean for null values
if (boolean.class.isAssignableFrom(type)) {
if (boolean.class.isAssignableFrom(to)) {
return Boolean.FALSE;
}
return null;
@ -155,12 +169,12 @@ public final class TypeConversionSupport {
// eager same instance type test to avoid the overhead of invoking the type converter
// if already same type
if (type.isInstance(value)) {
return type.cast(value);
if (to.isInstance(value)) {
return to.cast(value);
}
// lookup converter
Converter c = CONVERSION_MAP.get(new ConversionKey(value.getClass(), type));
Converter c = lookupConverter(value.getClass(), to);
if (c != null) {
return c.convert(value);
} else {
@ -168,4 +182,42 @@ public final class TypeConversionSupport {
}
}
public static Converter lookupConverter(Class from, Class to) {
// use wrapped type for primitives
if (from.isPrimitive()) {
from = convertPrimitiveTypeToWrapperType(from);
}
if (to.isPrimitive()) {
to = convertPrimitiveTypeToWrapperType(to);
}
return CONVERSION_MAP.get(new ConversionKey(from, to));
}
/**
* Converts primitive types such as int to its wrapper type like
* {@link Integer}
*/
private static Class<?> convertPrimitiveTypeToWrapperType(Class<?> type) {
Class<?> rc = type;
if (type.isPrimitive()) {
if (type == int.class) {
rc = Integer.class;
} else if (type == long.class) {
rc = Long.class;
} else if (type == double.class) {
rc = Double.class;
} else if (type == float.class) {
rc = Float.class;
} else if (type == short.class) {
rc = Short.class;
} else if (type == byte.class) {
rc = Byte.class;
} else if (type == boolean.class) {
rc = Boolean.class;
}
}
return rc;
}
}

View File

@ -52,6 +52,7 @@ public class ReflectionSupportTest extends TestCase {
map.put("favorites", favoritesString);
map.put("nonFavorites", nonFavoritesString);
map.put("others", null);
map.put("systems", "windows,mac");
IntrospectionSupport.setProperties(pojo, map);
@ -62,6 +63,8 @@ public class ReflectionSupportTest extends TestCase {
assertEquals(favorites, pojo.getFavorites());
assertEquals(nonFavorites, pojo.getNonFavorites());
assertNull(pojo.getOthers());
assertEquals("windows", pojo.getSystems()[0]);
assertEquals("mac", pojo.getSystems()[1]);
}
public void testGetProperties() {
@ -72,6 +75,7 @@ public class ReflectionSupportTest extends TestCase {
pojo.setFavorites(favorites);
pojo.setNonFavorites(nonFavorites);
pojo.setOthers(null);
pojo.setSystems(new String[]{"windows", "mac"});
Properties props = new Properties();
@ -79,10 +83,11 @@ public class ReflectionSupportTest extends TestCase {
assertEquals("Dejan", props.get("name"));
assertEquals("31", props.get("age"));
assertEquals("True", props.get("enabled"));
assertEquals("true", props.get("enabled"));
assertEquals(favoritesString, props.get("favorites"));
assertEquals(nonFavoritesString, props.get("nonFavorites"));
assertNull(props.get("others"));
assertEquals("windows,mac", props.get("systems"));
}
public void testSetBoolean() {

View File

@ -31,6 +31,7 @@ public class SimplePojo {
List<ActiveMQDestination> favorites = new ArrayList<ActiveMQDestination>();
List<ActiveMQDestination> nonFavorites = new ArrayList<ActiveMQDestination>();
List<ActiveMQDestination> others = new ArrayList<ActiveMQDestination>();
String[] systems;
public int getAge() {
return age;
@ -74,5 +75,10 @@ public class SimplePojo {
public void setOthers(List<ActiveMQDestination> others) {
this.others = others;
}
public String[] getSystems() {
return systems;
}
public void setSystems(String[] systems) {
this.systems = systems;
}
}