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}