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