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 {@inheritDoc}
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    public Object getAttribute(String name) 
210        throws AttributeNotFoundException, MBeanException, ReflectionException
211    {
212        Method m;
213        Object res = null;
214        try
215        {
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        }
222        catch (SecurityException e)
223        {
224            // TODO Auto-generated catch block
225            LOG.error( e.getMessage(), e );
226        }
227        catch (IllegalArgumentException e)
228        {
229            // TODO Auto-generated catch block
230            LOG.error( e.getMessage(), e );
231        }
232        catch (IllegalAccessException e)
233        {
234            // TODO Auto-generated catch block
235            LOG.error( e.getMessage(), e );
236        }
237        catch (InvocationTargetException e)
238        {
239            // TODO Auto-generated catch block
240            LOG.error( e.getMessage(), e );
241        }
242
243        return res;
244    }
245
246    /**
247     *  Gets multiple attributes at the same time.
248     *  
249     *  @param arg0 The attribute names to get
250     *  @return A list of attributes 
251     */
252    public AttributeList getAttributes(String[] arg0)
253    {
254        AttributeList list = new AttributeList();
255
256        for( int i = 0; i < arg0.length; i++ )
257        {
258            try
259            {
260                list.add( new Attribute(arg0[i], getAttribute(arg0[i])) );
261            }
262            catch (AttributeNotFoundException e)
263            {
264                // TODO Auto-generated catch block
265                LOG.error( e.getMessage(), e );
266            }
267            catch (MBeanException e)
268            {
269                // TODO Auto-generated catch block
270                LOG.error( e.getMessage(), e );
271            }
272            catch (ReflectionException e)
273            {
274                // TODO Auto-generated catch block
275                LOG.error( e.getMessage(), e );
276            }
277        }
278
279        return list;
280    }
281
282    /**
283     *  Return the MBeanInfo structure.
284     *  
285     *  @return the MBeanInfo
286     */
287    public MBeanInfo getMBeanInfo()
288    {
289        return m_beanInfo;
290    }
291
292    /**
293     *  Invokes a particular method.
294     *  
295     *  @param arg0 Method name
296     *  @param arg1 A list of arguments for the invocation
297     */
298    public Object invoke(String arg0, Object[] arg1, String[] arg2)
299        throws MBeanException, ReflectionException
300    {
301        Method[] methods = getClass().getMethods();
302
303        for( int i = 0; i < methods.length; i++ )
304        {
305            if( methods[i].getName().equals(arg0) )
306            {
307                try
308                {
309                    return methods[i].invoke( this, arg1 );
310                }
311                catch (IllegalArgumentException e)
312                {
313                    throw new ReflectionException( e, "Wrong arguments" );
314                }
315                catch (IllegalAccessException e)
316                {
317                    throw new ReflectionException( e, "No access" );
318                }
319                catch (InvocationTargetException e)
320                {
321                    throw new ReflectionException( e, "Wrong target" );
322                }
323            }
324        }
325
326        throw new ReflectionException(null, "There is no such method "+arg0); // TODO: Can you put a null exception?
327    }
328
329    public void setAttribute(Attribute attr)
330        throws AttributeNotFoundException,
331               InvalidAttributeValueException,
332               MBeanException,
333               ReflectionException
334    {
335        Method m;
336
337        String mname = "set"+StringUtils.capitalize( attr.getName() );
338        m = findGetterSetter( getClass(), mname, attr.getValue().getClass() );
339
340        if( m == null ) throw new AttributeNotFoundException( attr.getName() );
341
342        Object[] args = { attr.getValue() };
343
344        try
345        {
346            m.invoke( this, args );
347        }
348        catch (IllegalArgumentException e)
349        {
350            throw new InvalidAttributeValueException( "Faulty argument: "+e.getMessage() );
351        }
352        catch (IllegalAccessException e)
353        {
354            throw new ReflectionException( e, "Cannot access attribute "+e.getMessage() );
355        }
356        catch (InvocationTargetException e)
357        {
358            throw new ReflectionException( e, "Cannot invoke attribute "+e.getMessage() );
359        }
360    }
361
362    public AttributeList setAttributes(AttributeList arg0)
363    {
364        AttributeList result = new AttributeList();
365        for( Iterator i = arg0.iterator(); i.hasNext(); )
366        {
367            Attribute attr = (Attribute)i.next();
368
369            //
370            //  Attempt to set the attribute.  If it succeeds (no exception),
371            //  then we just add it to the list of successfull sets.
372            //
373            try
374            {
375                setAttribute( attr );
376                result.add( attr );
377            }
378            catch (AttributeNotFoundException e)
379            {
380                // TODO Auto-generated catch block
381                LOG.error( e.getMessage(), e );
382            }
383            catch (InvalidAttributeValueException e)
384            {
385                // TODO Auto-generated catch block
386                LOG.error( e.getMessage(), e );
387            }
388            catch (MBeanException e)
389            {
390                // TODO Auto-generated catch block
391                LOG.error( e.getMessage(), e );
392            }
393            catch (ReflectionException e)
394            {
395                // TODO Auto-generated catch block
396                LOG.error( e.getMessage(), e );
397            }
398        }
399
400        return result;
401    }
402    /**
403     *  This method must return a list of attributes which are
404     *  exposed by the SimpleMBean.  If there's a getXXX() method
405     *  available, it'll be exposed as a getter, and if there's a
406     *  setXXX() method available, it'll be exposed as a setter.
407     *  For example:
408     *  <pre>
409     *     public void setFoo( String foo ) ...
410     *     public String getFoo() ...
411     *
412     *     public String[] getAttributeNames()
413     *     {
414     *         String[] attrs = { "foo" };
415     *
416     *         return attrs;
417     *     }
418     *  </pre>
419     *  Also, methods starting with "is" are also recognized as getters
420     *  (e.g. <code>public boolean isFoo()</code>.)
421     *
422     *  @return An array of attribute names that can be get and optionally set.
423     */
424    public abstract String[] getAttributeNames();
425
426    /**
427     *  This method must return a list of operations which
428     *  are to be exposed by the SimpleMBean.  Note that using overloaded
429     *  method names is not supported - only one will be exposed as a JMX method
430     *  at random.
431     *
432     *  @return An array of method names that should be exposed as
433     *          JMX operations.
434     */
435    public abstract String[] getMethodNames();
436}