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 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< Object > 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}