001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     * 
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     * 
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    
019    package org.apache.commons.modeler;
020    
021    
022    import java.lang.reflect.InvocationTargetException;
023    import java.lang.reflect.Method;
024    import java.util.HashMap;
025    import java.util.Hashtable;
026    import java.util.Iterator;
027    
028    import javax.management.Attribute;
029    import javax.management.AttributeChangeNotification;
030    import javax.management.AttributeList;
031    import javax.management.AttributeNotFoundException;
032    import javax.management.Descriptor;
033    import javax.management.DynamicMBean;
034    import javax.management.InstanceNotFoundException;
035    import javax.management.InvalidAttributeValueException;
036    import javax.management.ListenerNotFoundException;
037    import javax.management.MBeanException;
038    import javax.management.MBeanInfo;
039    import javax.management.MBeanNotificationInfo;
040    import javax.management.MBeanRegistration;
041    import javax.management.MBeanServer;
042    import javax.management.Notification;
043    import javax.management.NotificationFilter;
044    import javax.management.NotificationListener;
045    import javax.management.ObjectName;
046    import javax.management.ReflectionException;
047    import javax.management.RuntimeErrorException;
048    import javax.management.RuntimeOperationsException;
049    import javax.management.ServiceNotFoundException;
050    import javax.management.modelmbean.DescriptorSupport;
051    import javax.management.modelmbean.InvalidTargetObjectTypeException;
052    import javax.management.modelmbean.ModelMBean;
053    import javax.management.modelmbean.ModelMBeanAttributeInfo;
054    import javax.management.modelmbean.ModelMBeanInfo;
055    import javax.management.modelmbean.ModelMBeanInfoSupport;
056    import javax.management.modelmbean.ModelMBeanNotificationInfo;
057    import javax.management.modelmbean.ModelMBeanOperationInfo;
058    
059    import org.apache.commons.logging.Log;
060    import org.apache.commons.logging.LogFactory;
061    import org.apache.commons.modeler.modules.ModelerSource;
062    
063    // TODO: enable ant-like substitutions ? ( or at least discuss it )
064    
065    /**
066     * <p>Basic implementation of the <code>ModelMBean</code> interface, which
067     * supports the minimal requirements of the interface contract.</p>
068     *
069     * <p>This can be used directly to wrap an existing java bean, or inside
070     * an mlet or anywhere an MBean would be used. The String parameter
071     * passed to the constructor will be used to construct an instance of the
072     * real object that we wrap.
073     *
074     * Limitations:
075     * <ul>
076     * <li>Only managed resources of type <code>objectReference</code> are
077     *     supportd.</li>
078     * <li>Caching of attribute values and operation results is not supported.
079     *     All calls to <code>invoke()</code> are immediately executed.</li>
080     * <li>Logging (under control of descriptors) is not supported.</li>
081     * <li>Persistence of MBean attributes and operations is not supported.</li>
082     * <li>All classes referenced as attribute types, operation parameters, or
083     *     operation return values must be one of the following:
084     *     <ul>
085     *     <li>One of the Java primitive types (boolean, byte, char, double,
086     *         float, integer, long, short).  Corresponding value will be wrapped
087     *         in the appropriate wrapper class automatically.</li>
088     *     <li>Operations that return no value should declare a return type of
089     *         <code>void</code>.</li>
090     *     </ul>
091     * <li>Attribute caching is not supported</li>
092     * </ul>
093     *
094     * @author Craig R. McClanahan
095     * @author Costin Manolache
096     * @version $Revision: 480402 $ $Date: 2006-11-29 05:43:23 +0100 (Wed, 29 Nov 2006) $
097     */
098    
099    public class BaseModelMBean implements ModelMBean, MBeanRegistration {
100        private static Log log = LogFactory.getLog(BaseModelMBean.class);
101    
102        // ----------------------------------------------------------- Constructors
103    
104        /**
105         * Construct a <code>ModelMBean</code> with default
106         * <code>ModelMBeanInfo</code> information.
107         *
108         * @exception MBeanException if the initializer of an object
109         *  throws an exception
110         * @exception RuntimeOperationsException if an IllegalArgumentException
111         *  occurs
112         */
113        public BaseModelMBean() throws MBeanException, RuntimeOperationsException {
114    
115            super();
116            if( log.isDebugEnabled()) log.debug("default constructor");
117            setModelMBeanInfo(createDefaultModelMBeanInfo());
118        }
119    
120    
121        /**
122         * Construct a <code>ModelMBean</code> associated with the specified
123         * <code>ModelMBeanInfo</code> information.
124         *
125         * @param info ModelMBeanInfo for this MBean
126         *
127         * @exception MBeanException if the initializer of an object
128         *  throws an exception
129         * @exception RuntimeOperationsException if an IllegalArgumentException
130         *  occurs
131         */
132        public BaseModelMBean(ModelMBeanInfo info)
133            throws MBeanException, RuntimeOperationsException {
134            // XXX should be deprecated - just call setInfo
135            super();
136            setModelMBeanInfo(info);
137            if( log.isDebugEnabled()) log.debug("ModelMBeanInfo constructor");
138        }
139    
140        /** Construct a ModelMBean of a specified type.
141         *  The type can be a class name or the key used in one of the descriptors.
142         *
143         * If no descriptor is available, we'll first try to locate one in
144         * the same package with the class, then use introspection.
145         *
146         * The mbean resource will be created.
147         *
148         * @param type Class name or the type key used in the descriptor.
149         * @throws MBeanException
150         * @throws RuntimeOperationsException
151         */
152        public BaseModelMBean( String type )
153            throws MBeanException, RuntimeOperationsException
154        {
155            try {
156                // This constructor is used from <mlet>, it should create
157                // the resource
158                setModeledType(type);
159            } catch( Throwable ex ) {
160                log.error( "Error creating mbean ", ex);
161            }
162        }
163    
164        public BaseModelMBean( String type, ModelerSource source )
165            throws MBeanException, RuntimeOperationsException
166        {
167            try {
168                setModeledType(type);
169            } catch( Throwable ex ) {
170                log.error( "Error creating mbean ", ex);
171            }
172            this.source=source;
173        }
174    
175        // ----------------------------------------------------- Instance Variables
176    
177    
178        /**
179         * Notification broadcaster for attribute changes.
180         */
181        protected BaseNotificationBroadcaster attributeBroadcaster = null;
182    
183        /** Registry we are associated with
184         */
185        protected Registry registry=null;
186    
187        /**
188         * Notification broadcaster for general notifications.
189         */
190        protected BaseNotificationBroadcaster generalBroadcaster = null;
191    
192        protected ObjectName oname=null;
193    
194        /**
195         * The <code>ModelMBeanInfo</code> object that controls our activity.
196         */
197        protected ModelMBeanInfo info = null;
198    
199    
200        /**
201         * The managed resource this MBean is associated with (if any).
202         */
203        protected Object resource = null;
204        protected String resourceType = null;
205    
206        /** Source object used to read this mbean. Can be used to
207         * persist the mbean
208         */
209        protected ModelerSource source=null;
210    
211        /** Attribute values. XXX That can be stored in the value Field
212         */
213        protected HashMap attributes=new HashMap();
214    
215        // --------------------------------------------------- DynamicMBean Methods
216        static final Object[] NO_ARGS_PARAM=new Object[0];
217        static final Class[] NO_ARGS_PARAM_SIG=new Class[0];
218        // key: attribute val: getter method
219        private Hashtable getAttMap=new Hashtable();
220    
221        // key: attribute val: setter method
222        private Hashtable setAttMap=new Hashtable();
223    
224        // key: operation val: invoke method
225        private Hashtable invokeAttMap=new Hashtable();
226    
227        /**
228         * Obtain and return the value of a specific attribute of this MBean.
229         *
230         * @param name Name of the requested attribute
231         *
232         * @exception AttributeNotFoundException if this attribute is not
233         *  supported by this MBean
234         * @exception MBeanException if the initializer of an object
235         *  throws an exception
236         * @exception ReflectionException if a Java reflection exception
237         *  occurs when invoking the getter
238         */
239        public Object getAttribute(String name)
240            throws AttributeNotFoundException, MBeanException,
241                ReflectionException {
242            // Validate the input parameters
243            if (name == null)
244                throw new RuntimeOperationsException
245                    (new IllegalArgumentException("Attribute name is null"),
246                     "Attribute name is null");
247    
248            if( (resource instanceof DynamicMBean) && 
249                 ! ( resource instanceof BaseModelMBean )) {
250                return ((DynamicMBean)resource).getAttribute(name);
251            }
252            
253            // Extract the method from cache
254            Method m=(Method)getAttMap.get( name );
255    
256            if( m==null ) {
257                // Look up the actual operation to be used
258                ModelMBeanAttributeInfo attrInfo = info.getAttribute(name);
259                if (attrInfo == null)
260                    throw new AttributeNotFoundException(" Cannot find attribute " + name);
261                Descriptor attrDesc = attrInfo.getDescriptor();
262                if (attrDesc == null)
263                    throw new AttributeNotFoundException("Cannot find attribute " + name + " descriptor");
264                String getMethod = (String) attrDesc.getFieldValue("getMethod");
265    
266                if (getMethod == null)
267                    throw new AttributeNotFoundException("Cannot find attribute " + name + " get method name");
268    
269                Object object = null;
270                NoSuchMethodException exception = null;
271                try {
272                    object = this;
273                    m = object.getClass().getMethod(getMethod, NO_ARGS_PARAM_SIG);
274                } catch (NoSuchMethodException e) {
275                    exception = e;;
276                }
277                if( m== null && resource != null ) {
278                    try {
279                        object = resource;
280                        m = object.getClass().getMethod(getMethod, NO_ARGS_PARAM_SIG);
281                        exception=null;
282                    } catch (NoSuchMethodException e) {
283                        exception = e;
284                    }
285                }
286                if( exception != null )
287                    throw new ReflectionException(exception,
288                                                  "Cannot find getter method " + getMethod);
289                getAttMap.put( name, m );
290            }
291    
292            Object result = null;
293            try {
294                Class declaring=m.getDeclaringClass();
295                // workaround for catalina weird mbeans - the declaring class is BaseModelMBean.
296                // but this is the catalina class.
297                if( declaring.isAssignableFrom(this.getClass()) ) {
298                    result = m.invoke(this, NO_ARGS_PARAM );
299                } else {
300                    result = m.invoke(resource, NO_ARGS_PARAM );
301                }
302            } catch (InvocationTargetException e) {
303                Throwable t = e.getTargetException();
304                if (t == null)
305                    t = e;
306                if (t instanceof RuntimeException)
307                    throw new RuntimeOperationsException
308                        ((RuntimeException) t, "Exception invoking method " + name);
309                else if (t instanceof Error)
310                    throw new RuntimeErrorException
311                        ((Error) t, "Error invoking method " + name);
312                else
313                    throw new MBeanException
314                        (e, "Exception invoking method " + name);
315            } catch (Exception e) {
316                throw new MBeanException
317                    (e, "Exception invoking method " + name);
318            }
319    
320            // Return the results of this method invocation
321            // FIXME - should we validate the return type?
322            return (result);
323        }
324    
325    
326        /**
327         * Obtain and return the values of several attributes of this MBean.
328         *
329         * @param names Names of the requested attributes
330         */
331        public AttributeList getAttributes(String names[]) {
332    
333            // Validate the input parameters
334            if (names == null)
335                throw new RuntimeOperationsException
336                    (new IllegalArgumentException("Attribute names list is null"),
337                     "Attribute names list is null");
338    
339            // Prepare our response, eating all exceptions
340            AttributeList response = new AttributeList();
341            for (int i = 0; i < names.length; i++) {
342                try {
343                    response.add(new Attribute(names[i],getAttribute(names[i])));
344                } catch (Exception e) {
345                    ; // Not having a particular attribute in the response
346                    ; // is the indication of a getter problem
347                }
348            }
349            return (response);
350    
351        }
352    
353    
354        /**
355         * Return the <code>MBeanInfo</code> object for this MBean.
356         */
357        public MBeanInfo getMBeanInfo() {
358            // XXX Why do we have to clone ?
359            if( info== null ) return null;
360            return ((MBeanInfo) info.clone());
361        }
362    
363    
364        /**
365         * Invoke a particular method on this MBean, and return any returned
366         * value.
367         *
368         * <p><strong>IMPLEMENTATION NOTE</strong> - This implementation will
369         * attempt to invoke this method on the MBean itself, or (if not
370         * available) on the managed resource object associated with this
371         * MBean.</p>
372         *
373         * @param name Name of the operation to be invoked
374         * @param params Array containing the method parameters of this operation
375         * @param signature Array containing the class names representing
376         *  the signature of this operation
377         *
378         * @exception MBeanException if the initializer of an object
379         *  throws an exception
380         * @exception ReflectioNException if a Java reflection exception
381         *  occurs when invoking a method
382         */
383        public Object invoke(String name, Object params[], String signature[])
384            throws MBeanException, ReflectionException 
385        {
386            if( (resource instanceof DynamicMBean) && 
387                 ! ( resource instanceof BaseModelMBean )) {
388                return ((DynamicMBean)resource).invoke(name, params, signature);
389            }
390        
391            // Validate the input parameters
392            if (name == null)
393                throw new RuntimeOperationsException
394                    (new IllegalArgumentException("Method name is null"),
395                     "Method name is null");
396    
397            if( log.isDebugEnabled()) log.debug("Invoke " + name);
398            MethodKey mkey = new MethodKey(name, signature);
399            Method method=(Method)invokeAttMap.get(mkey);
400            if( method==null ) {
401                if (params == null)
402                    params = new Object[0];
403                if (signature == null)
404                    signature = new String[0];
405                if (params.length != signature.length)
406                    throw new RuntimeOperationsException
407                        (new IllegalArgumentException("Inconsistent arguments and signature"),
408                         "Inconsistent arguments and signature");
409    
410                // Acquire the ModelMBeanOperationInfo information for
411                // the requested operation
412                ModelMBeanOperationInfo opInfo = info.getOperation(name);
413                if (opInfo == null)
414                    throw new MBeanException
415                        (new ServiceNotFoundException("Cannot find operation " + name),
416                         "Cannot find operation " + name);
417    
418                // Prepare the signature required by Java reflection APIs
419                // FIXME - should we use the signature from opInfo?
420                Class types[] = new Class[signature.length];
421                for (int i = 0; i < signature.length; i++) {
422                    types[i]=getAttributeClass( signature[i] );
423                }
424    
425                // Locate the method to be invoked, either in this MBean itself
426                // or in the corresponding managed resource
427                // FIXME - Accessible methods in superinterfaces?
428                Object object = null;
429                Exception exception = null;
430                try {
431                    object = this;
432                    method = object.getClass().getMethod(name, types);
433                } catch (NoSuchMethodException e) {
434                    exception = e;;
435                }
436                try {
437                    if ((method == null) && (resource != null)) {
438                        object = resource;
439                        method = object.getClass().getMethod(name, types);
440                    }
441                } catch (NoSuchMethodException e) {
442                    exception = e;
443                }
444                if (method == null) {
445                    throw new ReflectionException(exception,
446                                                  "Cannot find method " + name +
447                                                  " with this signature");
448                }
449                invokeAttMap.put( mkey, method );
450            }
451    
452            // Invoke the selected method on the appropriate object
453            Object result = null;
454            try {
455                if( method.getDeclaringClass().isAssignableFrom( this.getClass()) ) {
456                    result = method.invoke(this, params );
457                } else {
458                    result = method.invoke(resource, params);
459                }
460            } catch (InvocationTargetException e) {
461                Throwable t = e.getTargetException();
462                log.error("Exception invoking method " + name , t );
463                if (t == null)
464                    t = e;
465                if (t instanceof RuntimeException)
466                    throw new RuntimeOperationsException
467                        ((RuntimeException) t, "Exception invoking method " + name);
468                else if (t instanceof Error)
469                    throw new RuntimeErrorException
470                        ((Error) t, "Error invoking method " + name);
471                else
472                    throw new MBeanException
473                        ((Exception)t, "Exception invoking method " + name);
474            } catch (Exception e) {
475                log.error("Exception invoking method " + name , e );
476                throw new MBeanException
477                    (e, "Exception invoking method " + name);
478            }
479    
480            // Return the results of this method invocation
481            // FIXME - should we validate the return type?
482            return (result);
483    
484        }
485    
486        private Class getAttributeClass(String signature)
487            throws ReflectionException
488        {
489            if (signature.equals(Boolean.TYPE.getName()))
490                return Boolean.TYPE;
491            else if (signature.equals(Byte.TYPE.getName()))
492                return Byte.TYPE;
493            else if (signature.equals(Character.TYPE.getName()))
494                return Character.TYPE;
495            else if (signature.equals(Double.TYPE.getName()))
496                return Double.TYPE;
497            else if (signature.equals(Float.TYPE.getName()))
498                return Float.TYPE;
499            else if (signature.equals(Integer.TYPE.getName()))
500                return Integer.TYPE;
501            else if (signature.equals(Long.TYPE.getName()))
502                return Long.TYPE;
503            else if (signature.equals(Short.TYPE.getName()))
504                return Short.TYPE;
505            else {
506                try {
507                    ClassLoader cl=Thread.currentThread().getContextClassLoader();
508                    if( cl!=null )
509                        return cl.loadClass(signature); 
510                } catch( ClassNotFoundException e ) {
511                }
512                try {
513                    return Class.forName(signature);
514                } catch (ClassNotFoundException e) {
515                    throw new ReflectionException
516                        (e, "Cannot find Class for " + signature);
517                }
518            }
519        }
520    
521        /**
522         * Set the value of a specific attribute of this MBean.
523         *
524         * @param attribute The identification of the attribute to be set
525         *  and the new value
526         *
527         * @exception AttributeNotFoundException if this attribute is not
528         *  supported by this MBean
529         * @exception MBeanException if the initializer of an object
530         *  throws an exception
531         * @exception ReflectionException if a Java reflection exception
532         *  occurs when invoking the getter
533         */
534        public void setAttribute(Attribute attribute)
535            throws AttributeNotFoundException, MBeanException,
536            ReflectionException
537        {
538            if( log.isDebugEnabled() )
539                log.debug("Setting attribute " + this + " " + attribute );
540    
541            if( (resource instanceof DynamicMBean) && 
542                 ! ( resource instanceof BaseModelMBean )) {
543                try {
544                    ((DynamicMBean)resource).setAttribute(attribute);
545                } catch (InvalidAttributeValueException e) {
546                    throw new MBeanException(e);                
547                }
548                return;
549            }
550            
551            // Validate the input parameters
552            if (attribute == null)
553                throw new RuntimeOperationsException
554                    (new IllegalArgumentException("Attribute is null"),
555                     "Attribute is null");
556    
557            String name = attribute.getName();
558            Object value = attribute.getValue();
559    
560            if (name == null)
561                throw new RuntimeOperationsException
562                    (new IllegalArgumentException("Attribute name is null"),
563                     "Attribute name is null");
564    
565            ModelMBeanAttributeInfo attrInfo=info.getAttribute(name);
566            if (attrInfo == null)
567                throw new AttributeNotFoundException("Cannot find attribute " + name);
568    
569            Descriptor attrDesc=attrInfo.getDescriptor();
570            if (attrDesc == null)
571                throw new AttributeNotFoundException("Cannot find attribute " + name + " descriptor");
572    
573            Object oldValue=null;
574            if( getAttMap.get(name) != null )
575                oldValue=getAttribute( name );
576    
577    
578            // Extract the method from cache
579            Method m=(Method)setAttMap.get( name );
580    
581            if( m==null ) {
582                // Look up the actual operation to be used
583                String setMethod = (String) attrDesc.getFieldValue("setMethod");
584                if (setMethod == null)
585                    throw new AttributeNotFoundException("Cannot find attribute " + name + " set method name");
586    
587                String argType=attrInfo.getType();
588    
589                Class signature[] = new Class[] { getAttributeClass( argType ) };
590    
591                Object object = null;
592                NoSuchMethodException exception = null;
593                try {
594                    object = this;
595                    m = object.getClass().getMethod(setMethod, signature);
596                } catch (NoSuchMethodException e) {
597                    exception = e;;
598                }
599                if( m== null && resource != null ) {
600                    try {
601                        object = resource;
602                        m = object.getClass().getMethod(setMethod, signature);
603                        exception=null;
604                    } catch (NoSuchMethodException e) {
605                        if( log.isDebugEnabled())
606                            log.debug("Method not found in resource " +resource);
607                        exception = e;
608                    }
609                }
610                if( exception != null )
611                    throw new ReflectionException(exception,
612                                                  "Cannot find setter method " + setMethod +
613                            " " + resource);
614                setAttMap.put( name, m );
615            }
616    
617            Object result = null;
618            try {
619                if( m.getDeclaringClass().isAssignableFrom( this.getClass()) ) {
620                    result = m.invoke(this, new Object[] { value });
621                } else {
622                    result = m.invoke(resource, new Object[] { value });
623                }
624            } catch (InvocationTargetException e) {
625                Throwable t = e.getTargetException();
626                if (t == null)
627                    t = e;
628                if (t instanceof RuntimeException)
629                    throw new RuntimeOperationsException
630                        ((RuntimeException) t, "Exception invoking method " + name);
631                else if (t instanceof Error)
632                    throw new RuntimeErrorException
633                        ((Error) t, "Error invoking method " + name);
634                else
635                    throw new MBeanException
636                        (e, "Exception invoking method " + name);
637            } catch (Exception e) {
638                log.error("Exception invoking method " + name , e );
639                throw new MBeanException
640                    (e, "Exception invoking method " + name);
641            }
642            try {
643                sendAttributeChangeNotification(new Attribute( name, oldValue),
644                        attribute);
645            } catch(Exception ex) {
646                log.error("Error sending notification " + name, ex);
647            }
648            attributes.put( name, value );
649            if( source != null ) {
650                // this mbean is asscoiated with a source - maybe we want to persist
651                source.updateField(oname, name, value);
652            }
653        }
654    
655        public String toString() {
656            if( resource==null ) 
657                return "BaseModelMbean[" + resourceType + "]";
658            return resource.toString();
659        }
660    
661        /**
662         * Set the values of several attributes of this MBean.
663         *
664         * @param attributes THe names and values to be set
665         *
666         * @return The list of attributes that were set and their new values
667         */
668        public AttributeList setAttributes(AttributeList attributes) {
669    
670            // Validate the input parameters
671            if (attributes == null)
672                throw new RuntimeOperationsException
673                    (new IllegalArgumentException("Attributes list is null"),
674                     "Attributes list is null");
675    
676            // Prepare and return our response, eating all exceptions
677            AttributeList response = new AttributeList();
678            String names[] = new String[attributes.size()];
679            int n = 0;
680            Iterator items = attributes.iterator();
681            while (items.hasNext()) {
682                Attribute item = (Attribute) items.next();
683                names[n++] = item.getName();
684                try {
685                    setAttribute(item);
686                } catch (Exception e) {
687                    ; // Ignore all exceptions
688                }
689            }
690    
691            return (getAttributes(names));
692    
693        }
694    
695    
696        // ----------------------------------------------------- ModelMBean Methods
697    
698    
699        /**
700         * Get the instance handle of the object against which we execute
701         * all methods in this ModelMBean management interface.
702         *
703         * @exception InstanceNotFoundException if the managed resource object
704         *  cannot be found
705         * @exception MBeanException if the initializer of the object throws
706         *  an exception
707         * @exception RuntimeOperationsException if the managed resource or the
708         *  resource type is <code>null</code> or invalid
709         */
710        public Object getManagedResource()
711            throws InstanceNotFoundException, InvalidTargetObjectTypeException,
712            MBeanException, RuntimeOperationsException {
713    
714            if (resource == null)
715                throw new RuntimeOperationsException
716                    (new IllegalArgumentException("Managed resource is null"),
717                     "Managed resource is null");
718    
719            return resource;
720    
721        }
722    
723    
724        /**
725         * Set the instance handle of the object against which we will execute
726         * all methods in this ModelMBean management interface.
727         *
728         * This method will detect and call "setModelMbean" method. A resource
729         * can implement this method to get a reference to the model mbean.
730         * The reference can be used to send notification and access the
731         * registry.
732         *
733         * @param resource The resource object to be managed
734         * @param type The type of reference for the managed resource
735         *  ("ObjectReference", "Handle", "IOR", "EJBHandle", or
736         *  "RMIReference")
737         *
738         * @exception InstanceNotFoundException if the managed resource object
739         *  cannot be found
740         * @exception InvalidTargetObjectTypeException if this ModelMBean is
741         *  asked to handle a reference type it cannot deal with
742         * @exception MBeanException if the initializer of the object throws
743         *  an exception
744         * @exception RuntimeOperationsException if the managed resource or the
745         *  resource type is <code>null</code> or invalid
746         */
747        public void setManagedResource(Object resource, String type)
748            throws InstanceNotFoundException, InvalidTargetObjectTypeException,
749            MBeanException, RuntimeOperationsException
750        {
751            if (resource == null)
752                throw new RuntimeOperationsException
753                    (new IllegalArgumentException("Managed resource is null"),
754                     "Managed resource is null");
755    
756            if (!"objectreference".equalsIgnoreCase(type))
757                throw new InvalidTargetObjectTypeException(type);
758    
759            this.resource = resource;
760            this.resourceType = resource.getClass().getName();
761            
762            // Make the resource aware of the model mbean.
763            try {
764                Method m=resource.getClass().getMethod("setModelMBean",
765                        new Class[] {ModelMBean.class});
766                if( m!= null ) {
767                    m.invoke(resource, new Object[] {this});
768                }
769            } catch( NoSuchMethodException t ) {
770                // ignore
771            } catch( Throwable t ) {
772                log.error( "Can't set model mbean ", t );
773            }
774        }
775    
776    
777        /**
778         * Initialize the <code>ModelMBeanInfo</code> associated with this
779         * <code>ModelMBean</code>.  After the information and associated
780         * descriptors have been customized, the <code>ModelMBean</code> should
781         * be registered with the associated <code>MBeanServer</code>.
782         *
783         * Currently the model can be set after registration. This behavior is
784         * deprecated and won't be supported in future versions.
785         *
786         * @param info The ModelMBeanInfo object to be used by this ModelMBean
787         *
788         * @exception MBeanException If an exception occurs recording this
789         *  ModelMBeanInfo information
790         * @exception RuntimeOperations if the specified parameter is
791         *  <code>null</code> or invalid
792         */
793        public void setModelMBeanInfo(ModelMBeanInfo info)
794            throws MBeanException, RuntimeOperationsException {
795    
796            if (info == null)
797                throw new RuntimeOperationsException
798                    (new IllegalArgumentException("ModelMBeanInfo is null"),
799                     "ModelMBeanInfo is null");
800    
801            if (!isModelMBeanInfoValid(info))
802                throw new RuntimeOperationsException
803                    (new IllegalArgumentException("ModelMBeanInfo is invalid"),
804                     "ModelMBeanInfo is invalid");
805    
806            this.info = (ModelMBeanInfo) info.clone();
807    
808        }
809    
810    
811        // ------------------------------ ModelMBeanNotificationBroadcaster Methods
812    
813    
814        /**
815         * Add an attribute change notification event listener to this MBean.
816         *
817         * @param listener Listener that will receive event notifications
818         * @param name Name of the attribute of interest, or <code>null</code>
819         *  to indicate interest in all attributes
820         * @param handback Handback object to be sent along with event
821         *  notifications
822         *
823         * @exception IllegalArgumentException if the listener parameter is null
824         */
825        public void addAttributeChangeNotificationListener
826            (NotificationListener listener, String name, Object handback)
827            throws IllegalArgumentException {
828    
829            if (listener == null)
830                throw new IllegalArgumentException("Listener is null");
831            if (attributeBroadcaster == null)
832                attributeBroadcaster = new BaseNotificationBroadcaster();
833    
834            if( log.isDebugEnabled() )
835                log.debug("addAttributeNotificationListener " + listener);
836    
837            BaseAttributeFilter filter = new BaseAttributeFilter(name);
838            attributeBroadcaster.addNotificationListener
839                (listener, filter, handback);
840    
841        }
842    
843    
844        /**
845         * Remove an attribute change notification event listener from
846         * this MBean.
847         *
848         * @param listener The listener to be removed
849         * @param name The attribute name for which no more events are required
850         *
851         *
852         * @exception ListenerNotFoundException if this listener is not
853         *  registered in the MBean
854         */
855        public void removeAttributeChangeNotificationListener
856            (NotificationListener listener, String name)
857            throws ListenerNotFoundException {
858    
859            if (listener == null)
860                throw new IllegalArgumentException("Listener is null");
861            if (attributeBroadcaster == null)
862                attributeBroadcaster = new BaseNotificationBroadcaster();
863    
864            // FIXME - currently this removes *all* notifications for this listener
865            attributeBroadcaster.removeNotificationListener(listener);
866    
867        }
868    
869    
870        /**
871         * Remove an attribute change notification event listener from
872         * this MBean.
873         *
874         * @param listener The listener to be removed
875         * @param attributeName The attribute name for which no more events are required
876         * @param handback Handback object to be sent along with event
877         *  notifications
878         *
879         *
880         * @exception ListenerNotFoundException if this listener is not
881         *  registered in the MBean
882         */
883        public void removeAttributeChangeNotificationListener
884            (NotificationListener listener, String attributeName, Object handback)
885            throws ListenerNotFoundException {
886    
887            removeAttributeChangeNotificationListener(listener, attributeName);
888    
889        }
890    
891    
892        /**
893         * Send an <code>AttributeChangeNotification</code> to all registered
894         * listeners.
895         *
896         * @param notification The <code>AttributeChangeNotification</code>
897         *  that will be passed
898         *
899         * @exception MBeanException if an object initializer throws an
900         *  exception
901         * @exception RuntimeOperationsException wraps IllegalArgumentException
902         *  when the specified notification is <code>null</code> or invalid
903         */
904        public void sendAttributeChangeNotification
905            (AttributeChangeNotification notification)
906            throws MBeanException, RuntimeOperationsException {
907    
908            if (notification == null)
909                throw new RuntimeOperationsException
910                    (new IllegalArgumentException("Notification is null"),
911                     "Notification is null");
912            if (attributeBroadcaster == null)
913                return; // This means there are no registered listeners
914            if( log.isDebugEnabled() )
915                log.debug( "AttributeChangeNotification " + notification );
916            attributeBroadcaster.sendNotification(notification);
917    
918        }
919    
920    
921        /**
922         * Send an <code>AttributeChangeNotification</code> to all registered
923         * listeners.
924         *
925         * @param oldValue The original value of the <code>Attribute</code>
926         * @param newValue The new value of the <code>Attribute</code>
927         *
928         * @exception MBeanException if an object initializer throws an
929         *  exception
930         * @exception RuntimeOperationsException wraps IllegalArgumentException
931         *  when the specified notification is <code>null</code> or invalid
932         */
933        public void sendAttributeChangeNotification
934            (Attribute oldValue, Attribute newValue)
935            throws MBeanException, RuntimeOperationsException {
936    
937            // Calculate the class name for the change notification
938            String type = null;
939            if (newValue.getValue() != null)
940                type = newValue.getValue().getClass().getName();
941            else if (oldValue.getValue() != null)
942                type = oldValue.getValue().getClass().getName();
943            else
944                return;  // Old and new are both null == no change
945    
946            AttributeChangeNotification notification =
947                new AttributeChangeNotification
948                (this, 1, System.currentTimeMillis(),
949                 "Attribute value has changed",
950                 oldValue.getName(), type,
951                 oldValue.getValue(), newValue.getValue());
952            sendAttributeChangeNotification(notification);
953    
954        }
955    
956    
957    
958    
959        /**
960         * Send a <code>Notification</code> to all registered listeners as a
961         * <code>jmx.modelmbean.general</code> notification.
962         *
963         * @param notification The <code>Notification</code> that will be passed
964         *
965         * @exception MBeanException if an object initializer throws an
966         *  exception
967         * @exception RuntimeOperationsException wraps IllegalArgumentException
968         *  when the specified notification is <code>null</code> or invalid
969         */
970        public void sendNotification(Notification notification)
971            throws MBeanException, RuntimeOperationsException {
972    
973            if (notification == null)
974                throw new RuntimeOperationsException
975                    (new IllegalArgumentException("Notification is null"),
976                     "Notification is null");
977            if (generalBroadcaster == null)
978                return; // This means there are no registered listeners
979            generalBroadcaster.sendNotification(notification);
980    
981        }
982    
983    
984        /**
985         * Send a <code>Notification</code> which contains the specified string
986         * as a <code>jmx.modelmbean.generic</code> notification.
987         *
988         * @param message The message string to be passed
989         *
990         * @exception MBeanException if an object initializer throws an
991         *  exception
992         * @exception RuntimeOperationsException wraps IllegalArgumentException
993         *  when the specified notification is <code>null</code> or invalid
994         */
995        public void sendNotification(String message)
996            throws MBeanException, RuntimeOperationsException {
997    
998            if (message == null)
999                throw new RuntimeOperationsException
1000                    (new IllegalArgumentException("Message is null"),
1001                     "Message is null");
1002            Notification notification = new Notification
1003                ("jmx.modelmbean.generic", this, 1, message);
1004            sendNotification(notification);
1005    
1006        }
1007    
1008    
1009    
1010    
1011        // ---------------------------------------- NotificationBroadcaster Methods
1012    
1013    
1014        /**
1015         * Add a notification event listener to this MBean.
1016         *
1017         * @param listener Listener that will receive event notifications
1018         * @param filter Filter object used to filter event notifications
1019         *  actually delivered, or <code>null</code> for no filtering
1020         * @param handback Handback object to be sent along with event
1021         *  notifications
1022         *
1023         * @exception IllegalArgumentException if the listener parameter is null
1024         */
1025        public void addNotificationListener(NotificationListener listener,
1026                                            NotificationFilter filter,
1027                                            Object handback)
1028            throws IllegalArgumentException {
1029    
1030            if (listener == null)
1031                throw new IllegalArgumentException("Listener is null");
1032    
1033            if( log.isDebugEnabled() ) log.debug("addNotificationListener " + listener);
1034    
1035            if (generalBroadcaster == null)
1036                generalBroadcaster = new BaseNotificationBroadcaster();
1037            generalBroadcaster.addNotificationListener
1038                (listener, filter, handback);
1039    
1040            // We'll send the attribute change notifications to all listeners ( who care )
1041            // The normal filtering can be used.
1042            // The problem is that there is no other way to add attribute change listeners
1043            // to a model mbean ( AFAIK ). I suppose the spec should be fixed.
1044            if (attributeBroadcaster == null)
1045                attributeBroadcaster = new BaseNotificationBroadcaster();
1046    
1047            if( log.isDebugEnabled() )
1048                log.debug("addAttributeNotificationListener " + listener);
1049    
1050            attributeBroadcaster.addNotificationListener
1051                    (listener, filter, handback);
1052        }
1053    
1054    
1055        /**
1056         * Return an <code>MBeanNotificationInfo</code> object describing the
1057         * notifications sent by this MBean.
1058         */
1059        public MBeanNotificationInfo[] getNotificationInfo() {
1060    
1061            // Acquire the set of application notifications
1062            MBeanNotificationInfo current[] = info.getNotifications();
1063            if (current == null)
1064                current = new MBeanNotificationInfo[0];
1065            MBeanNotificationInfo response[] =
1066                new MBeanNotificationInfo[current.length + 2];
1067            Descriptor descriptor = null;
1068    
1069            // Fill in entry for general notifications
1070            descriptor = new DescriptorSupport
1071                (new String[] { "name=GENERIC",
1072                                "descriptorType=notification",
1073                                "log=T",
1074                                "severity=5",
1075                                "displayName=jmx.modelmbean.generic" });
1076            response[0] = new ModelMBeanNotificationInfo
1077                (new String[] { "jmx.modelmbean.generic" },
1078                 "GENERIC",
1079                 "Text message notification from the managed resource",
1080                 descriptor);
1081    
1082            // Fill in entry for attribute change notifications
1083            descriptor = new DescriptorSupport
1084                (new String[] { "name=ATTRIBUTE_CHANGE",
1085                                "descriptorType=notification",
1086                                "log=T",
1087                                "severity=5",
1088                                "displayName=jmx.attribute.change" });
1089            response[1] = new ModelMBeanNotificationInfo
1090                (new String[] { "jmx.attribute.change" },
1091                 "ATTRIBUTE_CHANGE",
1092                 "Observed MBean attribute value has changed",
1093                 descriptor);
1094    
1095            // Copy remaining notifications as reported by the application
1096            System.arraycopy(current, 0, response, 2, current.length);
1097            return (response);
1098    
1099        }
1100    
1101    
1102        /**
1103         * Remove a notification event listener from this MBean.
1104         *
1105         * @param listener The listener to be removed (any and all registrations
1106         *  for this listener will be eliminated)
1107         *
1108         * @exception ListenerNotFoundException if this listener is not
1109         *  registered in the MBean
1110         */
1111        public void removeNotificationListener(NotificationListener listener)
1112            throws ListenerNotFoundException {
1113    
1114            if (listener == null)
1115                throw new IllegalArgumentException("Listener is null");
1116            if (generalBroadcaster == null)
1117                generalBroadcaster = new BaseNotificationBroadcaster();
1118            generalBroadcaster.removeNotificationListener(listener);
1119    
1120    
1121        }
1122    
1123    
1124        /**
1125         * Remove a notification event listener from this MBean.
1126         *
1127         * @param listener The listener to be removed (any and all registrations
1128         *  for this listener will be eliminated)
1129         * @param handback Handback object to be sent along with event
1130         *  notifications
1131         *
1132         * @exception ListenerNotFoundException if this listener is not
1133         *  registered in the MBean
1134         */
1135        public void removeNotificationListener(NotificationListener listener,
1136                                               Object handback)
1137            throws ListenerNotFoundException {
1138    
1139            removeNotificationListener(listener);
1140    
1141        }
1142    
1143    
1144        /**
1145         * Remove a notification event listener from this MBean.
1146         *
1147         * @param listener The listener to be removed (any and all registrations
1148         *  for this listener will be eliminated)
1149         * @param filter Filter object used to filter event notifications
1150         *  actually delivered, or <code>null</code> for no filtering
1151         * @param handback Handback object to be sent along with event
1152         *  notifications
1153         *
1154         * @exception ListenerNotFoundException if this listener is not
1155         *  registered in the MBean
1156         */
1157        public void removeNotificationListener(NotificationListener listener,
1158                                               NotificationFilter filter,
1159                                               Object handback)
1160            throws ListenerNotFoundException {
1161    
1162            removeNotificationListener(listener);
1163    
1164        }
1165    
1166    
1167        // ------------------------------------------------ PersistentMBean Methods
1168    
1169    
1170        /**
1171         * Instantiates this MBean instance from data found in the persistent
1172         * store.  The data loaded could include attribute and operation values.
1173         * This method should be called during construction or initialization
1174         * of the instance, and before the MBean is registered with the
1175         * <code>MBeanServer</code>.
1176         *
1177         * <p><strong>IMPLEMENTATION NOTE</strong> - This implementation does
1178         * not support persistence.</p>
1179         *
1180         * @exception InstanceNotFoundException if the managed resource object
1181         *  cannot be found
1182         * @exception MBeanException if the initializer of the object throws
1183         *  an exception
1184         * @exception RuntimeOperationsException if an exception is reported
1185         *  by the persistence mechanism
1186         */
1187        public void load() throws InstanceNotFoundException,
1188            MBeanException, RuntimeOperationsException {
1189            // XXX If a context was set, use it to load the data
1190            throw new MBeanException
1191                (new IllegalStateException("Persistence is not supported"),
1192                 "Persistence is not supported");
1193    
1194        }
1195    
1196    
1197        /**
1198         * Capture the current state of this MBean instance and write it out
1199         * to the persistent store.  The state stored could include attribute
1200         * and operation values.  If one of these methods of persistence is not
1201         * supported, a "service not found" exception will be thrown.
1202         *
1203         * <p><strong>IMPLEMENTATION NOTE</strong> - This implementation does
1204         * not support persistence.</p>
1205         *
1206         * @exception InstanceNotFoundException if the managed resource object
1207         *  cannot be found
1208         * @exception MBeanException if the initializer of the object throws
1209         *  an exception, or persistence is not supported
1210         * @exception RuntimeOperationsException if an exception is reported
1211         *  by the persistence mechanism
1212         */
1213        public void store() throws InstanceNotFoundException,
1214            MBeanException, RuntimeOperationsException {
1215    
1216            // XXX if a context was set, use it to store the data
1217            throw new MBeanException
1218                (new IllegalStateException("Persistence is not supported"),
1219                 "Persistence is not supported");
1220    
1221        }
1222    
1223        // --------------------  BaseModelMBean methods --------------------
1224    
1225        /** Set the type of the mbean. This is used as a key to locate
1226         * the description in the Registry.
1227         *
1228         * @param type the type of classname of the modeled object
1229         */
1230        public void setModeledType( String type ) {
1231            initModelInfo(type);
1232            createResource();
1233        }
1234        /** Set the type of the mbean. This is used as a key to locate
1235         * the description in the Registry.
1236         *
1237         * @param type the type of classname of the modeled object
1238         */
1239        protected void initModelInfo( String type ) {
1240            try {
1241                if( log.isDebugEnabled())
1242                    log.debug("setModeledType " + type);
1243    
1244                log.debug( "Set model Info " + type);
1245                if(type==null) {
1246                    return;
1247                }
1248                resourceType=type;
1249                //Thread.currentThread().setContextClassLoader(BaseModelMBean.class.getClassLoader());
1250                Class c=null;
1251                try {
1252                    c=Class.forName( type);
1253                } catch( Throwable t ) {
1254                    log.debug( "Error creating class " + t);
1255                }
1256    
1257                // The class c doesn't need to exist
1258                ManagedBean descriptor=getRegistry().findManagedBean(c, type);
1259                if( descriptor==null ) 
1260                    return;
1261                this.setModelMBeanInfo(descriptor.createMBeanInfo());
1262            } catch( Throwable ex) {
1263                log.error( "TCL: " + Thread.currentThread().getContextClassLoader(),
1264                        ex);
1265            }
1266        }
1267    
1268        /** Set the type of the mbean. This is used as a key to locate
1269         * the description in the Registry.
1270         */
1271        protected void createResource() {
1272            try {
1273                //Thread.currentThread().setContextClassLoader(BaseModelMBean.class.getClassLoader());
1274                Class c=null;
1275                try {
1276                    c=Class.forName( resourceType );
1277                    resource = c.newInstance();
1278                } catch( Throwable t ) {
1279                    log.error( "Error creating class " + t);
1280                }
1281            } catch( Throwable ex) {
1282                log.error( "TCL: " + Thread.currentThread().getContextClassLoader(),
1283                        ex);
1284            }
1285        }
1286    
1287    
1288        public String getModelerType() {
1289            return resourceType;
1290        }
1291    
1292        public String getClassName() {
1293            return getModelerType();
1294        }
1295    
1296        public ObjectName getJmxName() {
1297            return oname;
1298        }
1299    
1300        public String getObjectName() {
1301            if (oname != null) {
1302                return oname.toString();
1303            } else {
1304                return null;
1305            }
1306        }
1307    
1308        public void setRegistry(Registry registry) {
1309            this.registry = registry;
1310        }
1311    
1312        public Registry getRegistry() {
1313            // XXX Need a better solution - to avoid the static
1314            if( registry == null )
1315                registry=Registry.getRegistry();
1316    
1317            return registry;
1318        }
1319    
1320        // ------------------------------------------------------ Protected Methods
1321    
1322    
1323        /**
1324         * Create and return a default <code>ModelMBeanInfo</code> object.
1325         */
1326        protected ModelMBeanInfo createDefaultModelMBeanInfo() {
1327    
1328            return (new ModelMBeanInfoSupport(this.getClass().getName(),
1329                                              "Default ModelMBean",
1330                                              null, null, null, null));
1331    
1332        }
1333    
1334        /**
1335         * Is the specified <code>ModelMBeanInfo</code> instance valid?
1336         *
1337         * <p><strong>IMPLEMENTATION NOTE</strong> - This implementation
1338         * does not check anything, but this method can be overridden
1339         * as required.</p>
1340         *
1341         * @param info The <code>ModelMBeanInfo object to check
1342         */
1343        protected boolean isModelMBeanInfoValid(ModelMBeanInfo info) {
1344            return (true);
1345        }
1346    
1347        // -------------------- Registration  --------------------
1348        // XXX We can add some method patterns here- like setName() and
1349        // setDomain() for code that doesn't implement the Registration
1350    
1351        public ObjectName preRegister(MBeanServer server,
1352                                      ObjectName name)
1353                throws Exception
1354        {
1355            if( log.isDebugEnabled())
1356                log.debug("preRegister " + resource + " " + name );
1357            oname=name;
1358            if( resource instanceof MBeanRegistration ) {
1359                oname = ((MBeanRegistration)resource).preRegister(server, name );
1360            }
1361            return oname;
1362        }
1363    
1364        public void postRegister(Boolean registrationDone) {
1365            if( resource instanceof MBeanRegistration ) {
1366                ((MBeanRegistration)resource).postRegister(registrationDone);
1367            }
1368        }
1369    
1370        public void preDeregister() throws Exception {
1371            if( resource instanceof MBeanRegistration ) {
1372                ((MBeanRegistration)resource).preDeregister();
1373            }
1374        }
1375    
1376        public void postDeregister() {
1377            if( resource instanceof MBeanRegistration ) {
1378                ((MBeanRegistration)resource).postDeregister();
1379            }
1380        }
1381    
1382        static class MethodKey {
1383            private String name;
1384            private String[] signature;
1385    
1386            MethodKey(String name, String[] signature) {
1387                this.name = name;
1388                if(signature == null) {
1389                    signature = new String[0];
1390                }
1391                this.signature = signature;
1392            }
1393    
1394            public boolean equals(Object other) {
1395                if(!(other instanceof MethodKey)) {
1396                    return false;
1397                }
1398                MethodKey omk = (MethodKey)other;
1399                if(!name.equals(omk.name)) {
1400                    return false;
1401                }
1402                if(signature.length != omk.signature.length) {
1403                    return false;
1404                }
1405                for(int i=0; i < signature.length; i++) {
1406                    if(!signature[i].equals(omk.signature[i])) {
1407                        return false;
1408                    }
1409                }
1410                return true;
1411            }
1412    
1413            public int hashCode() {
1414                return name.hashCode();
1415            }
1416        }
1417    }