001    /*
002     $Id: GroovyMBean.java 3747 2006-04-17 12:56:57Z glaforge $
003    
004     Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
005    
006     Redistribution and use of this software and associated documentation
007     ("Software"), with or without modification, are permitted provided
008     that the following conditions are met:
009    
010     1. Redistributions of source code must retain copyright
011        statements and notices.  Redistributions must also contain a
012        copy of this document.
013    
014     2. Redistributions in binary form must reproduce the
015        above copyright notice, this list of conditions and the
016        following disclaimer in the documentation and/or other
017        materials provided with the distribution.
018    
019     3. The name "groovy" must not be used to endorse or promote
020        products derived from this Software without prior written
021        permission of The Codehaus.  For written permission,
022        please contact info@codehaus.org.
023    
024     4. Products derived from this Software may not be called "groovy"
025        nor may "groovy" appear in their names without prior written
026        permission of The Codehaus. "groovy" is a registered
027        trademark of The Codehaus.
028    
029     5. Due credit should be given to The Codehaus -
030        http://groovy.codehaus.org/
031    
032     THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
033     ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
034     NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
035     FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
036     THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
037     INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
038     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
039     SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
040     HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
041     STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
042     ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
043     OF THE POSSIBILITY OF SUCH DAMAGE.
044    
045     */
046    package groovy.util;
047    
048    import groovy.lang.GroovyObjectSupport;
049    import groovy.lang.GroovyRuntimeException;
050    
051    import java.util.HashMap;
052    import java.util.Map;
053    import java.util.Collection;
054    import java.util.ArrayList;
055    import java.util.List;
056    import java.util.Iterator;
057    import java.io.IOException;
058    
059    import javax.management.Attribute;
060    import javax.management.JMException;
061    import javax.management.MBeanException;
062    import javax.management.MBeanInfo;
063    import javax.management.MBeanOperationInfo;
064    import javax.management.MBeanParameterInfo;
065    import javax.management.ObjectName;
066    import javax.management.MBeanServerConnection;
067    import javax.management.MBeanAttributeInfo;
068    
069    
070    /**
071     * A GroovyObject facade for an underlying MBean which acts like a normal
072     * groovy object but which is actually implemented via
073     * an underlying JMX MBean.
074     * Properties and normal method invocations
075     * delegate to the MBeanServer to the actual MBean.
076     *
077     * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
078     * @author Steve Button
079     * @version $Revision: 3747 $
080     */
081    public class GroovyMBean extends GroovyObjectSupport {
082    
083        private MBeanServerConnection server;
084        private ObjectName name;
085        private MBeanInfo beanInfo;
086        private Map operations = new HashMap();
087    
088    
089        public GroovyMBean(MBeanServerConnection server, ObjectName name) throws JMException, IOException {
090            this.server = server;
091            this.name = name;
092            this.beanInfo = server.getMBeanInfo(name);
093    
094    
095            MBeanOperationInfo[] operationInfos = beanInfo.getOperations();
096            for (int i = 0; i < operationInfos.length; i++) {
097                MBeanOperationInfo info = operationInfos[i];
098                String signature[] = createSignature(info);
099    
100                // Include a simple fix here to support overloaded operations on the MBean.
101                // Construct a simple key for an operation by adding the number of parameters it uses
102                String operationKey = createOperationKey(info.getName(), signature.length);
103                operations.put(operationKey, signature);
104            }
105    
106        }
107    
108        public MBeanServerConnection server() {
109            return server;
110        }
111    
112        public ObjectName name() {
113            return name;
114        }
115    
116        public MBeanInfo info() {
117            return beanInfo;
118        }
119    
120        public Object getProperty(String property) {
121            try {
122                return server.getAttribute(name, property);
123            }
124            catch (MBeanException e) {
125                throw new GroovyRuntimeException("Could not access property: " + property + ". Reason: " + e, e.getTargetException());
126            }
127            catch (Exception e) {
128                throw new GroovyRuntimeException("Could not access property: " + property + ". Reason: " + e, e);
129            }
130        }
131    
132        public void setProperty(String property, Object value) {
133            try {
134                server.setAttribute(name, new Attribute(property, value));
135            }
136            catch (MBeanException e) {
137                throw new GroovyRuntimeException("Could not set property: " + property + ". Reason: " + e, e.getTargetException());
138            }
139            catch (Exception e) {
140                throw new GroovyRuntimeException("Could not set property: " + property + ". Reason: " + e, e);
141            }
142        }
143    
144        public Object invokeMethod(String method, Object arguments) {
145            // Moved this outside the try block so we can obtain the number of parameters
146            // specified in the arguments array, which is needed to find the correct method.
147            Object[] argArray = null;
148            if (arguments instanceof Object[]) {
149                argArray = (Object[]) arguments;
150            } else {
151                argArray = new Object[]{arguments};
152            }
153            // Locate the specific method based on the name and number of parameters
154            String operationKey = createOperationKey(method, argArray.length);
155            String[] signature = (String[]) operations.get(operationKey);
156    
157            if (signature != null) {
158                try {
159                    return server.invoke(name, method, argArray, signature);
160                }
161                catch (MBeanException e) {
162                    throw new GroovyRuntimeException("Could not invoke method: " + method + ". Reason: " + e, e.getTargetException());
163                }
164                catch (Exception e) {
165                    throw new GroovyRuntimeException("Could not invoke method: " + method + ". Reason: " + e, e);
166                }
167            } else {
168                return super.invokeMethod(method, arguments);
169            }
170        }
171    
172        protected String[] createSignature(MBeanOperationInfo info) {
173            MBeanParameterInfo[] params = info.getSignature();
174            String[] answer = new String[params.length];
175            for (int i = 0; i < params.length; i++) {
176                answer[i] = params[i].getType();
177            }
178            return answer;
179        }
180    
181        /**
182         * Construct a simple key based on the method name and the number of parameters
183         *
184         * @param operation - the mbean operation name
185         * @param params    - the number of parameters the operation supports
186         * @return simple unique identifier for a method
187         */
188        protected String createOperationKey(String operation, int params) {
189            // This could be changed to support some hash of the parameter types, etc.
190            return operation + "_" + params;
191        }
192    
193        /**
194         * List of the names of each of the attributes on the MBean
195         *
196         * @return list of attribute names
197         */
198        public Collection listAttributeNames() {
199            ArrayList list = new ArrayList();
200            try {
201                MBeanAttributeInfo[] attrs = beanInfo.getAttributes();
202                for (int i = 0; i < attrs.length; i++) {
203                    MBeanAttributeInfo attr = attrs[i];
204                    list.add(attr.getName());
205                }
206            }
207            catch (Throwable t) {
208            }
209            finally {
210            }
211            return list;
212        }
213    
214        /**
215         * The values of each of the attributes on the MBean
216         *
217         * @return list of values of each attribute
218         */
219        public List listAttributeValues() {
220            ArrayList list = new ArrayList();
221            Collection names = listAttributeNames();
222            for (Iterator iterator = names.iterator(); iterator.hasNext();) {
223                String name = (String) iterator.next();
224                try {
225                    Object val = this.getProperty(name);
226                    if (val != null) {
227                        list.add(name + " : " + val.toString());
228                    }
229                }
230                catch (RuntimeException e) {
231                    // todo: fix this behaviour properly
232                    // Do nothing here, just handle the error silently.
233                    //e.printStackTrace();
234                }
235            }
236            return list;
237        }
238    
239    
240        /**
241         * List of string representations of all of the attributes on the MBean.
242         *
243         * @return list of descriptions of each attribute on the mbean
244         */
245        public Collection listAttributeDescriptions() {
246            ArrayList list = new ArrayList();
247            try {
248                MBeanAttributeInfo[] attrs = beanInfo.getAttributes();
249                for (int i = 0; i < attrs.length; i++) {
250                    MBeanAttributeInfo attr = attrs[i];
251                    list.add(describeAttribute(attr));
252                }
253            }
254            catch (Throwable t) {
255            }
256            finally {
257            }
258            return list;
259        }
260    
261        /**
262         * Description of the specified attribute name.
263         *
264         * @param attr - the attribute
265         * @return String the description
266         */
267        protected String describeAttribute(MBeanAttributeInfo attr) {
268            StringBuffer buf = new StringBuffer();
269            buf.append("(");
270            if (attr.isReadable()) {
271                buf.append("r");
272            }
273            if (attr.isWritable()) {
274                buf.append("w");
275            }
276            buf.append(") ")
277                    .append(attr.getType())
278                    .append(" ")
279                    .append(attr.getName());
280            return buf.toString();
281        }
282    
283        /**
284         * Description of the specified attribute name.
285         *
286         * @param attributeName - stringified name of the attribute
287         * @return the description
288         */
289        public String describeAttribute(String attributeName) {
290            String ret = "Attribute not found";
291            try {
292                MBeanAttributeInfo[] attributes = beanInfo.getAttributes();
293                for (int i = 0; i < attributes.length; i++) {
294                    MBeanAttributeInfo attribute = attributes[i];
295                    if (attribute.getName().equals(attributeName)) {
296                        return describeAttribute(attribute);
297                    }
298                }
299            }
300            catch (Throwable t) {
301            }
302            return ret;
303        }
304    
305        /**
306         * Names of all the operations available on the MBean.
307         *
308         * @return all the operations on the MBean
309         */
310        public Collection listOperationNames() {
311            ArrayList list = new ArrayList();
312            try {
313                MBeanOperationInfo[] operations = beanInfo.getOperations();
314                for (int i = 0; i < operations.length; i++) {
315                    MBeanOperationInfo operation = operations[i];
316                    list.add(operation.getName());
317                }
318            }
319            catch (Throwable t) {
320            }
321            return list;
322        }
323    
324    
325        /**
326         * Description of all of the operations available on the MBean.
327         *
328         * @return full description of each operation on the MBean
329         */
330        public Collection listOperationDescriptions() {
331            ArrayList list = new ArrayList();
332            try {
333                MBeanOperationInfo[] operations = beanInfo.getOperations();
334                for (int i = 0; i < operations.length; i++) {
335                    MBeanOperationInfo operation = operations[i];
336                    list.add(describeOperation(operation));
337                }
338            }
339            catch (Throwable t) {
340            }
341            return list;
342        }
343    
344        /**
345         * Get the dessciptions of the named operation.  This returns a Collection since
346         * operations can be overloaded and one operationName can have multiple forms.
347         *
348         * @param operationName
349         * @return Collection of operation description
350         */
351        public List describeOperation(String operationName) {
352            ArrayList list = new ArrayList();
353            try {
354                MBeanOperationInfo[] operations = beanInfo.getOperations();
355                for (int i = 0; i < operations.length; i++) {
356                    MBeanOperationInfo operation = operations[i];
357                    if (operation.getName().equals(operationName)) {
358                        list.add(describeOperation(operation));
359                    }
360                }
361            }
362            catch (Throwable t) {
363            }
364            return list;
365        }
366    
367        /**
368         * Dessciption of the named operation.
369         *
370         * @param operation
371         * @return description
372         */
373        protected String describeOperation(MBeanOperationInfo operation) {
374            StringBuffer buf = new StringBuffer();
375            buf.append(operation.getReturnType())
376                    .append(" ")
377                    .append(operation.getName())
378                    .append("(");
379    
380            MBeanParameterInfo[] params = operation.getSignature();
381            for (int j = 0; j < params.length; j++) {
382                MBeanParameterInfo param = params[j];
383                if (j != 0) {
384                    buf.append(", ");
385                }
386                buf.append(param.getType())
387                        .append(" ")
388                        .append(param.getName());
389            }
390            buf.append(")");
391            return buf.toString();
392        }
393    
394    
395        /**
396         * Return an end user readable representation of the underlying MBean
397         * @return the user readable description
398         */
399        public String toString() {
400            StringBuffer buf = new StringBuffer();
401            buf.append("MBean Name:")
402                    .append("\n  ")
403                    .append(name.getCanonicalName())
404                    .append("\n  ");
405            if (!listAttributeDescriptions().isEmpty()) {
406                buf.append("\nAttributes:");
407                for (Iterator iterator = listAttributeDescriptions().iterator(); iterator.hasNext();) {
408                    buf.append("\n  ")
409                            .append((String) iterator.next());
410                }
411            }
412            if (!listOperationDescriptions().isEmpty()) {
413                buf.append("\nOperations:");
414                for (Iterator iterator = listOperationDescriptions().iterator(); iterator.hasNext();) {
415                    buf.append("\n  ")
416                            .append((String) iterator.next());
417                }
418            }
419            return buf.toString();
420      }
421    }