    001/*
    002    Licensed to the Apache Software Foundation (ASF) under one
    003    or more contributor license agreements.  See the NOTICE file
    004    distributed with this work for additional information
    005    regarding copyright ownership.  The ASF licenses this file
    006    to you under the Apache License, Version 2.0 (the
    007    "License"); you may not use this file except in compliance
    008    with the License.  You may obtain a copy of the License at
    009
    010       http://www.apache.org/licenses/LICENSE-2.0
    011
    012    Unless required by applicable law or agreed to in writing,
    013    software distributed under the License is distributed on an
    014    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    015    KIND, either express or implied.  See the License for the
    016    specific language governing permissions and limitations
    017    under the License.
    018 */
    019package org.apache.wiki.management;
    020
    021import org.apache.commons.lang3.StringUtils;
    022import org.apache.logging.log4j.LogManager;
    023import org.apache.logging.log4j.Logger;
    024
    025import javax.management.*;
    026import java.lang.reflect.InvocationTargetException;
    027import java.lang.reflect.Method;
    028
    029/**
    030 *  A simple MBean which does not require an interface class unlike
    031 *  the StandardMBean class.  The methods are exposed through a method
    032 *  call, which in turn then calls the methods using the Reflection API.
    033 *  <p>
    034 *  This class is similar to the javax.management.StandardMBean, but it does
    035 *  require the API interface to be declared, so it's simpler.  It's not as
    036 *  powerful, but it does not require you to declare two classes (and keep
    037 *  them in sync).
    038 *
    039 *  @since  2.6
    040 */
    041// FIXME: This class should really use Annotations instead of a method call.
    042// FIXME: Exception handling is not probably according to spec...
    043public abstract class SimpleMBean implements DynamicMBean {
    044
    045    private static final Logger LOG = LogManager.getLogger( SimpleMBean.class );
    046    protected MBeanInfo m_beanInfo;
    047
    048    private static Method findGetterSetter(final Class<?> clazz, final String name, final Class<?> parm )
    049    {
    050        try
    051        {
    052            final Class<?>[] params = { parm };
    053            final Class<?>[] emptyparms = {};
    054
    055            return clazz.getDeclaredMethod( name, parm != null ? params : emptyparms );
    056        }
    057        catch( final Exception e )
    058        {
    059            // There's nothing to do, really - we just return a null.
    060        }
    061
    062        return null;
    063    }
    064
    065    /**
    066     *  Create a new SimpleMBean
    067     *
    068     *  @throws NotCompliantMBeanException if an error occurs registering the MBean.
    069     */
    070    protected SimpleMBean() throws NotCompliantMBeanException
    071    {
    072        //
    073        //  Create attributes
    074        //
    075        final String[] attlist = getAttributeNames();
    076        MBeanAttributeInfo[] attributes = null;
    077
    078        if( attlist != null )
    079        {
    080            attributes = new MBeanAttributeInfo[attlist.length];
    081
    082            for( int i = 0; i < attlist.length; i++ )
    083            {
    084                String name = attlist[i];
    085                name = StringUtils.capitalize( name );
    086                Method getter = findGetterSetter( getClass(), "get"+name, null );
    087
    088                if( getter == null ) getter = findGetterSetter( getClass(), "is"+name, null );
    089
    090                Method setter = null;
    091
    092                if( getter != null )
    093                {
    094                    setter = findGetterSetter( getClass(), "set"+name, getter.getReturnType() );
    095                }
    096
    097                //
    098                //  Check, if there's a description available
    099                //
    100                final Method descriptor = findGetterSetter( getClass(), "get"+name+"Description", null );
    101                String description = "";
    102
    103                if( descriptor != null )
    104                {
    105                    try
    106                    {
    107                        description = (String) descriptor.invoke( this, (Object[])null );
    108                    }
    109                    catch( final Exception e )
    110                    {
    111                        description="Exception: "+e.getMessage();
    112                    }
    113                }
    114
    115                final MBeanAttributeInfo info;
    116                try
    117                {
    118                    info = new MBeanAttributeInfo( attlist[i], description, getter, setter );
    119                }
    120                catch (final IntrospectionException e)
    121                {
    122                    throw new NotCompliantMBeanException( e.getMessage() );
    123                }
    124
    125                attributes[i] = info;
    126            }
    127        }
    128
    129        //
    130        //  Create operations.
    131        //
    132        final String[] oplist = getMethodNames();
    133        final MBeanOperationInfo[] operations = new MBeanOperationInfo[oplist.length];
    134
    135        final Method[] methods = getClass().getMethods();
    136
    137        for( int i = 0; i < oplist.length; i++ )
    138        {
    139            Method method = null;
    140
    141            for( final Method value : methods ) {
    142                if ( value.getName().equals( oplist[ i ] ) ) {
    143                    method = value;
    144                }
    145            }
    146
    147            if( method == null )
    148            {
    149                throw new NotCompliantMBeanException("Class declares method "+oplist[i]+", yet does not implement it!");
    150            }
    151
    152            final MBeanOperationInfo info = new MBeanOperationInfo( method.getName(), method );
    153
    154            operations[i] = info;
    155        }
    156
    157        //
    158        //  Create the actual BeanInfo instance.
    159        //
    160        final MBeanConstructorInfo[] constructors = null;
    161        final MBeanNotificationInfo[] notifications = null;
    162
    163        m_beanInfo = new MBeanInfo( getClass().getName(),
    164                                    getDescription(),
    165                                    attributes,
    166                                    constructors,
    167                                    operations,
    168                                    notifications );
    169    }
    170
    171    /**
    172     *  Customization hook: Override this to get a description for your MBean.  By default,
    173     *  this is an empty string.
    174     *
    175     *  @return A description for the MBean.
    176     */
    177    protected String getDescription()
    178    {
    179        return "";
    180    }
    181
    182    /**
    183     *  Gets an attribute using reflection from the MBean.
    184     *
    185     *  @param name Name of the attribute to find.
    186     *  @return The value returned by the corresponding getXXX() call
    187     *  @throws AttributeNotFoundException If there is not such attribute
    188     *  @throws MBeanException
    189     *  @throws ReflectionException
    190     */
    191    @Override
    192    public Object getAttribute(final String name)
    193        throws AttributeNotFoundException, MBeanException, ReflectionException
    194    {
    195        final Method m;
    196        Object res = null;
    197        try {
    198            final String mname = "get"+StringUtils.capitalize( name );
    199            m = findGetterSetter( getClass(), mname, null );
    200
    201            if( m == null ) throw new AttributeNotFoundException( name );
    202            res = m.invoke( this, (Object[])null );
    203        } catch (final SecurityException | IllegalArgumentException | IllegalAccessException | InvocationTargetException e) {
    204            LOG.error( e.getMessage(), e );
    205        }
    206
    207        return res;
    208    }
    209
    210    /**
    211     *  Gets multiple attributes at the same time.
    212     *
    213     *  @param arg0 The attribute names to get
    214     *  @return A list of attributes
    215     */
    216    @Override
    217    public AttributeList getAttributes(final String[] arg0) {
    218        final AttributeList list = new AttributeList();
    219        for( final String s : arg0 ) {
    220            try {
    221                list.add( new Attribute( s, getAttribute( s ) ) );
    222            } catch ( final AttributeNotFoundException | MBeanException | ReflectionException e ) {
    223                LOG.error( e.getMessage(), e );
    224            }
    225        }
    226
    227        return list;
    228    }
    229
    230    /**
    231     *  Return the MBeanInfo structure.
    232     *
    233     *  @return the MBeanInfo
    234     */
    235    @Override
    236    public MBeanInfo getMBeanInfo()
    237    {
    238        return m_beanInfo;
    239    }
    240
    241    /**
    242     *  Invokes a particular method.
    243     *
    244     *  @param arg0 Method name
    245     *  @param arg1 A list of arguments for the invocation
    246     */
    247    @Override
    248    public Object invoke(final String arg0, final Object[] arg1, final String[] arg2)
    249        throws MBeanException, ReflectionException
    250    {
    251        final Method[] methods = getClass().getMethods();
    252
    253        for( final Method method : methods ) {
    254            if( method.getName().equals( arg0 ) ) {
    255                try {
    256                    return method.invoke( this, arg1 );
    257                } catch ( final IllegalArgumentException e ) {
    258                    throw new ReflectionException( e, "Wrong arguments" );
    259                } catch ( final IllegalAccessException e ) {
    260                    throw new ReflectionException( e, "No access" );
    261                } catch ( final InvocationTargetException e ) {
    262                    throw new ReflectionException( e, "Wrong target" );
    263                }
    264            }
    265        }
    266
    267        throw new ReflectionException(null, "There is no such method "+arg0);
    268    }
    269
    270    @Override
    271    public void setAttribute(final Attribute attr)
    272        throws AttributeNotFoundException,
    273               InvalidAttributeValueException,
    274               MBeanException,
    275               ReflectionException
    276    {
    277        final Method m;
    278
    279        final String mname = "set"+StringUtils.capitalize( attr.getName() );
    280        m = findGetterSetter( getClass(), mname, attr.getValue().getClass() );
    281
    282        if( m == null ) throw new AttributeNotFoundException( attr.getName() );
    283
    284        final Object[] args = { attr.getValue() };
    285
    286        try
    287        {
    288            m.invoke( this, args );
    289        }
    290        catch (final IllegalArgumentException e)
    291        {
    292            throw new InvalidAttributeValueException( "Faulty argument: "+e.getMessage() );
    293        }
    294        catch (final IllegalAccessException e)
    295        {
    296            throw new ReflectionException( e, "Cannot access attribute "+e.getMessage() );
    297        }
    298        catch (final InvocationTargetException e)
    299        {
    300            throw new ReflectionException( e, "Cannot invoke attribute "+e.getMessage() );
    301        }
    302    }
    303
    304    @Override
    305    public AttributeList setAttributes( final AttributeList arg0 ) {
    306        final AttributeList result = new AttributeList();
    307        for( final Object o : arg0 ) {
    308            final Attribute attr = ( Attribute ) o;
    309
    310            //
    311            //  Attempt to set the attribute.  If it succeeds (no exception),
    312            //  then we just add it to the list of successfull sets.
    313            //
    314            try {
    315                setAttribute( attr );
    316                result.add( attr );
    317            } catch ( final AttributeNotFoundException | InvalidAttributeValueException | MBeanException | ReflectionException e ) {
    318                LOG.error( e.getMessage(), e );
    319            }
    320        }
    321
    322        return result;
    323    }
    324    /**
    325     *  This method must return a list of attributes which are
    326     *  exposed by the SimpleMBean.  If there's a getXXX() method
    327     *  available, it'll be exposed as a getter, and if there's a
    328     *  setXXX() method available, it'll be exposed as a setter.
    329     *  For example:
    330     *  <pre>
    331     *     public void setFoo( String foo ) ...
    332     *     public String getFoo() ...
    333     *
    334     *     public String[] getAttributeNames()
    335     *     {
    336     *         String[] attrs = { "foo" };
    337     *
    338     *         return attrs;
    339     *     }
    340     *  </pre>
    341     *  Also, methods starting with "is" are also recognized as getters
    342     *  (e.g. <code>public boolean isFoo()</code>.)
    343     *
    344     *  @return An array of attribute names that can be get and optionally set.
    345     */
    346    public abstract String[] getAttributeNames();
    347
    348    /**
    349     *  This method must return a list of operations which
    350     *  are to be exposed by the SimpleMBean.  Note that using overloaded
    351     *  method names is not supported - only one will be exposed as a JMX method
    352     *  at random.
    353     *
    354     *  @return An array of method names that should be exposed as
    355     *          JMX operations.
    356     */
    357    public abstract String[] getMethodNames();
    358}