diff --git a/activemq-core/src/main/java/org/apache/activemq/util/BooleanEditor.java b/activemq-core/src/main/java/org/apache/activemq/util/BooleanEditor.java index d96079343d..065affd038 100644 --- a/activemq-core/src/main/java/org/apache/activemq/util/BooleanEditor.java +++ b/activemq-core/src/main/java/org/apache/activemq/util/BooleanEditor.java @@ -18,7 +18,12 @@ package org.apache.activemq.util; import java.beans.PropertyEditorSupport; -@Deprecated +/** + * Used by xbean to set booleans. + *

+ * Important: 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() { diff --git a/activemq-core/src/main/java/org/apache/activemq/util/IntrospectionSupport.java b/activemq-core/src/main/java/org/apache/activemq/util/IntrospectionSupport.java index 7a2eee06ab..ef38c353e7 100755 --- a/activemq-core/src/main/java/org/apache/activemq/util/IntrospectionSupport.java +++ b/activemq-core/src/main/java/org/apache/activemq/util/IntrospectionSupport.java @@ -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 list = new ArrayList(); - 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 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 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); } } diff --git a/activemq-core/src/main/java/org/apache/activemq/util/ListEditor.java b/activemq-core/src/main/java/org/apache/activemq/util/ListEditor.java deleted file mode 100644 index 3cc5b14ee7..0000000000 --- a/activemq-core/src/main/java/org/apache/activemq/util/ListEditor.java +++ /dev/null @@ -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 list = new ArrayList(); - for (String item : array) { - list.add(ActiveMQDestination.createDestination(item.trim(), ActiveMQDestination.QUEUE_TYPE)); - } - setValue(list); - } - -} diff --git a/activemq-core/src/main/java/org/apache/activemq/util/MemoryIntPropertyEditor.java b/activemq-core/src/main/java/org/apache/activemq/util/MemoryIntPropertyEditor.java index 099e5b93a6..6c167a58fa 100644 --- a/activemq-core/src/main/java/org/apache/activemq/util/MemoryIntPropertyEditor.java +++ b/activemq-core/src/main/java/org/apache/activemq/util/MemoryIntPropertyEditor.java @@ -21,10 +21,14 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; /** + * Used by xbean to set integers. + *

+ * Important: Do not use this for other purposes than xbean, as property editors + * are not thread safe, and they are slow to use. + *

* 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 { diff --git a/activemq-core/src/main/java/org/apache/activemq/util/MemoryPropertyEditor.java b/activemq-core/src/main/java/org/apache/activemq/util/MemoryPropertyEditor.java index b906d3bd0b..09fe06e664 100644 --- a/activemq-core/src/main/java/org/apache/activemq/util/MemoryPropertyEditor.java +++ b/activemq-core/src/main/java/org/apache/activemq/util/MemoryPropertyEditor.java @@ -21,10 +21,14 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; /** + * Used by xbean to set longs. + *

+ * Important: Do not use this for other purposes than xbean, as property editors + * are not thread safe, and they are slow to use. + *

* 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 { diff --git a/activemq-core/src/main/java/org/apache/activemq/util/StringToListOfActiveMQDestinationConverter.java b/activemq-core/src/main/java/org/apache/activemq/util/StringToListOfActiveMQDestinationConverter.java new file mode 100644 index 0000000000..bd35513084 --- /dev/null +++ b/activemq-core/src/main/java/org/apache/activemq/util/StringToListOfActiveMQDestinationConverter.java @@ -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 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 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 list = new ArrayList(); + 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; + } + } + +} diff --git a/activemq-core/src/main/java/org/apache/activemq/util/TypeConversionSupport.java b/activemq-core/src/main/java/org/apache/activemq/util/TypeConversionSupport.java index 1363f85ed0..2b6dfa011d 100755 --- a/activemq-core/src/main/java/org/apache/activemq/util/TypeConversionSupport.java +++ b/activemq-core/src/main/java/org/apache/activemq/util/TypeConversionSupport.java @@ -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; + } + } diff --git a/activemq-core/src/test/java/org/apache/activemq/util/ReflectionSupportTest.java b/activemq-core/src/test/java/org/apache/activemq/util/ReflectionSupportTest.java index 0f0095b738..89759219f2 100755 --- a/activemq-core/src/test/java/org/apache/activemq/util/ReflectionSupportTest.java +++ b/activemq-core/src/test/java/org/apache/activemq/util/ReflectionSupportTest.java @@ -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() { diff --git a/activemq-core/src/test/java/org/apache/activemq/util/SimplePojo.java b/activemq-core/src/test/java/org/apache/activemq/util/SimplePojo.java index ac19075463..841e2a9c56 100755 --- a/activemq-core/src/test/java/org/apache/activemq/util/SimplePojo.java +++ b/activemq-core/src/test/java/org/apache/activemq/util/SimplePojo.java @@ -31,6 +31,7 @@ public class SimplePojo { List favorites = new ArrayList(); List nonFavorites = new ArrayList(); List others = new ArrayList(); + String[] systems; public int getAge() { return age; @@ -74,5 +75,10 @@ public class SimplePojo { public void setOthers(List others) { this.others = others; } - + public String[] getSystems() { + return systems; + } + public void setSystems(String[] systems) { + this.systems = systems; + } }