KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > jmx > access > MBeanClientInterceptor


1 /*
2  * Copyright 2002-2007 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package org.springframework.jmx.access;
18
19 import java.beans.PropertyDescriptor JavaDoc;
20 import java.io.IOException JavaDoc;
21 import java.lang.reflect.Method JavaDoc;
22 import java.net.MalformedURLException JavaDoc;
23 import java.util.Arrays JavaDoc;
24 import java.util.HashMap JavaDoc;
25 import java.util.Map JavaDoc;
26
27 import javax.management.Attribute JavaDoc;
28 import javax.management.InstanceNotFoundException JavaDoc;
29 import javax.management.IntrospectionException JavaDoc;
30 import javax.management.JMException JavaDoc;
31 import javax.management.MBeanAttributeInfo JavaDoc;
32 import javax.management.MBeanInfo JavaDoc;
33 import javax.management.MBeanOperationInfo JavaDoc;
34 import javax.management.MBeanServer JavaDoc;
35 import javax.management.MBeanServerConnection JavaDoc;
36 import javax.management.MalformedObjectNameException JavaDoc;
37 import javax.management.ObjectName JavaDoc;
38 import javax.management.ReflectionException JavaDoc;
39 import javax.management.remote.JMXConnector JavaDoc;
40 import javax.management.remote.JMXConnectorFactory JavaDoc;
41 import javax.management.remote.JMXServiceURL JavaDoc;
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 /**
56  * <code>MethodInterceptor</code> implementation that routes calls to an MBean
57  * running on the supplied <code>MBeanServerConnection</code>. Works for both
58  * local and remote <code>MBeanServerConnection</code>s.
59  *
60  * <p>By default, the <code>MBeanClientInterceptor</code> will connect to the
61  * <code>MBeanServer</code> and cache MBean metadata at startup. This can
62  * be undesirable when running against a remote <code>MBeanServer</code>
63  * that may not be running when the application starts. Through setting the
64  * {@link #setConnectOnStartup(boolean) connectOnStartup} property to "false",
65  * you can defer this process until the first invocation against the proxy.
66  *
67  * <p>Requires JMX 1.2's <code>MBeanServerConnection</code> feature.
68  * As a consequence, this class will not work on JMX 1.0.
69  *
70  * <p>This functionality is usually used through <code>MBeanProxyFactoryBean</code>.
71  * See the javadoc of that class for more information.
72  *
73  * @author Rob Harrop
74  * @author Juergen Hoeller
75  * @since 1.2
76  * @see MBeanProxyFactoryBean
77  * @see #setConnectOnStartup
78  */

79 public class MBeanClientInterceptor implements MethodInterceptor, InitializingBean, DisposableBean {
80
81     /** Logger available to subclasses */
82     protected final Log logger = LogFactory.getLog(getClass());
83
84     private MBeanServerConnection JavaDoc server;
85
86     private JMXServiceURL JavaDoc serviceUrl;
87
88     private String JavaDoc agentId;
89
90     private boolean connectOnStartup = true;
91
92     private ObjectName JavaDoc objectName;
93
94     private boolean useStrictCasing = true;
95
96     private JMXConnector JavaDoc connector;
97
98     private Map JavaDoc allowedAttributes;
99
100     private Map JavaDoc allowedOperations;
101
102     private final Map JavaDoc signatureCache = new HashMap JavaDoc();
103
104
105     /**
106      * Set the <code>MBeanServerConnection</code> used to connect to the
107      * MBean which all invocations are routed to.
108      */

109     public void setServer(MBeanServerConnection JavaDoc server) {
110         this.server = server;
111     }
112
113     /**
114      * Set the service URL of the remote <code>MBeanServer</code>.
115      */

116     public void setServiceUrl(String JavaDoc url) throws MalformedURLException JavaDoc {
117         this.serviceUrl = new JMXServiceURL JavaDoc(url);
118     }
119
120     /**
121      * Set the agent id of the <code>MBeanServer</code> to locate.
122      * <p>Default is none. If specified, this will result in an
123      * attempt being made to locate the attendant MBeanServer, unless
124      * the {@link #setServiceUrl "serviceUrl"} property has been set.
125      * @see javax.management.MBeanServerFactory#findMBeanServer(String)
126      */

127     public void setAgentId(String JavaDoc agentId) {
128         this.agentId = agentId;
129     }
130
131     /**
132      * Set whether or not the proxy should connect to the <code>MBeanServer</code>
133      * at creation time ("true") or the first time it is invoked ("false").
134      * Default is "true".
135      */

136     public void setConnectOnStartup(boolean connectOnStartup) {
137         this.connectOnStartup = connectOnStartup;
138     }
139
140     /**
141      * Set the <code>ObjectName</code> of the MBean which calls are routed to,
142      * as <code>ObjectName</code> instance or as <code>String</code>.
143      */

144     public void setObjectName(Object JavaDoc objectName) throws MalformedObjectNameException JavaDoc {
145         this.objectName = ObjectNameManager.getInstance(objectName);
146     }
147
148     /**
149      * Set whether to use strict casing for attributes. Enabled by default.
150      * <p>When using strict casing, a JavaBean property with a getter such as
151      * <code>getFoo()</code> translates to an attribute called <code>Foo</code>.
152      * With strict casing disabled, <code>getFoo()</code> would translate to just
153      * <code>foo</code>.
154      */

155     public void setUseStrictCasing(boolean useStrictCasing) {
156         this.useStrictCasing = useStrictCasing;
157     }
158
159
160     /**
161      * Ensures that an <code>MBeanServerConnection</code> is configured and attempts to
162      * detect a local connection if one is not supplied.
163      */

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     /**
174      * Connects to the remote <code>MBeanServer</code> using the configured <code>JMXServiceURL</code>.
175      * @see #setServiceUrl(String)
176      * @see #setConnectOnStartup(boolean)
177      */

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 JavaDoc 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     /**
199      * Attempt to locate an existing <code>MBeanServer</code>.
200      * Called if no {@link #setServiceUrl "serviceUrl"} was specified.
201      * <p>The default implementation attempts to find an <code>MBeanServer</code> using
202      * a standard lookup. Subclasses may override to add additional location logic.
203      * @param agentId the agent identifier of the MBeanServer to retrieve.
204      * If this parameter is <code>null</code>, all registered MBeanServers are
205      * considered.
206      * @return the <code>MBeanServer</code> if found
207      * @throws org.springframework.jmx.MBeanServerNotFoundException
208      * if no <code>MBeanServer</code> could be found
209      * @see JmxUtils#locateMBeanServer(String)
210      * @see javax.management.MBeanServerFactory#findMBeanServer(String)
211      */

212     protected MBeanServer JavaDoc locateMBeanServer(String JavaDoc agentId) throws MBeanServerNotFoundException {
213         return JmxUtils.locateMBeanServer(agentId);
214     }
215
216     /**
217      * Loads the management interface info for the configured MBean into the caches.
218      * This information is used by the proxy when determining whether an invocation matches
219      * a valid operation or attribute on the management interface of the managed resource.
220      */

221     private void retrieveMBeanInfo() throws MBeanServerNotFoundException, MBeanInfoRetrievalException {
222         try {
223             MBeanInfo JavaDoc info = this.server.getMBeanInfo(this.objectName);
224
225             // get attributes
226
MBeanAttributeInfo JavaDoc[] attributeInfo = info.getAttributes();
227             this.allowedAttributes = new HashMap JavaDoc(attributeInfo.length);
228
229             for (int x = 0; x < attributeInfo.length; x++) {
230                 this.allowedAttributes.put(attributeInfo[x].getName(), attributeInfo[x]);
231             }
232
233             // get operations
234
MBeanOperationInfo JavaDoc[] operationInfo = info.getOperations();
235             this.allowedOperations = new HashMap JavaDoc(operationInfo.length);
236
237             for (int x = 0; x < operationInfo.length; x++) {
238                 MBeanOperationInfo JavaDoc opInfo = operationInfo[x];
239                 this.allowedOperations.put(
240                         new MethodCacheKey(
241                                 opInfo.getName(), JmxUtils.parameterInfoToTypes(opInfo.getSignature())), opInfo);
242             }
243         }
244         catch (ClassNotFoundException JavaDoc ex) {
245             throw new MBeanInfoRetrievalException("Unable to locate class specified in method signature", ex);
246         }
247         catch (IntrospectionException JavaDoc ex) {
248             throw new MBeanInfoRetrievalException("Unable to obtain MBean info for bean [" + this.objectName + "]", ex);
249         }
250         catch (InstanceNotFoundException JavaDoc ex) {
251             // if we are this far this shouldn't happen, but...
252
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 JavaDoc ex) {
257             throw new MBeanInfoRetrievalException("Unable to read MBean info for bean [ " + this.objectName + "]", ex);
258         }
259         catch (IOException JavaDoc 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     /**
269      * Route the invocation to the configured managed resource. Correctly routes JavaBean property
270      * access to <code>MBeanServerConnection.get/setAttribute</code> and method invocation to
271      * <code>MBeanServerConnection.invoke</code>. Any attempt to invoke a method that does not
272      * correspond to an attribute or operation defined in the management interface of the managed
273      * resource results in an <code>InvalidInvocationException</code>.
274      * @param invocation the <code>MethodInvocation</code> to re-route.
275      * @return the value returned as a result of the re-routed invocation.
276      * @throws InvocationFailureException if the invocation does not match an attribute or
277      * operation on the management interface of the resource.
278      * @throws Throwable typically as the result of an error during invocation
279      */

280     public Object JavaDoc invoke(MethodInvocation invocation) throws Throwable JavaDoc {
281         // Lazily connect to MBeanServer?
282
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 JavaDoc 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 JavaDoc ex) {
306             throw new InvocationFailureException("JMX access failed", ex);
307         }
308         catch (IOException JavaDoc ex) {
309             throw new InvocationFailureException("JMX access failed", ex);
310         }
311     }
312
313     private Object JavaDoc invokeAttribute(PropertyDescriptor JavaDoc pd, MethodInvocation invocation)
314             throws JMException JavaDoc, IOException JavaDoc {
315
316         String JavaDoc attributeName = JmxUtils.getAttributeName(pd, this.useStrictCasing);
317         MBeanAttributeInfo JavaDoc inf = (MBeanAttributeInfo JavaDoc) this.allowedAttributes.get(attributeName);
318
319         // If no attribute is returned, we know that it is not defined in the
320
// management interface.
321
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 JavaDoc(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 JavaDoc(
344                     "Method [" + invocation.getMethod() + "] is neither a bean property getter nor a setter");
345         }
346     }
347
348     /**
349      * Routes a method invocation (not a property get/set) to the corresponding
350      * operation on the managed resource.
351      * @param method the method corresponding to operation on the managed resource.
352      * @param args the invocation arguments
353      * @return the value returned by the method invocation.
354      */

355     private Object JavaDoc invokeOperation(Method JavaDoc method, Object JavaDoc[] args) throws JMException JavaDoc, IOException JavaDoc {
356         MethodCacheKey key = new MethodCacheKey(method.getName(), method.getParameterTypes());
357         MBeanOperationInfo JavaDoc info = (MBeanOperationInfo JavaDoc) 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 JavaDoc[] signature = null;
364         synchronized (this.signatureCache) {
365             signature = (String JavaDoc[]) 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     /**
375      * Closes any <code>JMXConnector</code> that may be managed by this interceptor.
376      */

377     public void destroy() throws Exception JavaDoc {
378         if (this.connector != null) {
379             this.connector.close();
380         }
381     }
382
383
384     /**
385      * Simple wrapper class around a method name and its signature.
386      * Used as the key when caching methods.
387      */

388     private static class MethodCacheKey {
389
390         /**
391          * the name of the method
392          */

393         private final String JavaDoc name;
394
395         /**
396          * the arguments in the method signature.
397          */

398         private final Class JavaDoc[] parameters;
399
400         /**
401          * Create a new instance of <code>MethodCacheKey</code> with the supplied
402          * method name and parameter list.
403          *
404          * @param name the name of the method.
405          * @param parameters the arguments in the method signature.
406          */

407         public MethodCacheKey(String JavaDoc name, Class JavaDoc[] parameters) {
408             this.name = name;
409             if (parameters == null) {
410                 this.parameters = new Class JavaDoc[]{};
411             }
412             else {
413                 this.parameters = parameters;
414             }
415         }
416
417         public boolean equals(Object JavaDoc 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