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     */
019    package org.apache.wiki.management;
020    
021    import java.lang.reflect.InvocationTargetException;
022    import java.lang.reflect.Method;
023    import java.util.Iterator;
024    
025    import javax.management.Attribute;
026    import javax.management.AttributeList;
027    import javax.management.AttributeNotFoundException;
028    import javax.management.DynamicMBean;
029    import javax.management.IntrospectionException;
030    import javax.management.InvalidAttributeValueException;
031    import javax.management.MBeanAttributeInfo;
032    import javax.management.MBeanConstructorInfo;
033    import javax.management.MBeanException;
034    import javax.management.MBeanInfo;
035    import javax.management.MBeanNotificationInfo;
036    import javax.management.MBeanOperationInfo;
037    import javax.management.NotCompliantMBeanException;
038    import javax.management.ReflectionException;
039    
040    import org.apache.commons.lang.StringUtils;
041    import 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...
057    public 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    }