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