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