mirror of https://github.com/apache/activemq.git
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:
parent
fef7f54a17
commit
1802116df8
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue