001/** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.activemq.util; 018 019import java.lang.reflect.Field; 020import java.lang.reflect.Method; 021import java.lang.reflect.Modifier; 022import java.util.Arrays; 023import java.util.HashMap; 024import java.util.Iterator; 025import java.util.LinkedHashMap; 026import java.util.List; 027import java.util.Locale; 028import java.util.Map; 029import java.util.Map.Entry; 030import java.util.Set; 031 032import javax.net.ssl.SSLServerSocket; 033 034import org.apache.activemq.command.ActiveMQDestination; 035import org.slf4j.Logger; 036import org.slf4j.LoggerFactory; 037 038public final class IntrospectionSupport { 039 040 private static final Logger LOG = LoggerFactory.getLogger(IntrospectionSupport.class); 041 042 private IntrospectionSupport() { 043 } 044 045 public static boolean getProperties(Object target, Map props, String optionPrefix) { 046 047 boolean rc = false; 048 if (target == null) { 049 throw new IllegalArgumentException("target was null."); 050 } 051 if (props == null) { 052 throw new IllegalArgumentException("props was null."); 053 } 054 055 if (optionPrefix == null) { 056 optionPrefix = ""; 057 } 058 059 Class<?> clazz = target.getClass(); 060 Method[] methods = clazz.getMethods(); 061 for (Method method : methods) { 062 String name = method.getName(); 063 Class<?> type = method.getReturnType(); 064 Class<?> params[] = method.getParameterTypes(); 065 if ((name.startsWith("is") || name.startsWith("get")) && params.length == 0 && type != null) { 066 067 try { 068 069 Object value = method.invoke(target); 070 if (value == null) { 071 continue; 072 } 073 074 String strValue = convertToString(value, type); 075 if (strValue == null) { 076 continue; 077 } 078 if (name.startsWith("get")) { 079 name = name.substring(3, 4).toLowerCase(Locale.ENGLISH) 080 + name.substring(4); 081 } else { 082 name = name.substring(2, 3).toLowerCase(Locale.ENGLISH) 083 + name.substring(3); 084 } 085 props.put(optionPrefix + name, strValue); 086 rc = true; 087 088 } catch (Exception ignore) { 089 } 090 } 091 } 092 093 return rc; 094 } 095 096 public static boolean setProperties(Object target, Map<String, ?> props, String optionPrefix) { 097 boolean rc = false; 098 if (target == null) { 099 throw new IllegalArgumentException("target was null."); 100 } 101 if (props == null) { 102 throw new IllegalArgumentException("props was null."); 103 } 104 105 for (Iterator<String> iter = props.keySet().iterator(); iter.hasNext();) { 106 String name = iter.next(); 107 if (name.startsWith(optionPrefix)) { 108 Object value = props.get(name); 109 name = name.substring(optionPrefix.length()); 110 if (setProperty(target, name, value)) { 111 iter.remove(); 112 rc = true; 113 } 114 } 115 } 116 return rc; 117 } 118 119 public static Map<String, Object> extractProperties(Map props, String optionPrefix) { 120 if (props == null) { 121 throw new IllegalArgumentException("props was null."); 122 } 123 124 HashMap<String, Object> rc = new HashMap<String, Object>(props.size()); 125 126 for (Iterator<?> iter = props.keySet().iterator(); iter.hasNext();) { 127 String name = (String)iter.next(); 128 if (name.startsWith(optionPrefix)) { 129 Object value = props.get(name); 130 name = name.substring(optionPrefix.length()); 131 rc.put(name, value); 132 iter.remove(); 133 } 134 } 135 136 return rc; 137 } 138 139 public static boolean setProperties(Object target, Map<?, ?> props) { 140 return setProperties(target, props, true); 141 } 142 143 public static boolean setProperties(Object target, Map<?, ?> props, boolean removeIfSet) { 144 boolean rc = false; 145 146 if (target == null) { 147 throw new IllegalArgumentException("target was null."); 148 } 149 if (props == null) { 150 throw new IllegalArgumentException("props was null."); 151 } 152 153 for (Iterator<?> iter = props.entrySet().iterator(); iter.hasNext();) { 154 Map.Entry<?,?> entry = (Entry<?,?>)iter.next(); 155 if (setProperty(target, (String)entry.getKey(), entry.getValue())) { 156 if (removeIfSet) { 157 iter.remove(); 158 } 159 rc = true; 160 } 161 } 162 163 return rc; 164 } 165 166 public static boolean setProperty(Object target, String name, Object value) { 167 try { 168 Class<?> clazz = target.getClass(); 169 if (target instanceof SSLServerSocket) { 170 // overcome illegal access issues with internal implementation class 171 clazz = SSLServerSocket.class; 172 } 173 Method setter = findSetterMethod(clazz, name); 174 if (setter == null) { 175 return false; 176 } 177 178 // JDK 11: class or setter might not be publicly accessible 179 setter.setAccessible(true); 180 181 // If the type is null or it matches the needed type, just use the 182 // value directly 183 if (value == null || value.getClass() == setter.getParameterTypes()[0]) { 184 setter.invoke(target, value); 185 } else { 186 // We need to convert it 187 setter.invoke(target, convert(value, setter.getParameterTypes()[0])); 188 } 189 return true; 190 } catch (Exception e) { 191 LOG.error(String.format("Could not set property %s on %s", name, target), e); 192 return false; 193 } 194 } 195 196 private static Object convert(Object value, Class to) { 197 if (value == null) { 198 // lets avoid NullPointerException when converting to boolean for null values 199 if (boolean.class.isAssignableFrom(to)) { 200 return Boolean.FALSE; 201 } 202 return null; 203 } 204 205 // eager same instance type test to avoid the overhead of invoking the type converter 206 // if already same type 207 if (to.isAssignableFrom(value.getClass())) { 208 return to.cast(value); 209 } 210 211 // special for String[] as we do not want to use a PropertyEditor for that 212 if (to.isAssignableFrom(String[].class)) { 213 return StringArrayConverter.convertToStringArray(value); 214 } 215 216 // special for String to List<ActiveMQDestination> as we do not want to use a PropertyEditor for that 217 if (value.getClass().equals(String.class) && to.equals(List.class)) { 218 Object answer = StringToListOfActiveMQDestinationConverter.convertToActiveMQDestination(value); 219 if (answer != null) { 220 return answer; 221 } 222 } 223 224 TypeConversionSupport.Converter converter = TypeConversionSupport.lookupConverter(value.getClass(), to); 225 if (converter != null) { 226 return converter.convert(value); 227 } else { 228 throw new IllegalArgumentException("Cannot convert from " + value.getClass() 229 + " to " + to + " with value " + value); 230 } 231 } 232 233 public static String convertToString(Object value, Class to) { 234 if (value == null) { 235 return null; 236 } 237 238 // already a String 239 if (value instanceof String) { 240 return (String) value; 241 } 242 243 // special for String[] as we do not want to use a PropertyEditor for that 244 if (String[].class.isInstance(value)) { 245 String[] array = (String[]) value; 246 return StringArrayConverter.convertToString(array); 247 } 248 249 // special for String to List<ActiveMQDestination> as we do not want to use a PropertyEditor for that 250 if (List.class.isInstance(value)) { 251 // if the list is a ActiveMQDestination, then return a comma list 252 String answer = StringToListOfActiveMQDestinationConverter.convertFromActiveMQDestination(value); 253 if (answer != null) { 254 return answer; 255 } 256 } 257 258 TypeConversionSupport.Converter converter = TypeConversionSupport.lookupConverter(value.getClass(), String.class); 259 if (converter != null) { 260 return (String) converter.convert(value); 261 } else { 262 throw new IllegalArgumentException("Cannot convert from " + value.getClass() 263 + " to " + to + " with value " + value); 264 } 265 } 266 267 public static Method findSetterMethod(Class clazz, String name) { 268 // Build the method name. 269 name = "set" + Character.toUpperCase(name.charAt(0)) + name.substring(1); 270 Method[] methods = clazz.getMethods(); 271 for (Method method : methods) { 272 Class<?> params[] = method.getParameterTypes(); 273 if (method.getName().equals(name) && params.length == 1 ) { 274 return method; 275 } 276 } 277 return null; 278 } 279 280 public static Method findGetterMethod(Class clazz, String name) { 281 // Build the method name. 282 name = "get" + Character.toUpperCase(name.charAt(0)) + name.substring(1); 283 Method[] methods = clazz.getMethods(); 284 for (Method method : methods) { 285 Class<?> params[] = method.getParameterTypes(); 286 if (method.getName().equals(name) && params.length == 0 ) { 287 return method; 288 } 289 } 290 return null; 291 } 292 293 public static String toString(Object target) { 294 return toString(target, Object.class, null); 295 } 296 297 public static String toString(Object target, Class stopClass) { 298 return toString(target, stopClass, null); 299 } 300 301 public static String toString(Object target, Class stopClass, Map<String, Object> overrideFields) { 302 LinkedHashMap<String, Object> map = new LinkedHashMap<String, Object>(); 303 addFields(target, target.getClass(), stopClass, map); 304 if (overrideFields != null) { 305 for(String key : overrideFields.keySet()) { 306 Object value = overrideFields.get(key); 307 map.put(key, value); 308 } 309 310 } 311 StringBuffer buffer = new StringBuffer(simpleName(target.getClass())); 312 buffer.append(" {"); 313 Set<Entry<String, Object>> entrySet = map.entrySet(); 314 boolean first = true; 315 for (Map.Entry<String,Object> entry : entrySet) { 316 Object value = entry.getValue(); 317 Object key = entry.getKey(); 318 if (first) { 319 first = false; 320 } else { 321 buffer.append(", "); 322 } 323 buffer.append(key); 324 buffer.append(" = "); 325 326 appendToString(buffer, key, value); 327 } 328 buffer.append("}"); 329 return buffer.toString(); 330 } 331 332 protected static void appendToString(StringBuffer buffer, Object key, Object value) { 333 if (value instanceof ActiveMQDestination) { 334 ActiveMQDestination destination = (ActiveMQDestination)value; 335 buffer.append(destination.getQualifiedName()); 336 } else if (key.toString().toLowerCase(Locale.ENGLISH).contains("password")){ 337 buffer.append("*****"); 338 } else { 339 buffer.append(value); 340 } 341 } 342 343 public static String simpleName(Class clazz) { 344 String name = clazz.getName(); 345 int p = name.lastIndexOf("."); 346 if (p >= 0) { 347 name = name.substring(p + 1); 348 } 349 return name; 350 } 351 352 private static void addFields(Object target, Class startClass, Class<Object> stopClass, LinkedHashMap<String, Object> map) { 353 354 if (startClass != stopClass) { 355 addFields(target, startClass.getSuperclass(), stopClass, map); 356 } 357 358 Field[] fields = startClass.getDeclaredFields(); 359 for (Field field : fields) { 360 if (Modifier.isStatic(field.getModifiers()) || Modifier.isTransient(field.getModifiers()) 361 || Modifier.isPrivate(field.getModifiers())) { 362 continue; 363 } 364 365 try { 366 field.setAccessible(true); 367 Object o = field.get(target); 368 if (o != null && o.getClass().isArray()) { 369 try { 370 o = Arrays.asList((Object[])o); 371 } catch (Exception e) { 372 } 373 } 374 map.put(field.getName(), o); 375 } catch (Exception e) { 376 LOG.debug("Error getting field " + field + " on class " + startClass + ". This exception is ignored.", e); 377 } 378 } 379 } 380}