View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17  package org.apache.mina.integration.jmx;
    18
    19  import java.beans.IntrospectionException;
    20  import java.beans.Introspector;
    21  import java.beans.PropertyDescriptor;
    22  import java.beans.PropertyEditor;
    23  import java.lang.reflect.InvocationTargetException;
    24  import java.lang.reflect.Method;
    25  import java.net.SocketAddress;
    26  import java.util.ArrayList;
    27  import java.util.Collection;
    28  import java.util.Date;
    29  import java.util.HashMap;
    30  import java.util.Iterator;
    31  import java.util.LinkedHashMap;
    32  import java.util.LinkedHashSet;
    33  import java.util.List;
    34  import java.util.Map;
    35  import java.util.Set;
    36  import java.util.concurrent.ConcurrentHashMap;
    37  import java.util.concurrent.ThreadPoolExecutor;
    38
    39  import javax.management.Attribute;
    40  import javax.management.AttributeChangeNotification;
    41  import javax.management.AttributeList;
    42  import javax.management.AttributeNotFoundException;
    43  import javax.management.InstanceNotFoundException;
    44  import javax.management.ListenerNotFoundException;
    45  import javax.management.MBeanException;
    46  import javax.management.MBeanInfo;
    47  import javax.management.MBeanNotificationInfo;
    48  import javax.management.MBeanParameterInfo;
    49  import javax.management.MBeanRegistration;
    50  import javax.management.MBeanServer;
    51  import javax.management.Notification;
    52  import javax.management.NotificationFilter;
    53  import javax.management.NotificationListener;
    54  import javax.management.ObjectName;
    55  import javax.management.ReflectionException;
    56  import javax.management.RuntimeOperationsException;
    57  import javax.management.modelmbean.InvalidTargetObjectTypeException;
    58  import javax.management.modelmbean.ModelMBean;
    59  import javax.management.modelmbean.ModelMBeanAttributeInfo;
    60  import javax.management.modelmbean.ModelMBeanConstructorInfo;
    61  import javax.management.modelmbean.ModelMBeanInfo;
    62  import javax.management.modelmbean.ModelMBeanInfoSupport;
    63  import javax.management.modelmbean.ModelMBeanNotificationInfo;
    64  import javax.management.modelmbean.ModelMBeanOperationInfo;
    65
    66  import ognl.ExpressionSyntaxException;
    67  import ognl.InappropriateExpressionException;
    68  import ognl.NoSuchPropertyException;
    69  import ognl.Ognl;
    70  import ognl.OgnlContext;
    71  import ognl.OgnlException;
    72  import ognl.OgnlRuntime;
    73  import ognl.TypeConverter;
    74
    75  import org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder;
    76  import org.apache.mina.core.filterchain.IoFilter;
    77  import org.apache.mina.core.filterchain.IoFilterChain;
    78  import org.apache.mina.core.filterchain.IoFilterChainBuilder;
    79  import org.apache.mina.core.service.IoAcceptor;
    80  import org.apache.mina.core.service.IoHandler;
    81  import org.apache.mina.core.service.IoService;
    82  import org.apache.mina.core.service.TransportMetadata;
    83  import org.apache.mina.core.session.IoSession;
    84  import org.apache.mina.core.session.IoSessionDataStructureFactory;
    85  import org.apache.mina.filter.executor.ExecutorFilter;
    86  import org.apache.mina.integration.beans.CollectionEditor;
    87  import org.apache.mina.integration.beans.ListEditor;
    88  import org.apache.mina.integration.beans.MapEditor;
    89  import org.apache.mina.integration.beans.PropertyEditorFactory;
    90  import org.apache.mina.integration.beans.SetEditor;
    91  import org.apache.mina.integration.ognl.IoFilterPropertyAccessor;
    92  import org.apache.mina.integration.ognl.IoServicePropertyAccessor;
    93  import org.apache.mina.integration.ognl.IoSessionPropertyAccessor;
    94  import org.apache.mina.integration.ognl.PropertyTypeConverter;
    95  import org.slf4j.Logger;
    96  import org.slf4j.LoggerFactory;
    97
    98  /**
    99   * A {@link ModelMBean} wrapper implementation for a POJO.
    100  *
    101  * @author <a href="http://mina.apache.org">Apache MINA Project</a>
    102  *
    103  * @param <T> the type of the managed object
    104  */
    105 public class ObjectMBean<T> implements ModelMBean, MBeanRegistration {
    106
    107     private static final Map<ObjectName, Object> sources = new ConcurrentHashMap<ObjectName, Object>();
    108
    109     public static Object getSource(ObjectName oname) {
    110         return sources.get(oname);
    111     }
    112
    113     static {
    114         OgnlRuntime.setPropertyAccessor(IoService.class, new IoServicePropertyAccessor());
    115         OgnlRuntime.setPropertyAccessor(IoSession.class, new IoSessionPropertyAccessor());
    116         OgnlRuntime.setPropertyAccessor(IoFilter.class, new IoFilterPropertyAccessor());
    117     }
    118
    119     protected final static Logger LOGGER = LoggerFactory.getLogger(ObjectMBean.class);
    120
    121     private final T source;
    122
    123     private final TransportMetadata transportMetadata;
    124
    125     private final MBeanInfo info;
    126
    127     private final Map<String, PropertyDescriptor> propertyDescriptors = new HashMap<String, PropertyDescriptor>();
    128
    129     private final TypeConverter typeConverter = new OgnlTypeConverter();
    130
    131     private volatile MBeanServer server;
    132
    133     private volatile ObjectName name;
    134
    135     /**
    136      * Creates a new instance with the specified POJO.
    137      */
    138     public ObjectMBean(T source) {
    139         if (source == null) {
    140             throw new IllegalArgumentException("source");
    141         }
    142
    143         this.source = source;
    144
    145         if (source instanceof IoService) {
    146             transportMetadata = ((IoService) source).getTransportMetadata();
    147         } else if (source instanceof IoSession) {
    148             transportMetadata = ((IoSession) source).getTransportMetadata();
    149         } else {
    150             transportMetadata = null;
    151         }
    152
    153         this.info = createModelMBeanInfo(source);
    154     }
    155
    156     public final Object getAttribute(String fqan) throws AttributeNotFoundException, MBeanException,
    157             ReflectionException {
    158         try {
    159             return convertValue(source.getClass(), fqan, getAttribute0(fqan), false);
    160         } catch (AttributeNotFoundException e) {
    161             // Do nothing
    162         } catch (Throwable e) {
    163             throwMBeanException(e);
    164         }
    165
    166         // Check if the attribute exist, if not throw an exception
    167         PropertyDescriptor pdesc = propertyDescriptors.get(fqan);
    168         if (pdesc == null) {
    169             throwMBeanException(new IllegalArgumentException("Unknown attribute: " + fqan));
    170         }
    171
    172         try {
    173
    174             Object parent = getParent(fqan);
    175             boolean writable = isWritable(source.getClass(), pdesc);
    176
    177             return convertValue(parent.getClass(), getLeafAttributeName(fqan),
    178                     getAttribute(source, fqan, pdesc.getPropertyType()), writable);
    179         } catch (Throwable e) {
    180             throwMBeanException(e);
    181         }
    182
    183         throw new IllegalStateException();
    184     }
    185
    186     public final void setAttribute(Attribute attribute) throws AttributeNotFoundException, MBeanException,
    187             ReflectionException {
    188         String aname = attribute.getName();
    189         Object avalue = attribute.getValue();
    190
    191         try {
    192             setAttribute0(aname, avalue);
    193         } catch (AttributeNotFoundException e) {
    194             // Do nothing
    195         } catch (Throwable e) {
    196             throwMBeanException(e);
    197         }
    198
    199         PropertyDescriptor pdesc = propertyDescriptors.get(aname);
    200         if (pdesc == null) {
    201             throwMBeanException(new IllegalArgumentException("Unknown attribute: " + aname));
    202         }
    203
    204         try {
    205             PropertyEditor e = getPropertyEditor(getParent(aname).getClass(), pdesc.getName(), pdesc.getPropertyType());
    206             e.setAsText((String) avalue);
    207             OgnlContext ctx = (OgnlContext) Ognl.createDefaultContext(source);
    208             ctx.setTypeConverter(typeConverter);
    209             Ognl.setValue(aname, ctx, source, e.getValue());
    210         } catch (Throwable e) {
    211             throwMBeanException(e);
    212         }
    213     }
    214
    215     public final Object invoke(String name, Object params[], String signature[]) throws MBeanException,
    216             ReflectionException {
    217
    218         // Handle synthetic operations first.
    219         if (name.equals("unregisterMBean")) {
    220             try {
    221                 server.unregisterMBean(this.name);
    222                 return null;
    223             } catch (InstanceNotFoundException e) {
    224                 throwMBeanException(e);
    225             }
    226         }
    227
    228         try {
    229             return convertValue(null, null, invoke0(name, params, signature), false);
    230         } catch (NoSuchMethodException e) {
    231             // Do nothing
    232         } catch (Throwable e) {
    233             throwMBeanException(e);
    234         }
    235
    236         // And then try reflection.
    237         Class<?>[] paramTypes = new Class[signature.length];
    238         for (int i = 0; i < paramTypes.length; i++) {
    239             try {
    240                 paramTypes[i] = getAttributeClass(signature[i]);
    241             } catch (ClassNotFoundException e) {
    242                 throwMBeanException(e);
    243             }
    244
    245             PropertyEditor e = getPropertyEditor(source.getClass(), "p" + i, paramTypes[i]);
    246             if (e == null) {
    247                 throwMBeanException(new RuntimeException("Conversion failure: " + params[i]));
    248             }
    249
    250             e.setValue(params[i]);
    251             params[i] = e.getAsText();
    252         }
    253
    254         try {
    255             // Find the right method.
    256             for (Method m : source.getClass().getMethods()) {
    257                 if (!m.getName().equalsIgnoreCase(name)) {
    258                     continue;
    259                 }
    260                 Class<?>[] methodParamTypes = m.getParameterTypes();
    261                 if (methodParamTypes.length != params.length) {
    262                     continue;
    263                 }
    264
    265                 Object[] convertedParams = new Object[params.length];
    266                 for (int i = 0; i < params.length; i++) {
    267                     if (Iterable.class.isAssignableFrom(methodParamTypes[i])) {
    268                         // Generics are not supported.
    269                         convertedParams = null;
    270                         break;
    271                     }
    272                     PropertyEditor e = getPropertyEditor(source.getClass(), "p" + i, methodParamTypes[i]);
    273                     if (e == null) {
    274                         convertedParams = null;
    275                         break;
    276                     }
    277
    278                     e.setAsText((String) params[i]);
    279                     convertedParams[i] = e.getValue();
    280                 }
    281                 if (convertedParams == null) {
    282                     continue;
    283                 }
    284
    285                 return convertValue(m.getReturnType(), "returnValue", m.invoke(source, convertedParams), false);
    286             }
    287
    288             // No methods matched.
    289             throw new IllegalArgumentException("Failed to find a matching operation: " + name);
    290         } catch (Throwable e) {
    291             throwMBeanException(e);
    292         }
    293
    294         throw new IllegalStateException();
    295     }
    296
    297     public final T getSource() {
    298         return source;
    299     }
    300
    301     public final MBeanServer getServer() {
    302         return server;
    303     }
    304
    305     public final ObjectName getName() {
    306         return name;
    307     }
    308
    309     public final MBeanInfo getMBeanInfo() {
    310         return info;
    311     }
    312
    313     public final AttributeList getAttributes(String names[]) {
    314         AttributeList answer = new AttributeList();
    315         for (int i = 0; i < names.length; i++) {
    316             try {
    317                 answer.add(new Attribute(names[i], getAttribute(names[i])));
    318             } catch (Exception e) {
    319                 // Ignore.
    320             }
    321         }
    322         return answer;
    323     }
    324
    325     public final AttributeList setAttributes(AttributeList attributes) {
    326         // Prepare and return our response, eating all exceptions
    327         String names[] = new String[attributes.size()];
    328         int n = 0;
    329         Iterator<Object> items = attributes.iterator();
    330         while (items.hasNext()) {
    331             Attribute item = (Attribute) items.next();
    332             names[n++] = item.getName();
    333             try {
    334                 setAttribute(item);
    335             } catch (Exception e) {
    336                 // Ignore all exceptions
    337             }
    338         }
    339
    340         return getAttributes(names);
    341     }
    342
    343     public final void setManagedResource(Object resource, String type) throws InstanceNotFoundException,
    344             InvalidTargetObjectTypeException, MBeanException {
    345         throw new RuntimeOperationsException(new UnsupportedOperationException());
    346
    347     }
    348
    349     public final void setModelMBeanInfo(ModelMBeanInfo info) throws MBeanException {
    350         throw new RuntimeOperationsException(new UnsupportedOperationException());
    351     }
    352
    353     @Override
    354     public final String toString() {
    355         return (source == null ? "" : source.toString());
    356     }
    357
    358     public void addAttributeChangeNotificationListener(NotificationListener listener, String name, Object handback) {
    359         // Do nothing
    360     }
    361
    362     public void removeAttributeChangeNotificationListener(NotificationListener listener, String name)
    363             throws ListenerNotFoundException {
    364         // Do nothing
    365     }
    366
    367     public void sendAttributeChangeNotification(AttributeChangeNotification notification) throws MBeanException {
    368         throw new RuntimeOperationsException(new UnsupportedOperationException());
    369     }
    370
    371     public void sendAttributeChangeNotification(Attribute oldValue, Attribute newValue) throws MBeanException {
    372         throw new RuntimeOperationsException(new UnsupportedOperationException());
    373     }
    374
    375     public void sendNotification(Notification notification) throws MBeanException {
    376         throw new RuntimeOperationsException(new UnsupportedOperationException());
    377     }
    378
    379     public void sendNotification(String message) throws MBeanException {
    380         throw new RuntimeOperationsException(new UnsupportedOperationException());
    381
    382     }
    383
    384     public void addNotificationListener(NotificationListener listener, NotificationFilter filter, Object handback)
    385             throws IllegalArgumentException {
    386         // Do nothing
    387     }
    388
    389     public MBeanNotificationInfo[] getNotificationInfo() {
    390         return new MBeanNotificationInfo[0];
    391     }
    392
    393     public void removeNotificationListener(NotificationListener listener) throws ListenerNotFoundException {
    394         // Do nothing
    395     }
    396
    397     public void load() throws InstanceNotFoundException, MBeanException, RuntimeOperationsException {
    398         throw new RuntimeOperationsException(new UnsupportedOperationException());
    399     }
    400
    401     public void store() throws InstanceNotFoundException, MBeanException, RuntimeOperationsException {
    402         throw new RuntimeOperationsException(new UnsupportedOperationException());
    403     }
    404
    405     public final ObjectName preRegister(MBeanServer server, ObjectName name) throws Exception {
    406         this.server = server;
    407         this.name = name;
    408         return name;
    409     }
    410
    411     public final void postRegister(Boolean registrationDone) {
    412         if (registrationDone) {
    413             sources.put(name, source);
    414         }
    415     }
    416
    417     public final void preDeregister() throws Exception {
    418         // Do nothing
    419     }
    420
    421     public final void postDeregister() {
    422         sources.remove(name);
    423         this.server = null;
    424         this.name = null;
    425     }
    426
    427     private MBeanInfo createModelMBeanInfo(T source) {
    428         String className = source.getClass().getName();
    429         String description = "";
    430
    431         ModelMBeanConstructorInfo[] constructors = new ModelMBeanConstructorInfo[0];
    432         ModelMBeanNotificationInfo[] notifications = new ModelMBeanNotificationInfo[0];
    433
    434         List<ModelMBeanAttributeInfo> attributes = new ArrayList<ModelMBeanAttributeInfo>();
    435         List<ModelMBeanOperationInfo> operations = new ArrayList<ModelMBeanOperationInfo>();
    436
    437         addAttributes(attributes, source);
    438         addExtraAttributes(attributes);
    439
    440         addOperations(operations, source);
    441         addExtraOperations(operations);
    442         operations.add(new ModelMBeanOperationInfo("unregisterMBean", "unregisterMBean", new MBeanParameterInfo[0],
    443                 void.class.getName(), ModelMBeanOperationInfo.ACTION));
    444
    445         return new ModelMBeanInfoSupport(className, description,
    446                 attributes.toArray(new ModelMBeanAttributeInfo[attributes.size()]), constructors,
    447                 operations.toArray(new ModelMBeanOperationInfo[operations.size()]), notifications);
    448     }
    449
    450     private void addAttributes(List<ModelMBeanAttributeInfo> attributes, Object object) {
    451         addAttributes(attributes, object, object.getClass(), "");
    452     }
    453
    454     private void addAttributes(List<ModelMBeanAttributeInfo> attributes, Object object, Class<?> type, String prefix) {
    455
    456         PropertyDescriptor[] pdescs;
    457         try {
    458             pdescs = Introspector.getBeanInfo(type).getPropertyDescriptors();
    459         } catch (IntrospectionException e) {
    460             return;
    461         }
    462
    463         for (PropertyDescriptor pdesc : pdescs) {
    464             // Ignore a write-only property.
    465             if (pdesc.getReadMethod() == null) {
    466                 continue;
    467             }
    468
    469             // Ignore unmanageable property.
    470             String attrName = pdesc.getName();
    471             Class<?> attrType = pdesc.getPropertyType();
    472             if (attrName.equals("class")) {
    473                 continue;
    474             }
    475             if (!isReadable(type, attrName)) {
    476                 continue;
    477             }
    478
    479             // Expand if possible.
    480             if (isExpandable(type, attrName)) {
    481                 expandAttribute(attributes, object, prefix, pdesc);
    482                 continue;
    483             }
    484
    485             // Ordinary property.
    486             String fqan = prefix + attrName;
    487             boolean writable = isWritable(type, pdesc);
    488             attributes.add(new ModelMBeanAttributeInfo(fqan, convertType(object.getClass(), attrName, attrType,
    489                     writable).getName(), pdesc.getShortDescription(), true, writable, false));
    490
    491             propertyDescriptors.put(fqan, pdesc);
    492         }
    493     }
    494
    495     private boolean isWritable(Class<?> type, PropertyDescriptor pdesc) {
    496         if (type == null) {
    497             throw new IllegalArgumentException("type");
    498         }
    499         if (pdesc == null) {
    500             return false;
    501         }
    502         String attrName = pdesc.getName();
    503         Class<?> attrType = pdesc.getPropertyType();
    504         boolean writable = (pdesc.getWriteMethod() != null) || isWritable(type, attrName);
    505         if (getPropertyEditor(type, attrName, attrType) == null) {
    506             writable = false;
    507         }
    508         return writable;
    509     }
    510
    511     private void expandAttribute(List<ModelMBeanAttributeInfo> attributes, Object object, String prefix,
    512             PropertyDescriptor pdesc) {
    513         Object property;
    514         String attrName = pdesc.getName();
    515         try {
    516             property = getAttribute(object, attrName, pdesc.getPropertyType());
    517         } catch (Exception e) {
    518             LOGGER.debug("Unexpected exception.", e);
    519             return;
    520         }
    521
    522         if (property == null) {
    523             return;
    524         }
    525
    526         addAttributes(attributes, property, property.getClass(), prefix + attrName + '.');
    527     }
    528
    529     private void addOperations(List<ModelMBeanOperationInfo> operations, Object object) {
    530
    531         for (Method m : object.getClass().getMethods()) {
    532             String mname = m.getName();
    533
    534             // Ignore getters and setters.
    535             if (mname.startsWith("is") || mname.startsWith("get") || mname.startsWith("set")) {
    536                 continue;
    537             }
    538
    539             // Ignore Object methods.
    540             if (mname.matches("(wait|notify|notifyAll|toString|equals|compareTo|hashCode|clone)")) {
    541                 continue;
    542             }
    543
    544             // Ignore other user-defined non-operations.
    545             if (!isOperation(mname, m.getParameterTypes())) {
    546                 continue;
    547             }
    548
    549             List<MBeanParameterInfo> signature = new ArrayList<MBeanParameterInfo>();
    550             int i = 1;
    551             for (Class<?> paramType : m.getParameterTypes()) {
    552                 String paramName = "p" + (i++);
    553                 if (getPropertyEditor(source.getClass(), paramName, paramType) == null) {
    554                     continue;
    555                 }
    556                 signature.add(new MBeanParameterInfo(paramName, convertType(null, null, paramType, true).getName(),
    557                         paramName));
    558             }
    559
    560             Class<?> returnType = convertType(null, null, m.getReturnType(), false);
    561             operations.add(new ModelMBeanOperationInfo(m.getName(), m.getName(), signature
    562                     .toArray(new MBeanParameterInfo[signature.size()]), returnType.getName(),
    563                     ModelMBeanOperationInfo.ACTION));
    564         }
    565     }
    566
    567     private Object getParent(String fqan) throws OgnlException {
    568         Object parent;
    569         int dotIndex = fqan.lastIndexOf('.');
    570         if (dotIndex < 0) {
    571             parent = source;
    572         } else {
    573             parent = getAttribute(source, fqan.substring(0, dotIndex), null);
    574         }
    575         return parent;
    576     }
    577
    578     private String getLeafAttributeName(String fqan) {
    579         int dotIndex = fqan.lastIndexOf('.');
    580         if (dotIndex < 0) {
    581             return fqan;
    582         }
    583         return fqan.substring(dotIndex + 1);
    584     }
    585
    586     private Class<?> getAttributeClass(String signature) throws ClassNotFoundException {
    587         if (signature.equals(Boolean.TYPE.getName())) {
    588             return Boolean.TYPE;
    589         }
    590         if (signature.equals(Byte.TYPE.getName())) {
    591             return Byte.TYPE;
    592         }
    593         if (signature.equals(Character.TYPE.getName())) {
    594             return Character.TYPE;
    595         }
    596         if (signature.equals(Double.TYPE.getName())) {
    597             return Double.TYPE;
    598         }
    599         if (signature.equals(Float.TYPE.getName())) {
    600             return Float.TYPE;
    601         }
    602         if (signature.equals(Integer.TYPE.getName())) {
    603             return Integer.TYPE;
    604         }
    605         if (signature.equals(Long.TYPE.getName())) {
    606             return Long.TYPE;
    607         }
    608         if (signature.equals(Short.TYPE.getName())) {
    609             return Short.TYPE;
    610         }
    611
    612         try {
    613             ClassLoader cl = Thread.currentThread().getContextClassLoader();
    614             if (cl != null) {
    615                 return cl.loadClass(signature);
    616             }
    617         } catch (ClassNotFoundException e) {
    618             // Do nothing
    619         }
    620
    621         return Class.forName(signature);
    622     }
    623
    624     private Object getAttribute(Object object, String fqan, Class<?> attrType) throws OgnlException {
    625         Object property;
    626         OgnlContext ctx = (OgnlContext) Ognl.createDefaultContext(object);
    627         ctx.setTypeConverter(new OgnlTypeConverter());
    628         if (attrType == null) {
    629             property = Ognl.getValue(fqan, ctx, object);
    630         } else {
    631             property = Ognl.getValue(fqan, ctx, object, attrType);
    632         }
    633         return property;
    634     }
    635
    636     private Class<?> convertType(Class<?> type, String attrName, Class<?> attrType, boolean writable) {
    637         if ((attrName != null) && ((attrType == Long.class) || (attrType == long.class))) {
    638             if (attrName.endsWith("Time") && (attrName.indexOf("Total") < 0) && (attrName.indexOf("Min") < 0)
    639                     && (attrName.indexOf("Max") < 0) && (attrName.indexOf("Avg") < 0)
    640                     && (attrName.indexOf("Average") < 0) && !propertyDescriptors.containsKey(attrName + "InMillis")) {
    641                 return Date.class;
    642             }
    643         }
    644
    645         if (IoFilterChain.class.isAssignableFrom(attrType)) {
    646             return Map.class;
    647         }
    648
    649         if (IoFilterChainBuilder.class.isAssignableFrom(attrType)) {
    650             return Map.class;
    651         }
    652
    653         if (!writable) {
    654             if (Collection.class.isAssignableFrom(attrType) || Map.class.isAssignableFrom(attrType)) {
    655                 if (List.class.isAssignableFrom(attrType)) {
    656                     return List.class;
    657                 }
    658                 if (Set.class.isAssignableFrom(attrType)) {
    659                     return Set.class;
    660                 }
    661                 if (Map.class.isAssignableFrom(attrType)) {
    662                     return Map.class;
    663                 }
    664                 return Collection.class;
    665             }
    666
    667             if (attrType.isPrimitive() || Date.class.isAssignableFrom(attrType)
    668                     || Boolean.class.isAssignableFrom(attrType) || Character.class.isAssignableFrom(attrType)
    669                     || Number.class.isAssignableFrom(attrType)) {
    670                 if ((attrName == null) || !attrName.endsWith("InMillis")
    671                         || !propertyDescriptors.containsKey(attrName.substring(0, attrName.length() - 8))) {
    672                     return attrType;
    673                 }
    674             }
    675         }
    676
    677         return String.class;
    678     }
    679
    680     private Object convertValue(Class<?> type, String attrName, Object v, boolean writable) {
    681         if (v == null) {
    682             return null;
    683         }
    684
    685         if ((attrName != null) && (v instanceof Long)) {
    686             if (attrName.endsWith("Time") && (attrName.indexOf("Total") < 0) && (attrName.indexOf("Min") < 0)
    687                     && (attrName.indexOf("Max") < 0) && (attrName.indexOf("Avg") < 0)
    688                     && (attrName.indexOf("Average") < 0) && !propertyDescriptors.containsKey(attrName + "InMillis")) {
    689                 long time = (Long) v;
    690                 if (time <= 0) {
    691                     return null;
    692                 }
    693
    694                 return new Date((Long) v);
    695             }
    696         }
    697
    698         if ((v instanceof IoSessionDataStructureFactory) || (v instanceof IoHandler)) {
    699             return v.getClass().getName();
    700         }
    701
    702         if (v instanceof IoFilterChainBuilder) {
    703             Map<String, String> filterMapping = new LinkedHashMap<String, String>();
    704             if (v instanceof DefaultIoFilterChainBuilder) {
    705                 for (IoFilterChain.Entry e : ((DefaultIoFilterChainBuilder) v).getAll()) {
    706                     filterMapping.put(e.getName(), e.getFilter().getClass().getName());
    707                 }
    708             } else {
    709                 filterMapping.put("Unknown builder type", v.getClass().getName());
    710             }
    711             return filterMapping;
    712         }
    713
    714         if (v instanceof IoFilterChain) {
    715             Map<String, String> filterMapping = new LinkedHashMap<String, String>();
    716             for (IoFilterChain.Entry e : ((IoFilterChain) v).getAll()) {
    717                 filterMapping.put(e.getName(), e.getFilter().getClass().getName());
    718             }
    719             return filterMapping;
    720         }
    721
    722         if (!writable) {
    723             if ((v instanceof Collection) || (v instanceof Map)) {
    724                 if (v instanceof List) {
    725                     return convertCollection(v, new ArrayList<Object>());
    726                 }
    727                 if (v instanceof Set) {
    728                     return convertCollection(v, new LinkedHashSet<Object>());
    729                 }
    730                 if (v instanceof Map) {
    731                     return convertCollection(v, new LinkedHashMap<Object, Object>());
    732                 }
    733                 return convertCollection(v, new ArrayList<Object>());
    734             }
    735
    736             if ((v instanceof Date) || (v instanceof Boolean) || (v instanceof Character) || (v instanceof Number)) {
    737                 if ((attrName == null) || !attrName.endsWith("InMillis")
    738                         || !propertyDescriptors.containsKey(attrName.substring(0, attrName.length() - 8))) {
    739                     return v;
    740                 }
    741             }
    742         }
    743
    744         PropertyEditor editor = getPropertyEditor(type, attrName, v.getClass());
    745         if (editor != null) {
    746             editor.setValue(v);
    747             return editor.getAsText();
    748         }
    749
    750         return v.toString();
    751     }
    752
    753     private Object convertCollection(Object src, Collection<Object> dst) {
    754         Collection<?> srcCol = (Collection<?>) src;
    755         for (Object e : srcCol) {
    756             Object convertedValue = convertValue(dst.getClass(), "element", e, false);
    757             if ((e != null) && (convertedValue == null)) {
    758                 convertedValue = (e == null ? "" : e.toString());
    759             }
    760             dst.add(convertedValue);
    761         }
    762         return dst;
    763     }
    764
    765     private Object convertCollection(Object src, Map<Object, Object> dst) {
    766         Map<?, ?> srcCol = (Map<?, ?>) src;
    767         for (Map.Entry<?, ?> e : srcCol.entrySet()) {
    768             Object convertedKey = convertValue(dst.getClass(), "key", e.getKey(), false);
    769             Object convertedValue = convertValue(dst.getClass(), "value", e.getValue(), false);
    770             if ((e.getKey() != null) && (convertedKey == null)) {
    771                 convertedKey = e.getKey().toString();
    772             }
    773             if ((e.getValue() != null) && (convertedValue == null)) {
    774                 convertedKey = e.getValue().toString();
    775             }
    776             dst.put(convertedKey, convertedValue);
    777         }
    778         return dst;
    779     }
    780
    781     private void throwMBeanException(Throwable e) throws MBeanException {
    782         if (e instanceof OgnlException) {
    783             OgnlException ognle = (OgnlException) e;
    784             if (ognle.getReason() != null) {
    785                 throwMBeanException(ognle.getReason());
    786             } else {
    787                 String message = ognle.getMessage();
    788                 if (e instanceof NoSuchPropertyException) {
    789                     message = "No such property: " + message;
    790                 } else if (e instanceof ExpressionSyntaxException) {
    791                     message = "Illegal expression syntax: " + message;
    792                 } else if (e instanceof InappropriateExpressionException) {
    793                     message = "Inappropriate expression: " + message;
    794                 }
    795                 e = new IllegalArgumentException(ognle.getMessage());
    796                 e.setStackTrace(ognle.getStackTrace());
    797             }
    798         }
    799         if (e instanceof InvocationTargetException) {
    800             throwMBeanException(e.getCause());
    801         }
    802
    803         LOGGER.warn("Unexpected exception.", e);
    804         if (e.getClass().getPackage().getName().matches("javax?\\..+")) {
    805             if (e instanceof Exception) {
    806                 throw new MBeanException((Exception) e, e.getMessage());
    807             }
    808
    809             throw new MBeanException(new RuntimeException(e), e.getMessage());
    810         }
    811
    812         throw new MBeanException(new RuntimeException(e.getClass().getName() + ": " + e.getMessage()), e.getMessage());
    813     }
    814
    815     protected Object getAttribute0(String fqan) throws Exception {
    816         throw new AttributeNotFoundException(fqan);
    817     }
    818
    819     protected void setAttribute0(String attrName, Object attrValue) throws Exception {
    820         throw new AttributeNotFoundException(attrName);
    821     }
    822
    823     protected Object invoke0(String name, Object params[], String signature[]) throws Exception {
    824         throw new NoSuchMethodException();
    825     }
    826
    827     protected boolean isReadable(Class<?> type, String attrName) {
    828         if (IoService.class.isAssignableFrom(type) && attrName.equals("filterChain")) {
    829             return false;
    830         }
    831         if (IoService.class.isAssignableFrom(type) && attrName.equals("localAddress")) {
    832             return false;
    833         }
    834         if (IoService.class.isAssignableFrom(type) && attrName.equals("defaultLocalAddress")) {
    835             return false;
    836         }
    837         if (IoSession.class.isAssignableFrom(type) && attrName.equals("attachment")) {
    838             return false;
    839         }
    840         if (IoSession.class.isAssignableFrom(type) && attrName.equals("attributeKeys")) {
    841             return false;
    842         }
    843         if (IoSession.class.isAssignableFrom(type) && attrName.equals("closeFuture")) {
    844             return false;
    845         }
    846
    847         if (ThreadPoolExecutor.class.isAssignableFrom(type) && attrName.equals("queue")) {
    848             return false;
    849         }
    850
    851         return true;
    852     }
    853
    854     protected boolean isWritable(Class<?> type, String attrName) {
    855         if (IoService.class.isAssignableFrom(type) && attrName.startsWith("defaultLocalAddress")) {
    856             return true;
    857         }
    858         return false;
    859     }
    860
    861     protected Class<?> getElementType(Class<?> type, String attrName) {
    862         if ((transportMetadata != null) && IoAcceptor.class.isAssignableFrom(type)
    863                 && "defaultLocalAddresses".equals(attrName)) {
    864             return transportMetadata.getAddressType();
    865         }
    866         return String.class;
    867     }
    868
    869     protected Class<?> getMapKeyType(Class<?> type, String attrName) {
    870         return String.class;
    871     }
    872
    873     protected Class<?> getMapValueType(Class<?> type, String attrName) {
    874         return String.class;
    875     }
    876
    877     protected boolean isExpandable(Class<?> type, String attrName) {
    878
    879         if (IoService.class.isAssignableFrom(type)) {
    880             if (attrName.equals("statistics") || attrName.equals("sessionConfig")
    881                     || attrName.equals("transportMetadata") || attrName.equals("config")
    882                     || attrName.equals("transportMetadata")) {
    883                 return true;
    884             }
    885         }
    886
    887         if (ExecutorFilter.class.isAssignableFrom(type) && attrName.equals("executor")) {
    888             return true;
    889         }
    890
    891         if (ThreadPoolExecutor.class.isAssignableFrom(type) && attrName.equals("queueHandler")) {
    892             return true;
    893         }
    894
    895         return false;
    896     }
    897
    898     protected boolean isOperation(String methodName, Class<?>[] paramTypes) {
    899         return true;
    900     }
    901
    902     protected void addExtraAttributes(List<ModelMBeanAttributeInfo> attributes) {
    903         // Do nothing
    904     }
    905
    906     protected void addExtraOperations(List<ModelMBeanOperationInfo> operations) {
    907         // Do nothing
    908     }
    909
    910     protected PropertyEditor getPropertyEditor(Class<?> type, String attrName, Class<?> attrType) {
    911         if (type == null) {
    912             throw new IllegalArgumentException("type");
    913         }
    914
    915         if (attrName == null) {
    916             throw new IllegalArgumentException("attrName");
    917         }
    918
    919         if ((transportMetadata != null) && (attrType == SocketAddress.class)) {
    920             attrType = transportMetadata.getAddressType();
    921         }
    922
    923         if (((attrType == Long.class) || (attrType == long.class))) {
    924             if (attrName.endsWith("Time") && (attrName.indexOf("Total") < 0) && (attrName.indexOf("Min") < 0)
    925                     && (attrName.indexOf("Max") < 0) && (attrName.indexOf("Avg") < 0)
    926                     && (attrName.indexOf("Average") < 0) && !propertyDescriptors.containsKey(attrName + "InMillis")) {
    927                 return PropertyEditorFactory.getInstance(Date.class);
    928             }
    929
    930             if (attrName.equals("id")) {
    931                 return PropertyEditorFactory.getInstance(String.class);
    932             }
    933         }
    934
    935         if (List.class.isAssignableFrom(attrType)) {
    936             return new ListEditor(getElementType(type, attrName));
    937         }
    938
    939         if (Set.class.isAssignableFrom(attrType)) {
    940             return new SetEditor(getElementType(type, attrName));
    941         }
    942
    943         if (Collection.class.isAssignableFrom(attrType)) {
    944             return new CollectionEditor(getElementType(type, attrName));
    945         }
    946
    947         if (Map.class.isAssignableFrom(attrType)) {
    948             return new MapEditor(getMapKeyType(type, attrName), getMapValueType(type, attrName));
    949         }
    950
    951         return PropertyEditorFactory.getInstance(attrType);
    952     }
    953
    954     private class OgnlTypeConverter extends PropertyTypeConverter {
    955         @Override
    956         protected PropertyEditor getPropertyEditor(Class<?> type, String attrName, Class<?> attrType) {
    957             return ObjectMBean.this.getPropertyEditor(type, attrName, attrType);
    958         }
    959     }
    960 }