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