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