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.logging.log4j.LogManager; 023import org.apache.logging.log4j.Logger; 024 025import javax.management.*; 026import java.lang.reflect.InvocationTargetException; 027import java.lang.reflect.Method; 028 029/** 030 * A simple MBean which does not require an interface class unlike 031 * the StandardMBean class. The methods are exposed through a method 032 * call, which in turn then calls the methods using the Reflection API. 033 * <p> 034 * This class is similar to the javax.management.StandardMBean, but it does 035 * require the API interface to be declared, so it's simpler. It's not as 036 * powerful, but it does not require you to declare two classes (and keep 037 * them in sync). 038 * 039 * @since 2.6 040 */ 041// FIXME: This class should really use Annotations instead of a method call. 042// FIXME: Exception handling is not probably according to spec... 043public abstract class SimpleMBean implements DynamicMBean { 044 045 private static final Logger LOG = LogManager.getLogger( SimpleMBean.class ); 046 protected MBeanInfo m_beanInfo; 047 048 private static Method findGetterSetter(final Class<?> clazz, final String name, final Class<?> parm ) 049 { 050 try 051 { 052 final Class<?>[] params = { parm }; 053 final Class<?>[] emptyparms = {}; 054 055 return clazz.getDeclaredMethod( name, parm != null ? params : emptyparms ); 056 } 057 catch( final Exception e ) 058 { 059 // There's nothing to do, really - we just return a null. 060 } 061 062 return null; 063 } 064 065 /** 066 * Create a new SimpleMBean 067 * 068 * @throws NotCompliantMBeanException if an error occurs registering the MBean. 069 */ 070 protected SimpleMBean() throws NotCompliantMBeanException 071 { 072 // 073 // Create attributes 074 // 075 final String[] attlist = getAttributeNames(); 076 MBeanAttributeInfo[] attributes = null; 077 078 if( attlist != null ) 079 { 080 attributes = new MBeanAttributeInfo[attlist.length]; 081 082 for( int i = 0; i < attlist.length; i++ ) 083 { 084 String name = attlist[i]; 085 name = StringUtils.capitalize( name ); 086 Method getter = findGetterSetter( getClass(), "get"+name, null ); 087 088 if( getter == null ) getter = findGetterSetter( getClass(), "is"+name, null ); 089 090 Method setter = null; 091 092 if( getter != null ) 093 { 094 setter = findGetterSetter( getClass(), "set"+name, getter.getReturnType() ); 095 } 096 097 // 098 // Check, if there's a description available 099 // 100 final Method descriptor = findGetterSetter( getClass(), "get"+name+"Description", null ); 101 String description = ""; 102 103 if( descriptor != null ) 104 { 105 try 106 { 107 description = (String) descriptor.invoke( this, (Object[])null ); 108 } 109 catch( final Exception e ) 110 { 111 description="Exception: "+e.getMessage(); 112 } 113 } 114 115 final MBeanAttributeInfo info; 116 try 117 { 118 info = new MBeanAttributeInfo( attlist[i], description, getter, setter ); 119 } 120 catch (final IntrospectionException e) 121 { 122 throw new NotCompliantMBeanException( e.getMessage() ); 123 } 124 125 attributes[i] = info; 126 } 127 } 128 129 // 130 // Create operations. 131 // 132 final String[] oplist = getMethodNames(); 133 final MBeanOperationInfo[] operations = new MBeanOperationInfo[oplist.length]; 134 135 final Method[] methods = getClass().getMethods(); 136 137 for( int i = 0; i < oplist.length; i++ ) 138 { 139 Method method = null; 140 141 for( final Method value : methods ) { 142 if ( value.getName().equals( oplist[ i ] ) ) { 143 method = value; 144 } 145 } 146 147 if( method == null ) 148 { 149 throw new NotCompliantMBeanException("Class declares method "+oplist[i]+", yet does not implement it!"); 150 } 151 152 final MBeanOperationInfo info = new MBeanOperationInfo( method.getName(), method ); 153 154 operations[i] = info; 155 } 156 157 // 158 // Create the actual BeanInfo instance. 159 // 160 final MBeanConstructorInfo[] constructors = null; 161 final MBeanNotificationInfo[] notifications = null; 162 163 m_beanInfo = new MBeanInfo( getClass().getName(), 164 getDescription(), 165 attributes, 166 constructors, 167 operations, 168 notifications ); 169 } 170 171 /** 172 * Customization hook: Override this to get a description for your MBean. By default, 173 * this is an empty string. 174 * 175 * @return A description for the MBean. 176 */ 177 protected String getDescription() 178 { 179 return ""; 180 } 181 182 /** 183 * Gets an attribute using reflection from the MBean. 184 * 185 * @param name Name of the attribute to find. 186 * @return The value returned by the corresponding getXXX() call 187 * @throws AttributeNotFoundException If there is not such attribute 188 * @throws MBeanException 189 * @throws ReflectionException 190 */ 191 @Override 192 public Object getAttribute(final String name) 193 throws AttributeNotFoundException, MBeanException, ReflectionException 194 { 195 final Method m; 196 Object res = null; 197 try { 198 final String mname = "get"+StringUtils.capitalize( name ); 199 m = findGetterSetter( getClass(), mname, null ); 200 201 if( m == null ) throw new AttributeNotFoundException( name ); 202 res = m.invoke( this, (Object[])null ); 203 } catch (final SecurityException | IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { 204 LOG.error( e.getMessage(), e ); 205 } 206 207 return res; 208 } 209 210 /** 211 * Gets multiple attributes at the same time. 212 * 213 * @param arg0 The attribute names to get 214 * @return A list of attributes 215 */ 216 @Override 217 public AttributeList getAttributes(final String[] arg0) { 218 final AttributeList list = new AttributeList(); 219 for( final String s : arg0 ) { 220 try { 221 list.add( new Attribute( s, getAttribute( s ) ) ); 222 } catch ( final AttributeNotFoundException | MBeanException | ReflectionException e ) { 223 LOG.error( e.getMessage(), e ); 224 } 225 } 226 227 return list; 228 } 229 230 /** 231 * Return the MBeanInfo structure. 232 * 233 * @return the MBeanInfo 234 */ 235 @Override 236 public MBeanInfo getMBeanInfo() 237 { 238 return m_beanInfo; 239 } 240 241 /** 242 * Invokes a particular method. 243 * 244 * @param arg0 Method name 245 * @param arg1 A list of arguments for the invocation 246 */ 247 @Override 248 public Object invoke(final String arg0, final Object[] arg1, final String[] arg2) 249 throws MBeanException, ReflectionException 250 { 251 final Method[] methods = getClass().getMethods(); 252 253 for( final Method method : methods ) { 254 if( method.getName().equals( arg0 ) ) { 255 try { 256 return method.invoke( this, arg1 ); 257 } catch ( final IllegalArgumentException e ) { 258 throw new ReflectionException( e, "Wrong arguments" ); 259 } catch ( final IllegalAccessException e ) { 260 throw new ReflectionException( e, "No access" ); 261 } catch ( final InvocationTargetException e ) { 262 throw new ReflectionException( e, "Wrong target" ); 263 } 264 } 265 } 266 267 throw new ReflectionException(null, "There is no such method "+arg0); 268 } 269 270 @Override 271 public void setAttribute(final Attribute attr) 272 throws AttributeNotFoundException, 273 InvalidAttributeValueException, 274 MBeanException, 275 ReflectionException 276 { 277 final Method m; 278 279 final String mname = "set"+StringUtils.capitalize( attr.getName() ); 280 m = findGetterSetter( getClass(), mname, attr.getValue().getClass() ); 281 282 if( m == null ) throw new AttributeNotFoundException( attr.getName() ); 283 284 final Object[] args = { attr.getValue() }; 285 286 try 287 { 288 m.invoke( this, args ); 289 } 290 catch (final IllegalArgumentException e) 291 { 292 throw new InvalidAttributeValueException( "Faulty argument: "+e.getMessage() ); 293 } 294 catch (final IllegalAccessException e) 295 { 296 throw new ReflectionException( e, "Cannot access attribute "+e.getMessage() ); 297 } 298 catch (final InvocationTargetException e) 299 { 300 throw new ReflectionException( e, "Cannot invoke attribute "+e.getMessage() ); 301 } 302 } 303 304 @Override 305 public AttributeList setAttributes( final AttributeList arg0 ) { 306 final AttributeList result = new AttributeList(); 307 for( final Object o : arg0 ) { 308 final Attribute attr = ( Attribute ) o; 309 310 // 311 // Attempt to set the attribute. If it succeeds (no exception), 312 // then we just add it to the list of successfull sets. 313 // 314 try { 315 setAttribute( attr ); 316 result.add( attr ); 317 } catch ( final AttributeNotFoundException | InvalidAttributeValueException | MBeanException | ReflectionException e ) { 318 LOG.error( e.getMessage(), e ); 319 } 320 } 321 322 return result; 323 } 324 /** 325 * This method must return a list of attributes which are 326 * exposed by the SimpleMBean. If there's a getXXX() method 327 * available, it'll be exposed as a getter, and if there's a 328 * setXXX() method available, it'll be exposed as a setter. 329 * For example: 330 * <pre> 331 * public void setFoo( String foo ) ... 332 * public String getFoo() ... 333 * 334 * public String[] getAttributeNames() 335 * { 336 * String[] attrs = { "foo" }; 337 * 338 * return attrs; 339 * } 340 * </pre> 341 * Also, methods starting with "is" are also recognized as getters 342 * (e.g. <code>public boolean isFoo()</code>.) 343 * 344 * @return An array of attribute names that can be get and optionally set. 345 */ 346 public abstract String[] getAttributeNames(); 347 348 /** 349 * This method must return a list of operations which 350 * are to be exposed by the SimpleMBean. Note that using overloaded 351 * method names is not supported - only one will be exposed as a JMX method 352 * at random. 353 * 354 * @return An array of method names that should be exposed as 355 * JMX operations. 356 */ 357 public abstract String[] getMethodNames(); 358}