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.util.Enumeration;
023    import java.util.Hashtable;
024    
025    import javax.management.AttributeChangeNotification;
026    import javax.management.InstanceNotFoundException;
027    import javax.management.MBeanException;
028    import javax.management.MBeanServer;
029    import javax.management.MBeanServerNotification;
030    import javax.management.Notification;
031    import javax.management.NotificationBroadcaster;
032    import javax.management.NotificationListener;
033    import javax.management.ObjectName;
034    import javax.naming.Context;
035    
036    import org.apache.commons.logging.Log;
037    import org.apache.commons.logging.LogFactory;
038    
039    // EXPERIMENTAL. It may fit better in tomcat jndi impl.
040    
041    
042    /**
043     *
044     * Link between JNDI and JMX. JNDI can be used for persistence ( it is
045     * an API for storing hierarchical data and a perfect fit for that ), as
046     * well as an alternate view of the MBean registry.
047     *
048     * If this component is enabled, all MBeans will be registered in JNDI, and
049     * all attributes that are set via JMX can be stored in a DirContext.
050     *
051     * This acts as a "recorder" for creation of mbeans and attribute changes
052     * done via JMX.
053     *
054     * XXX How can we control ( filter ) which mbeans will be registere ? Or
055     * attributes ?
056     * XXX How can we get the beans and attributes loaded before jndijmx ?
057     *
058     * The intended use:
059     * - do whatever you want to start the application
060     * - load JndiJmx as an mbean
061     * - make changes via JMX. All changes are recorded
062     * - you can use JndiJmx to save the changes in a Jndi context.
063     * - you can use JndiJmx to load changes from a JndiContext and replay them.
064     *
065     * The main benefit is that only changed attributes are saved, and the Jndi
066     * layer can preserve most of the original structure of the config file. The
067     * alternative is to override the config files with config info extracted
068     * from the live objects - but it's very hard to save only what was actually
069     * changed and preserve structure and comments.
070     *
071     * @author Costin Manolache
072     */
073    public class JndiJmx extends BaseModelMBean implements NotificationListener {
074    
075    
076        private static Log log= LogFactory.getLog(JndiJmx.class);
077    
078        protected Context componentContext;
079        protected Context descriptorContext;
080        protected Context configContext;
081    
082        MBeanServer mserver;
083    
084        /**
085         * Protected constructor to require use of the factory create method.
086         */
087        public JndiJmx() throws MBeanException {
088            super(JndiJmx.class.getName());
089        }
090    
091    
092        /** If a JNDI context is set, all components
093         * will be registered in the context.
094         *
095         * @param ctx
096         */
097        public void setComponentContext(Context ctx) {
098            this.componentContext= ctx;
099        }
100    
101        /** JNDI context for component descriptors ( metadata ).
102         *
103         * @param ctx
104         */
105        public void setDescriptorContext(Context ctx) {
106            this.descriptorContext= ctx;
107        }
108    
109        /** JNDI context where attributes will be stored for persistence
110         *
111         */
112        public void setConfigContext( Context ctx ) {
113            this.configContext= ctx;
114        }
115    
116        // --------------------  Registration/unregistration --------------------
117        // temp - will only set in the jndi contexts
118        Hashtable attributes=new Hashtable();
119        Hashtable instances=new Hashtable();
120    
121        public void handleNotification(Notification notification, Object handback)
122        {
123            // register/unregister mbeans in jndi
124            if( notification instanceof MBeanServerNotification ) {
125                MBeanServerNotification msnot=(MBeanServerNotification)notification;
126    
127                ObjectName oname=msnot.getMBeanName();
128    
129                if( "jmx.mbean.created".equalsIgnoreCase( notification.getType() )) {
130                    try {
131                        Object mbean=mserver.getObjectInstance(oname);
132    
133                        if( log.isDebugEnabled() )
134                            log.debug( "MBean created " + oname + " " + mbean);
135    
136                        // XXX add filter support
137                        if( mbean instanceof NotificationBroadcaster ) {
138                            // register for attribute changes
139                            NotificationBroadcaster nb=(NotificationBroadcaster)mbean;
140                            nb.addNotificationListener(this, null, null);
141                            if( log.isDebugEnabled() )
142                                log.debug( "Add attribute change listener");
143                        }
144    
145                        instances.put( oname.toString(), mbean );
146                    } catch( InstanceNotFoundException ex ) {
147                        log.error( "Instance not found for the created object", ex );
148                    }
149                }
150                if( "jmx.mbean.deleted".equalsIgnoreCase( notification.getType() )) {
151                    instances.remove(oname.toString());
152                }
153            }
154    
155            // set attributes in jndi
156           //     if( "jmx.attribute.changed".equals( notification.getType() )) {
157            if( notification instanceof AttributeChangeNotification) {
158    
159                AttributeChangeNotification anotif=(AttributeChangeNotification)notification;
160                String name=anotif.getAttributeName();
161                Object value=anotif.getNewValue();
162                Object source=anotif.getSource();
163                String mname=null;
164    
165                Hashtable mbeanAtt=(Hashtable)attributes.get( source );
166                if( mbeanAtt==null ) {
167                    mbeanAtt=new Hashtable();
168                    attributes.put( source, mbeanAtt);
169                    if( log.isDebugEnabled())
170                        log.debug("First attribute for " + source );
171                }
172                mbeanAtt.put( name, anotif );
173    
174                log.debug( "Attribute change notification " + name + " " + value + " " + source );
175    
176            }
177    
178        }
179    
180        public String dumpStatus() throws Exception
181        {
182            StringBuffer sb=new StringBuffer();
183            Enumeration en=instances.keys();
184            while (en.hasMoreElements()) {
185                String on = (String) en.nextElement();
186                Object mbean=instances.get(on);
187                Hashtable mbeanAtt=(Hashtable)attributes.get(mbean);
188    
189                sb.append( "<mbean class=\"").append(on).append("\">");
190                sb.append( "\n");
191                Enumeration attEn=mbeanAtt.keys();
192                while (attEn.hasMoreElements()) {
193                    String an = (String) attEn.nextElement();
194                    AttributeChangeNotification anotif=
195                            (AttributeChangeNotification)mbeanAtt.get(an);
196                    sb.append("  <attribute name=\"").append(an).append("\" ");
197                    sb.append("value=\"").append(anotif.getNewValue()).append("\">");
198                    sb.append( "\n");
199                }
200    
201    
202                sb.append( "</mbean>");
203                sb.append( "\n");
204            }
205            return sb.toString();
206        }
207    
208        public void replay() throws Exception
209        {
210    
211    
212        }
213    
214    
215        public void init() throws Exception
216        {
217    
218            MBeanServer mserver=(MBeanServer)Registry.getRegistry().getMBeanServer();
219            ObjectName delegate=new ObjectName("JMImplementation:type=MBeanServerDelegate");
220    
221            // XXX need to extract info about previously loaded beans
222    
223            // we'll know of all registered beans
224            mserver.addNotificationListener(delegate, this, null, null );
225    
226        }
227    
228    }