    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            LOG.debug(e.getMessage(), e);
    060            // There's nothing to do, really - we just return a null.
    061        }
    062
    063        return null;
    064    }
    065
    066    /**
    067     *  Create a new SimpleMBean
    068     *
    069     *  @throws NotCompliantMBeanException if an error occurs registering the MBean.
    070     */
    071    protected SimpleMBean() throws NotCompliantMBeanException
    072    {
    073        //
    074        //  Create attributes
    075        //
    076        final String[] attlist = getAttributeNames();
    077        MBeanAttributeInfo[] attributes = null;
    078
    079        if( attlist != null )
    080        {
    081            attributes = new MBeanAttributeInfo[attlist.length];
    082
    083            for( int i = 0; i < attlist.length; i++ )
    084            {
    085                String name = attlist[i];
    086                name = StringUtils.capitalize( name );
    087                Method getter = findGetterSetter( getClass(), "get"+name, null );
    088
    089                if( getter == null ) getter = findGetterSetter( getClass(), "is"+name, null );
    090
    091                Method setter = null;
    092
    093                if( getter != null )
    094                {
    095                    setter = findGetterSetter( getClass(), "set"+name, getter.getReturnType() );
    096                }
    097
    098                //
    099                //  Check, if there's a description available
    100                //
    101                final Method descriptor = findGetterSetter( getClass(), "get"+name+"Description", null );
    102                String description = "";
    103
    104                if( descriptor != null )
    105                {
    106                    try
    107                    {
    108                        description = (String) descriptor.invoke( this, (Object[])null );
    109                    }
    110                    catch( final Exception e )
    111                    {
    112                        description="Exception: "+e.getMessage();
    113                    }
    114                }
    115
    116                final MBeanAttributeInfo info;
    117                try
    118                {
    119                    info = new MBeanAttributeInfo( attlist[i], description, getter, setter );
    120                }
    121                catch (final IntrospectionException e)
    122                {
    123                    throw new NotCompliantMBeanException( e.getMessage() );
    124                }
    125
    126                attributes[i] = info;
    127            }
    128        }
    129
    130        //
    131        //  Create operations.
    132        //
    133        final String[] oplist = getMethodNames();
    134        final MBeanOperationInfo[] operations = new MBeanOperationInfo[oplist.length];
    135
    136        final Method[] methods = getClass().getMethods();
    137
    138        for( int i = 0; i < oplist.length; i++ )
    139        {
    140            Method method = null;
    141
    142            for( final Method value : methods ) {
    143                if ( value.getName().equals( oplist[ i ] ) ) {
    144                    method = value;
    145                }
    146            }
    147
    148            if( method == null )
    149            {
    150                throw new NotCompliantMBeanException("Class declares method "+oplist[i]+", yet does not implement it!");
    151            }
    152
    153            final MBeanOperationInfo info = new MBeanOperationInfo( method.getName(), method );
    154
    155            operations[i] = info;
    156        }
    157
    158        //
    159        //  Create the actual BeanInfo instance.
    160        //
    161        final MBeanConstructorInfo[] constructors = null;
    162        final MBeanNotificationInfo[] notifications = null;
    163
    164        m_beanInfo = new MBeanInfo( getClass().getName(),
    165                                    getDescription(),
    166                                    attributes,
    167                                    constructors,
    168                                    operations,
    169                                    notifications );
    170    }
    171
    172    /**
    173     *  Customization hook: Override this to get a description for your MBean.  By default,
    174     *  this is an empty string.
    175     *
    176     *  @return A description for the MBean.
    177     */
    178    protected String getDescription()
    179    {
    180        return "";
    181    }
    182
    183    /**
    184     *  Gets an attribute using reflection from the MBean.
    185     *
    186     *  @param name Name of the attribute to find.
    187     *  @return The value returned by the corresponding getXXX() call
    188     *  @throws AttributeNotFoundException If there is not such attribute
    189     *  @throws MBeanException
    190     *  @throws ReflectionException
    191     */
    192    @Override
    193    public Object getAttribute(final String name)
    194        throws AttributeNotFoundException, MBeanException, ReflectionException
    195    {
    196        final Method m;
    197        Object res = null;
    198        try {
    199            final String mname = "get"+StringUtils.capitalize( name );
    200            m = findGetterSetter( getClass(), mname, null );
    201
    202            if( m == null ) throw new AttributeNotFoundException( name );
    203            res = m.invoke( this, (Object[])null );
    204        } catch (final SecurityException | IllegalArgumentException | IllegalAccessException | InvocationTargetException e) {
    205            LOG.error( e.getMessage(), e );
    206        }
    207
    208        return res;
    209    }
    210
    211    /**
    212     *  Gets multiple attributes at the same time.
    213     *
    214     *  @param arg0 The attribute names to get
    215     *  @return A list of attributes
    216     */
    217    @Override
    218    public AttributeList getAttributes(final String[] arg0) {
    219        final AttributeList list = new AttributeList();
    220        for( final String s : arg0 ) {
    221            try {
    222                list.add( new Attribute( s, getAttribute( s ) ) );
    223            } catch ( final AttributeNotFoundException | MBeanException | ReflectionException e ) {
    224                LOG.error( e.getMessage(), e );
    225            }
    226        }
    227
    228        return list;
    229    }
    230
    231    /**
    232     *  Return the MBeanInfo structure.
    233     *
    234     *  @return the MBeanInfo
    235     */
    236    @Override
    237    public MBeanInfo getMBeanInfo()
    238    {
    239        return m_beanInfo;
    240    }
    241
    242    /**
    243     *  Invokes a particular method.
    244     *
    245     *  @param arg0 Method name
    246     *  @param arg1 A list of arguments for the invocation
    247     */
    248    @Override
    249    public Object invoke(final String arg0, final Object[] arg1, final String[] arg2)
    250        throws MBeanException, ReflectionException
    251    {
    252        final Method[] methods = getClass().getMethods();
    253
    254        for( final Method method : methods ) {
    255            if( method.getName().equals( arg0 ) ) {
    256                try {
    257                    return method.invoke( this, arg1 );
    258                } catch ( final IllegalArgumentException e ) {
    259                    throw new ReflectionException( e, "Wrong arguments" );
    260                } catch ( final IllegalAccessException e ) {
    261                    throw new ReflectionException( e, "No access" );
    262                } catch ( final InvocationTargetException e ) {
    263                    throw new ReflectionException( e, "Wrong target" );
    264                }
    265            }
    266        }
    267
    268        throw new ReflectionException(null, "There is no such method "+arg0);
    269    }
    270
    271    @Override
    272    public void setAttribute(final Attribute attr)
    273        throws AttributeNotFoundException,
    274               InvalidAttributeValueException,
    275               MBeanException,
    276               ReflectionException
    277    {
    278        final Method m;
    279
    280        final String mname = "set"+StringUtils.capitalize( attr.getName() );
    281        m = findGetterSetter( getClass(), mname, attr.getValue().getClass() );
    282
    283        if( m == null ) throw new AttributeNotFoundException( attr.getName() );
    284
    285        final Object[] args = { attr.getValue() };
    286
    287        try
    288        {
    289            m.invoke( this, args );
    290        }
    291        catch (final IllegalArgumentException e)
    292        {
    293            throw new InvalidAttributeValueException( "Faulty argument: "+e.getMessage() );
    294        }
    295        catch (final IllegalAccessException e)
    296        {
    297            throw new ReflectionException( e, "Cannot access attribute "+e.getMessage() );
    298        }
    299        catch (final InvocationTargetException e)
    300        {
    301            throw new ReflectionException( e, "Cannot invoke attribute "+e.getMessage() );
    302        }
    303    }
    304
    305    @Override
    306    public AttributeList setAttributes( final AttributeList arg0 ) {
    307        final AttributeList result = new AttributeList();
    308        for( final Object o : arg0 ) {
    309            final Attribute attr = ( Attribute ) o;
    310
    311            //
    312            //  Attempt to set the attribute.  If it succeeds (no exception),
    313            //  then we just add it to the list of successfull sets.
    314            //
    315            try {
    316                setAttribute( attr );
    317                result.add( attr );
    318            } catch ( final AttributeNotFoundException | InvalidAttributeValueException | MBeanException | ReflectionException e ) {
    319                LOG.error( e.getMessage(), e );
    320            }
    321        }
    322
    323        return result;
    324    }
    325    /**
    326     *  This method must return a list of attributes which are
    327     *  exposed by the SimpleMBean.  If there's a getXXX() method
    328     *  available, it'll be exposed as a getter, and if there's a
    329     *  setXXX() method available, it'll be exposed as a setter.
    330     *  For example:
    331     *  <pre>
    332     *     public void setFoo( String foo ) ...
    333     *     public String getFoo() ...
    334     *
    335     *     public String[] getAttributeNames()
    336     *     {
    337     *         String[] attrs = { "foo" };
    338     *
    339     *         return attrs;
    340     *     }
    341     *  </pre>
    342     *  Also, methods starting with "is" are also recognized as getters
    343     *  (e.g. <code>public boolean isFoo()</code>.)
    344     *
    345     *  @return An array of attribute names that can be get and optionally set.
    346     */
    347    public abstract String[] getAttributeNames();
    348
    349    /**
    350     *  This method must return a list of operations which
    351     *  are to be exposed by the SimpleMBean.  Note that using overloaded
    352     *  method names is not supported - only one will be exposed as a JMX method
    353     *  at random.
    354     *
    355     *  @return An array of method names that should be exposed as
    356     *          JMX operations.
    357     */
    358    public abstract String[] getMethodNames();
    359}