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