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 }