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