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