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}