| 1 | package org.jtoolkit.essence.app.impl; |
| 2 | |
| 3 | import org.apache.commons.logging.Log; |
| 4 | import static org.apache.commons.logging.LogFactory.getLog; |
| 5 | import org.jetbrains.annotations.Nullable; |
| 6 | import static org.jtoolkit.essence.app.pojo.impl.DataValueClass.*; |
| 7 | import org.jtoolkit.essence.utils.Named; |
| 8 | |
| 9 | import javax.management.*; |
| 10 | import java.lang.reflect.Field; |
| 11 | import java.lang.reflect.InvocationTargetException; |
| 12 | import java.lang.reflect.Method; |
| 13 | import java.lang.reflect.Modifier; |
| 14 | import java.util.ArrayList; |
| 15 | import static java.util.Arrays.asList; |
| 16 | import java.util.LinkedHashMap; |
| 17 | import java.util.List; |
| 18 | import java.util.Map; |
| 19 | |
| 20 | /* |
| 21 | Copyright 2006 Peter Lawrey |
| 22 | |
| 23 | Licensed under the Apache License, Version 2.0 (the "License"); |
| 24 | you may not use this file except in compliance with the License. |
| 25 | You may obtain a copy of the License at |
| 26 | |
| 27 | http://www.apache.org/licenses/LICENSE-2.0 |
| 28 | |
| 29 | Unless required by applicable law or agreed to in writing, software |
| 30 | distributed under the License is distributed on an "AS IS" BASIS, |
| 31 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 32 | See the License for the specific language governing permissions and |
| 33 | limitations under the License. |
| 34 | */ |
| 35 | /** |
| 36 | * @author Peter Lawrey |
| 37 | */ |
| 38 | public class DynamicMBeanWrapper<T> implements DynamicMBean { |
| 39 | private static final Log LOG = getLog(DynamicMBeanWrapper.class); |
| 40 | private static final MBeanConstructorInfo[] CONSTRUCTORS = {}; |
| 41 | // private static final int MAX_FIELDS_LENGTH = 1000; |
| 42 | // private static final int MAX_FIELD_WIDTH = 64; |
| 43 | // private static final int MAX_VALUE_WIDTH = 128; |
| 44 | |
| 45 | private final T comp; |
| 46 | private final MBeanInfo mBeanInfo; |
| 47 | private final Map<String, Method> settersMap = new LinkedHashMap<String, Method>(); |
| 48 | private final Map<String, Method> methodMap = new LinkedHashMap<String, Method>(); |
| 49 | private final Map<String, Field> fieldMap = new LinkedHashMap<String, Field>(); |
| 50 | private static final MBeanNotificationInfo[] NO_M_BEAN_NOTIFICATION_INFO = new MBeanNotificationInfo[0]; |
| 51 | |
| 52 | public DynamicMBeanWrapper(T comp, String description) { |
| 53 | this.comp = comp; |
| 54 | Class<? extends Object> type = comp.getClass(); |
| 55 | List<Field> fields = getDeclaredFields(type); |
| 56 | List<Method> setters = getSetters(type); |
| 57 | for (Method m : setters) { |
| 58 | String name = m.getName(); |
| 59 | name = Character.toLowerCase(name.charAt(3)) + name.substring(4); |
| 60 | settersMap.put(name, m); |
| 61 | } |
| 62 | MBeanAttributeInfo[] attributes = new MBeanAttributeInfo[fields.size()]; |
| 63 | for (int i = 0; i < fields.size(); i++) { |
| 64 | Field field = fields.get(i); |
| 65 | String name = field.getName(); |
| 66 | fieldMap.put(name, field); |
| 67 | attributes[i] = new MBeanAttributeInfo(name, field.getType().getName(), name, true, settersMap.get(name) != null, false); |
| 68 | } |
| 69 | List<Method> methods = getDeclaredMethods(type); |
| 70 | MBeanOperationInfo[] operations = new MBeanOperationInfo[methods.size()]; |
| 71 | for (int i = 0; i < methods.size(); i++) { |
| 72 | Method method = methods.get(i); |
| 73 | Class<?>[] parameterTypes = method.getParameterTypes(); |
| 74 | MBeanParameterInfo[] signature = new MBeanParameterInfo[parameterTypes.length]; |
| 75 | String[] signatureArr = new String[parameterTypes.length]; |
| 76 | for (int j = 0; j < parameterTypes.length; j++) { |
| 77 | String sigName = parameterTypes[j].getName(); |
| 78 | signatureArr[j] = sigName; |
| 79 | signature[j] = new MBeanParameterInfo("p" + j, sigName, "p" + j); |
| 80 | } |
| 81 | operations[i] = new MBeanOperationInfo(method.getName(), method.getName(), signature, method.getReturnType().getName(), MBeanOperationInfo.UNKNOWN); |
| 82 | methodMap.put(method.getName() + asList(signatureArr), method); |
| 83 | } |
| 84 | MBeanNotificationInfo[] notifications = NO_M_BEAN_NOTIFICATION_INFO; |
| 85 | mBeanInfo = new MBeanInfo(type.getName(), description, attributes, CONSTRUCTORS, operations, notifications); |
| 86 | } |
| 87 | |
| 88 | private static List<Method> getDeclaredMethods(Class<? extends Object> type) { |
| 89 | List<Method> methods = new ArrayList<Method>(); |
| 90 | Class<?> parent = type.getSuperclass(); |
| 91 | if (parent != null && parent != Object.class) |
| 92 | methods.addAll(getDeclaredMethods(parent)); |
| 93 | LOOP: |
| 94 | for (Method method : type.getDeclaredMethods()) { |
| 95 | if ((method.getModifiers() & Modifier.STATIC) != 0) continue; |
| 96 | // is it an accessor method. |
| 97 | if (method.getName().startsWith("access$")) continue; |
| 98 | /// is it a setter? |
| 99 | if (isSetter(method) || isGetter(method)) continue; |
| 100 | for (Class type2 : method.getParameterTypes()) |
| 101 | if (!isBaseType(type2)) |
| 102 | continue LOOP; |
| 103 | Class<?> returnType = method.getReturnType(); |
| 104 | if (!isBaseType(returnType) && returnType != void.class) continue; |
| 105 | method.setAccessible(true); |
| 106 | methods.add(method); |
| 107 | } |
| 108 | return methods; |
| 109 | } |
| 110 | |
| 111 | private static boolean isSetter(Method method) { |
| 112 | String name = method.getName(); |
| 113 | Class<?>[] classes = method.getParameterTypes(); |
| 114 | return name.startsWith("set") && name.length() > 3 && classes.length == 1 && isBaseType(classes[0]); |
| 115 | } |
| 116 | |
| 117 | private static boolean isGetter(Method method) { |
| 118 | String name = method.getName(); |
| 119 | Class<?>[] classes = method.getParameterTypes(); |
| 120 | return name.startsWith("get") && name.length() > 3 && classes.length == 0; |
| 121 | } |
| 122 | |
| 123 | private static List<Method> getSetters(Class type) { |
| 124 | List<Method> ret = new ArrayList<Method>(); |
| 125 | Class<?> parent = type.getSuperclass(); |
| 126 | if (parent != null && parent != Object.class) |
| 127 | ret.addAll(getSetters(parent)); |
| 128 | for (Method m : type.getDeclaredMethods()) { |
| 129 | if (isSetter(m)) { |
| 130 | m.setAccessible(true); |
| 131 | ret.add(m); |
| 132 | } |
| 133 | } |
| 134 | return ret; |
| 135 | } |
| 136 | |
| 137 | private static List<Field> getDeclaredFields(Class<? extends Object> type) { |
| 138 | List<Field> fields = new ArrayList<Field>(); |
| 139 | Class<?> parent = type.getSuperclass(); |
| 140 | if (parent != null && parent != Object.class) |
| 141 | fields.addAll(getDeclaredFields(parent)); |
| 142 | for (Field field : type.getDeclaredFields()) |
| 143 | if (isBaseType(field.getType()) || isAssignableFrom(Named.class, field.getType())) { |
| 144 | field.setAccessible(true); |
| 145 | fields.add(field); |
| 146 | } |
| 147 | return fields; |
| 148 | } |
| 149 | |
| 150 | @Nullable public Object getAttribute(String attribute) throws AttributeNotFoundException, MBeanException, ReflectionException { |
| 151 | try { |
| 152 | Field field = fieldMap.get(attribute); |
| 153 | if (field == null) |
| 154 | throw new AttributeNotFoundException(attribute); |
| 155 | Object obj = field.get(comp); |
| 156 | if (obj == null) return null; |
| 157 | if (isPrimative(obj.getClass())) return obj; |
| 158 | if (obj.getClass().getPackage().getName().startsWith("java")) return obj; |
| 159 | if (obj instanceof Named) |
| 160 | obj = "Reference => " + ((Named) obj).getName(); |
| 161 | else |
| 162 | obj = obj.toString(); |
| 163 | return obj; |
| 164 | } catch (IllegalAccessException e) { |
| 165 | return handleAccessException(e, attribute); |
| 166 | } |
| 167 | } |
| 168 | |
| 169 | private static Object handleAccessException(IllegalAccessException e, String attribute) throws MBeanException { |
| 170 | throw new MBeanException(e, "Unable to access " + attribute); |
| 171 | } |
| 172 | |
| 173 | public void setAttribute(Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException { |
| 174 | try { |
| 175 | Method method = settersMap.get(attribute.getName()); |
| 176 | if (method == null) throw new AttributeNotFoundException(attribute.getName()); |
| 177 | method.invoke(comp, cast(method.getParameterTypes()[0], attribute.getValue())); |
| 178 | } catch (IllegalAccessException e) { |
| 179 | handleAccessException(e, attribute.getName()); |
| 180 | } catch (InvocationTargetException e) { |
| 181 | throw new MBeanException(e.getCause() instanceof Exception ? (Exception) e.getCause() : e, getAttributeSetError(attribute)); |
| 182 | } |
| 183 | } |
| 184 | |
| 185 | private static String getAttributeSetError(Attribute attribute) { |
| 186 | return getThreadName() + ": Unable to set " + attribute.getName() + " to " + attribute.getValue(); |
| 187 | } |
| 188 | |
| 189 | public AttributeList getAttributes(String[] attributes) { |
| 190 | AttributeList ret = new AttributeList(); |
| 191 | for (String attribute : attributes) { |
| 192 | ret.add(getAttributeOrError(attribute)); |
| 193 | } |
| 194 | return ret; |
| 195 | } |
| 196 | |
| 197 | private Attribute getAttributeOrError(String attribute) { |
| 198 | Object value; |
| 199 | try { |
| 200 | value = getAttribute(attribute); |
| 201 | } catch (Exception e) { |
| 202 | LOG.warn(getThreadName() + ": Unable to get " + attribute, e); |
| 203 | value = e.toString(); |
| 204 | } |
| 205 | return new Attribute(attribute, value); |
| 206 | } |
| 207 | |
| 208 | public AttributeList setAttributes(AttributeList attributes) { |
| 209 | AttributeList ret = new AttributeList(); |
| 210 | //noinspection unchecked,CastToIncompatibleInterface |
| 211 | for (Attribute attribute : (Iterable<Attribute>) attributes.iterator()) { |
| 212 | String attrName = attribute.getName(); |
| 213 | try { |
| 214 | setAttribute(attribute); |
| 215 | } catch (Exception e) { |
| 216 | LOG.warn(getAttributeSetError(attribute), e); |
| 217 | } |
| 218 | ret.add(new Attribute(attrName, getAttributeOrError(attrName))); |
| 219 | } |
| 220 | return ret; |
| 221 | } |
| 222 | |
| 223 | private static String getThreadName() { |
| 224 | return Thread.currentThread().getName(); |
| 225 | } |
| 226 | |
| 227 | public Object invoke(String actionName, Object[] params, String[] signature) throws MBeanException, ReflectionException { |
| 228 | String name = actionName + asList(signature); |
| 229 | Method m = methodMap.get(name); |
| 230 | if (m == null) |
| 231 | throw new MBeanException(null, "Unable to find " + name); |
| 232 | try { |
| 233 | Object[] toInvoke = new Object[params.length]; |
| 234 | Class<?>[] parameterTypes = m.getParameterTypes(); |
| 235 | for (int i = 0; i < params.length; i++) |
| 236 | toInvoke[i] = cast(parameterTypes[i], params[i]); |
| 237 | return m.invoke(comp, toInvoke); |
| 238 | } catch (IllegalAccessException e) { |
| 239 | throw new ReflectionException(e); |
| 240 | } catch (InvocationTargetException e) { |
| 241 | throw new ReflectionException(e); |
| 242 | } |
| 243 | } |
| 244 | |
| 245 | public MBeanInfo getMBeanInfo() { |
| 246 | /* |
| 247 | try { |
| 248 | Method asMap = comp.getClass().getMethod("asMap"); |
| 249 | Map map2 = (Map) asMap.invoke(comp); |
| 250 | List<String> fields = new ArrayList<String>(); |
| 251 | List<Object> values = new ArrayList<Object>(); |
| 252 | try { |
| 253 | map2 = new TreeMap(map2); |
| 254 | } catch (RuntimeException ignored) { |
| 255 | // ignored. |
| 256 | } |
| 257 | fields.add("{size}"); |
| 258 | values.add(map2.size()); |
| 259 | for (Map.Entry entry : (Set<Map.Entry>) map2.entrySet()) { |
| 260 | fields.add(trim(entry.getKey(), MAX_FIELD_WIDTH)); |
| 261 | values.add(trim(entry.getValue(), MAX_VALUE_WIDTH)); |
| 262 | if (fields.size() > MAX_FIELDS_LENGTH) |
| 263 | break; |
| 264 | } |
| 265 | */ |
| 266 | |
| 267 | //Descriptor descriptor = new ImmutableDescriptor(fields.toArray(new String[fields.size()]), values.toArray()); |
| 268 | return new MBeanInfo(mBeanInfo.getClassName(), mBeanInfo.getDescription(), mBeanInfo.getAttributes(), mBeanInfo.getConstructors(), mBeanInfo.getOperations(), mBeanInfo.getNotifications()); //, descriptor); |
| 269 | /* |
| 270 | } catch (IllegalAccessException e) { |
| 271 | throw new IllegalStateException(e); |
| 272 | } catch (InvocationTargetException e) { |
| 273 | throw new IllegalStateException(e); |
| 274 | } catch (NoSuchMethodException ignored) { |
| 275 | // ignored. |
| 276 | } |
| 277 | return mBeanInfo; |
| 278 | */ |
| 279 | } |
| 280 | } |