1 16 17 package org.springframework.jmx.access; 18 19 import java.beans.PropertyDescriptor ; 20 import java.io.IOException ; 21 import java.lang.reflect.Method ; 22 import java.net.MalformedURLException ; 23 import java.util.Arrays ; 24 import java.util.HashMap ; 25 import java.util.Map ; 26 27 import javax.management.Attribute ; 28 import javax.management.InstanceNotFoundException ; 29 import javax.management.IntrospectionException ; 30 import javax.management.JMException ; 31 import javax.management.MBeanAttributeInfo ; 32 import javax.management.MBeanInfo ; 33 import javax.management.MBeanOperationInfo ; 34 import javax.management.MBeanServer ; 35 import javax.management.MBeanServerConnection ; 36 import javax.management.MalformedObjectNameException ; 37 import javax.management.ObjectName ; 38 import javax.management.ReflectionException ; 39 import javax.management.remote.JMXConnector ; 40 import javax.management.remote.JMXConnectorFactory ; 41 import javax.management.remote.JMXServiceURL ; 42 43 import org.aopalliance.intercept.MethodInterceptor; 44 import org.aopalliance.intercept.MethodInvocation; 45 import org.apache.commons.logging.Log; 46 import org.apache.commons.logging.LogFactory; 47 48 import org.springframework.beans.BeanUtils; 49 import org.springframework.beans.factory.DisposableBean; 50 import org.springframework.beans.factory.InitializingBean; 51 import org.springframework.jmx.MBeanServerNotFoundException; 52 import org.springframework.jmx.support.JmxUtils; 53 import org.springframework.jmx.support.ObjectNameManager; 54 55 79 public class MBeanClientInterceptor implements MethodInterceptor, InitializingBean, DisposableBean { 80 81 82 protected final Log logger = LogFactory.getLog(getClass()); 83 84 private MBeanServerConnection server; 85 86 private JMXServiceURL serviceUrl; 87 88 private String agentId; 89 90 private boolean connectOnStartup = true; 91 92 private ObjectName objectName; 93 94 private boolean useStrictCasing = true; 95 96 private JMXConnector connector; 97 98 private Map allowedAttributes; 99 100 private Map allowedOperations; 101 102 private final Map signatureCache = new HashMap (); 103 104 105 109 public void setServer(MBeanServerConnection server) { 110 this.server = server; 111 } 112 113 116 public void setServiceUrl(String url) throws MalformedURLException { 117 this.serviceUrl = new JMXServiceURL (url); 118 } 119 120 127 public void setAgentId(String agentId) { 128 this.agentId = agentId; 129 } 130 131 136 public void setConnectOnStartup(boolean connectOnStartup) { 137 this.connectOnStartup = connectOnStartup; 138 } 139 140 144 public void setObjectName(Object objectName) throws MalformedObjectNameException { 145 this.objectName = ObjectNameManager.getInstance(objectName); 146 } 147 148 155 public void setUseStrictCasing(boolean useStrictCasing) { 156 this.useStrictCasing = useStrictCasing; 157 } 158 159 160 164 public void afterPropertiesSet() throws MBeanServerNotFoundException, MBeanInfoRetrievalException { 165 if (this.connectOnStartup) { 166 if (this.server == null) { 167 connect(); 168 } 169 retrieveMBeanInfo(); 170 } 171 } 172 173 178 private void connect() throws MBeanServerNotFoundException { 179 if (this.serviceUrl != null) { 180 if (logger.isDebugEnabled()) { 181 logger.debug("Connecting to remote MBeanServer at URL [" + this.serviceUrl + "]"); 182 } 183 try { 184 this.connector = JMXConnectorFactory.connect(this.serviceUrl); 185 this.server = this.connector.getMBeanServerConnection(); 186 } 187 catch (IOException ex) { 188 throw new MBeanServerNotFoundException( 189 "Could not connect to remote MBeanServer at URL [" + this.serviceUrl + "]", ex); 190 } 191 } 192 else { 193 logger.debug("Attempting to locate local MBeanServer"); 194 this.server = locateMBeanServer(this.agentId); 195 } 196 } 197 198 212 protected MBeanServer locateMBeanServer(String agentId) throws MBeanServerNotFoundException { 213 return JmxUtils.locateMBeanServer(agentId); 214 } 215 216 221 private void retrieveMBeanInfo() throws MBeanServerNotFoundException, MBeanInfoRetrievalException { 222 try { 223 MBeanInfo info = this.server.getMBeanInfo(this.objectName); 224 225 MBeanAttributeInfo [] attributeInfo = info.getAttributes(); 227 this.allowedAttributes = new HashMap (attributeInfo.length); 228 229 for (int x = 0; x < attributeInfo.length; x++) { 230 this.allowedAttributes.put(attributeInfo[x].getName(), attributeInfo[x]); 231 } 232 233 MBeanOperationInfo [] operationInfo = info.getOperations(); 235 this.allowedOperations = new HashMap (operationInfo.length); 236 237 for (int x = 0; x < operationInfo.length; x++) { 238 MBeanOperationInfo opInfo = operationInfo[x]; 239 this.allowedOperations.put( 240 new MethodCacheKey( 241 opInfo.getName(), JmxUtils.parameterInfoToTypes(opInfo.getSignature())), opInfo); 242 } 243 } 244 catch (ClassNotFoundException ex) { 245 throw new MBeanInfoRetrievalException("Unable to locate class specified in method signature", ex); 246 } 247 catch (IntrospectionException ex) { 248 throw new MBeanInfoRetrievalException("Unable to obtain MBean info for bean [" + this.objectName + "]", ex); 249 } 250 catch (InstanceNotFoundException ex) { 251 throw new MBeanInfoRetrievalException("Unable to obtain MBean info for bean [" + this.objectName + 253 "]: it is likely that this bean was unregistered during the proxy creation process", 254 ex); 255 } 256 catch (ReflectionException ex) { 257 throw new MBeanInfoRetrievalException("Unable to read MBean info for bean [ " + this.objectName + "]", ex); 258 } 259 catch (IOException ex) { 260 throw new MBeanInfoRetrievalException( 261 "An IOException occurred when communicating with the MBeanServer. " + 262 "It is likely that you are communicating with a remote MBeanServer. " + 263 "Check the inner exception for exact details.", ex); 264 } 265 } 266 267 268 280 public Object invoke(MethodInvocation invocation) throws Throwable { 281 if (!this.connectOnStartup) { 283 synchronized (this) { 284 if (this.server == null) { 285 logger.debug("Lazily establishing MBeanServer connection"); 286 connect(); 287 } 288 289 if (this.allowedAttributes == null) { 290 logger.debug("Lazily initializing MBeanInfo cache"); 291 retrieveMBeanInfo(); 292 } 293 } 294 } 295 296 try { 297 PropertyDescriptor pd = BeanUtils.findPropertyForMethod(invocation.getMethod()); 298 if (pd != null) { 299 return invokeAttribute(pd, invocation); 300 } 301 else { 302 return invokeOperation(invocation.getMethod(), invocation.getArguments()); 303 } 304 } 305 catch (JMException ex) { 306 throw new InvocationFailureException("JMX access failed", ex); 307 } 308 catch (IOException ex) { 309 throw new InvocationFailureException("JMX access failed", ex); 310 } 311 } 312 313 private Object invokeAttribute(PropertyDescriptor pd, MethodInvocation invocation) 314 throws JMException , IOException { 315 316 String attributeName = JmxUtils.getAttributeName(pd, this.useStrictCasing); 317 MBeanAttributeInfo inf = (MBeanAttributeInfo ) this.allowedAttributes.get(attributeName); 318 319 if (inf == null) { 322 throw new InvalidInvocationException( 323 "Attribute '" + pd.getName() + "' is not exposed on the management interface"); 324 } 325 if (invocation.getMethod().equals(pd.getReadMethod())) { 326 if (inf.isReadable()) { 327 return this.server.getAttribute(this.objectName, attributeName); 328 } 329 else { 330 throw new InvalidInvocationException("Attribute '" + attributeName + "' is not readable"); 331 } 332 } 333 else if (invocation.getMethod().equals(pd.getWriteMethod())) { 334 if (inf.isWritable()) { 335 server.setAttribute(this.objectName, new Attribute (attributeName, invocation.getArguments()[0])); 336 return null; 337 } 338 else { 339 throw new InvalidInvocationException("Attribute '" + attributeName + "' is not writable"); 340 } 341 } 342 else { 343 throw new IllegalStateException ( 344 "Method [" + invocation.getMethod() + "] is neither a bean property getter nor a setter"); 345 } 346 } 347 348 355 private Object invokeOperation(Method method, Object [] args) throws JMException , IOException { 356 MethodCacheKey key = new MethodCacheKey(method.getName(), method.getParameterTypes()); 357 MBeanOperationInfo info = (MBeanOperationInfo ) this.allowedOperations.get(key); 358 if (info == null) { 359 throw new InvalidInvocationException("Operation '" + method.getName() + 360 "' is not exposed on the management interface"); 361 } 362 363 String [] signature = null; 364 synchronized (this.signatureCache) { 365 signature = (String []) this.signatureCache.get(method); 366 if (signature == null) { 367 signature = JmxUtils.getMethodSignature(method); 368 this.signatureCache.put(method, signature); 369 } 370 } 371 return this.server.invoke(this.objectName, method.getName(), args, signature); 372 } 373 374 377 public void destroy() throws Exception { 378 if (this.connector != null) { 379 this.connector.close(); 380 } 381 } 382 383 384 388 private static class MethodCacheKey { 389 390 393 private final String name; 394 395 398 private final Class [] parameters; 399 400 407 public MethodCacheKey(String name, Class [] parameters) { 408 this.name = name; 409 if (parameters == null) { 410 this.parameters = new Class []{}; 411 } 412 else { 413 this.parameters = parameters; 414 } 415 } 416 417 public boolean equals(Object other) { 418 if (other == null) { 419 return false; 420 } 421 if (other == this) { 422 return true; 423 } 424 MethodCacheKey otherKey = null; 425 if (other instanceof MethodCacheKey) { 426 otherKey = (MethodCacheKey) other; 427 return this.name.equals(otherKey.name) && Arrays.equals(this.parameters, otherKey.parameters); 428 } 429 else { 430 return false; 431 } 432 } 433 434 public int hashCode() { 435 return this.name.hashCode(); 436 } 437 } 438 439 } 440 | Popular Tags |