001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.mina.integration.jmx;
018
019import java.beans.IntrospectionException;
020import java.beans.Introspector;
021import java.beans.PropertyDescriptor;
022import java.beans.PropertyEditor;
023import java.lang.reflect.InvocationTargetException;
024import java.lang.reflect.Method;
025import java.net.SocketAddress;
026import java.util.ArrayList;
027import java.util.Collection;
028import java.util.Date;
029import java.util.HashMap;
030import java.util.Iterator;
031import java.util.LinkedHashMap;
032import java.util.LinkedHashSet;
033import java.util.List;
034import java.util.Map;
035import java.util.Set;
036import java.util.concurrent.ConcurrentHashMap;
037import java.util.concurrent.ThreadPoolExecutor;
038
039import javax.management.Attribute;
040import javax.management.AttributeChangeNotification;
041import javax.management.AttributeList;
042import javax.management.AttributeNotFoundException;
043import javax.management.InstanceNotFoundException;
044import javax.management.ListenerNotFoundException;
045import javax.management.MBeanException;
046import javax.management.MBeanInfo;
047import javax.management.MBeanNotificationInfo;
048import javax.management.MBeanParameterInfo;
049import javax.management.MBeanRegistration;
050import javax.management.MBeanServer;
051import javax.management.Notification;
052import javax.management.NotificationFilter;
053import javax.management.NotificationListener;
054import javax.management.ObjectName;
055import javax.management.ReflectionException;
056import javax.management.RuntimeOperationsException;
057import javax.management.modelmbean.InvalidTargetObjectTypeException;
058import javax.management.modelmbean.ModelMBean;
059import javax.management.modelmbean.ModelMBeanAttributeInfo;
060import javax.management.modelmbean.ModelMBeanConstructorInfo;
061import javax.management.modelmbean.ModelMBeanInfo;
062import javax.management.modelmbean.ModelMBeanInfoSupport;
063import javax.management.modelmbean.ModelMBeanNotificationInfo;
064import javax.management.modelmbean.ModelMBeanOperationInfo;
065
066import ognl.ExpressionSyntaxException;
067import ognl.InappropriateExpressionException;
068import ognl.NoSuchPropertyException;
069import ognl.Ognl;
070import ognl.OgnlContext;
071import ognl.OgnlException;
072import ognl.OgnlRuntime;
073import ognl.TypeConverter;
074
075import org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder;
076import org.apache.mina.core.filterchain.IoFilter;
077import org.apache.mina.core.filterchain.IoFilterChain;
078import org.apache.mina.core.filterchain.IoFilterChainBuilder;
079import org.apache.mina.core.service.IoAcceptor;
080import org.apache.mina.core.service.IoHandler;
081import org.apache.mina.core.service.IoService;
082import org.apache.mina.core.service.TransportMetadata;
083import org.apache.mina.core.session.IoSession;
084import org.apache.mina.core.session.IoSessionDataStructureFactory;
085import org.apache.mina.filter.executor.ExecutorFilter;
086import org.apache.mina.integration.beans.CollectionEditor;
087import org.apache.mina.integration.beans.ListEditor;
088import org.apache.mina.integration.beans.MapEditor;
089import org.apache.mina.integration.beans.PropertyEditorFactory;
090import org.apache.mina.integration.beans.SetEditor;
091import org.apache.mina.integration.ognl.IoFilterPropertyAccessor;
092import org.apache.mina.integration.ognl.IoServicePropertyAccessor;
093import org.apache.mina.integration.ognl.IoSessionPropertyAccessor;
094import org.apache.mina.integration.ognl.PropertyTypeConverter;
095import org.slf4j.Logger;
096import org.slf4j.LoggerFactory;
097
098/**
099 * 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 */
105public 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}