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}