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