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