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