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    package org.apache.commons.modeler.mbeans;
018    
019    import java.io.IOException;
020    import java.io.InputStream;
021    import java.net.URL;
022    import java.net.URLConnection;
023    import java.util.HashMap;
024    import java.util.Iterator;
025    import java.util.Map;
026    import java.util.jar.Attributes;
027    import java.util.jar.Manifest;
028    
029    import javax.management.Attribute;
030    import javax.management.AttributeNotFoundException;
031    import javax.management.MBeanException;
032    import javax.management.MBeanServer;
033    import javax.management.ObjectName;
034    import javax.management.ReflectionException;
035    
036    import org.apache.commons.logging.Log;
037    import org.apache.commons.logging.LogFactory;
038    import org.apache.commons.modeler.Registry;
039    
040    /**
041     * Based on jk2 proxy.
042     *
043     * Proxy using a very simple HTTP based protocol.
044     *
045     * For efficiency, it'll get bulk results and cache them - you
046     * can force an update by calling the refreshAttributes and refreshMetadata
047     * operations on this mbean.
048     *
049     * TODO: implement the user/pass auth ( right now you must use IP based security )
050     * TODO: eventually support https
051     * TODO: support for metadata ( mbean-descriptors ) for description and type conversions
052     * TODO: filter out trivial components ( mutexes, etc )
053     *
054     * @author Costin Manolache
055     */
056    public class SimpleRemoteConnector
057    {
058        private static Log log = LogFactory.getLog(SimpleRemoteConnector.class);
059    
060        // HTTP port of the remote JMX
061        String webServerHost="localhost";
062        int webServerPort=8080;
063    
064        // URL of the remote JMX servlet ( or equivalent )
065        String statusPath="/jkstatus";
066    
067        // Not used right now
068        String user;
069        String pass;
070    
071        // Domain we mirror
072        String domain;
073    
074        // XXX Not used - allow translations
075        String localDomain;
076        String filter;
077    
078        //
079        long lastRefresh=0;
080        long updateInterval=5000; // 5 sec - it's min time between updates
081    
082        String prefix="";
083    
084        Registry reg;
085    
086        MBeanServer mserver;
087    
088        // Last view
089        HashMap mbeans=new HashMap();
090    
091        public SimpleRemoteConnector()
092        {
093        }
094    
095        /* -------------------- Public methods -------------------- */
096    
097        public String getWebServerHost() {
098            return webServerHost;
099        }
100    
101        public void setWebServerHost(String webServerHost) {
102            this.webServerHost = webServerHost;
103        }
104    
105        public int getWebServerPort() {
106            return webServerPort;
107        }
108    
109        public void setWebServerPort(int webServerPort) {
110            this.webServerPort = webServerPort;
111        }
112    
113        public long getUpdateInterval() {
114            return updateInterval;
115        }
116    
117        public void setUpdateInterval(long updateInterval) {
118            this.updateInterval = updateInterval;
119        }
120    
121        public String getUser() {
122            return user;
123        }
124    
125        public void setUser(String user) {
126            this.user = user;
127        }
128    
129        public String getPass() {
130            return pass;
131        }
132    
133        public String getDomain() {
134            return domain;
135        }
136    
137        public void setDomain(String domain) {
138            this.domain = domain;
139        }
140    
141        public void setPass(String pass) {
142            this.pass = pass;
143        }
144    
145        public String getStatusPath() {
146            return statusPath;
147        }
148    
149        public void setStatusPath(String statusPath) {
150            this.statusPath = statusPath;
151        }
152    
153        public String getFilter() {
154            return filter;
155        }
156    
157        public void setFilter(String filter) {
158            this.filter = filter;
159        }
160    
161        /* ==================== Start/stop ==================== */
162    
163        public void destroy() {
164            try {
165                // We should keep track of loaded beans and call stop.
166                // Modeler should do it...
167                Iterator mbeansIt=mbeans.values().iterator();
168                while( mbeansIt.hasNext()) {
169                    MBeanProxy proxy=(MBeanProxy)mbeansIt.next();
170                    ObjectName oname=proxy.getJmxName();
171                    Registry.getRegistry().getMBeanServer().unregisterMBean(oname);
172                }
173            } catch( Throwable t ) {
174                log.error( "Destroy error", t );
175            }
176        }
177    
178        public void init() throws IOException {
179            try {
180                //if( log.isDebugEnabled() )
181                log.info("init " + webServerHost + " " + webServerPort);
182                reg=Registry.getRegistry();
183                // Get metadata for all mbeans on the remote side
184                //refreshMetadata();
185                // Get current values and mbeans
186                refreshAttributes();
187            } catch( Throwable t ) {
188                log.error( "Init error", t );
189            }
190        }
191    
192        public void start() throws IOException {
193            System.out.println("XXX start");
194            if( reg==null)
195                init();
196        }
197    
198        /** Refresh the proxies, if updateInterval passed
199         *
200         */
201        public void refresh()  {
202            long time=System.currentTimeMillis();
203            if( time - lastRefresh < updateInterval ) {
204                return;
205            }
206            System.out.println("refresh... ");
207            lastRefresh=time;
208            //refreshMetadata();
209            refreshAttributes();
210        }
211    
212        public void refreshAttributes()  {
213            try {
214                int cnt=0;
215                // connect to apache, get a list of mbeans
216                if( filter==null ) {
217                    filter=domain + ":*";
218                }
219    
220                InputStream is=getStream( "qry=" + filter);
221                if( is==null ) return;
222    
223                Manifest mf=new Manifest(is);
224                
225                HashMap currentObjects=new HashMap(); // used to remove older ones
226                Map entries=mf.getEntries();
227                Iterator it=entries.keySet().iterator();
228                while( it.hasNext() ) {
229                    String name=(String)it.next();
230                    Attributes attrs=(Attributes)entries.get( name );
231    
232                    ObjectName oname=new ObjectName(name);
233                    currentObjects.put( oname, "");
234                    MBeanProxy proxy=(MBeanProxy)mbeans.get(oname);
235                    if( proxy==null ) {
236                        log.debug( "New object " + name);
237                        String code=attrs.getValue("modelerType");
238                        if(log.isDebugEnabled())
239                            log.debug("Register " + name  + " " + code );
240    
241                        proxy= new MBeanProxy(this, code);
242                        mbeans.put( oname, proxy );
243    
244                        // Register
245                        MBeanServer mserver=Registry.getRegistry().getMBeanServer();
246                        if( ! mserver.isRegistered(oname ) ) {
247                            mserver.registerMBean(proxy, oname);
248                        }
249                    }
250                    Iterator it2=attrs.keySet().iterator();
251                    while( it2.hasNext() ) {
252                        Object o=it2.next();
253                        String att=(o==null) ? null : o.toString();
254                        if( "modelerType".equals( att )) continue;
255                        String val=attrs.getValue(att);
256                        proxy.update(att, val);
257                        cnt++;
258                    }
259                }
260                
261                // Now we have to remove all the mbeans that are no longer there
262                Iterator existingIt=mbeans.keySet().iterator();
263                while( existingIt.hasNext() ) {
264                    ObjectName on=(ObjectName)existingIt.next();
265                    if(currentObjects.get( on ) != null )
266                        continue; // still alive
267                    if( log.isDebugEnabled() )
268                        log.debug("No longer alive " + on);
269                    try {
270                        mserver.unregisterMBean(on);
271                    } catch( Throwable t ) {
272                        log.info("Error unregistering " + on + " " + t.toString());
273                    }
274                }
275                
276                log.info( "Refreshing attributes " + cnt);
277            } catch( Exception ex ) {
278                log.info("Error ", ex);
279            }
280        }
281    
282        // Not used right now - assume the metadata is available locally
283        // Could use mbeans-descriptors.xml or other formats.
284        // Will be called if code= is not found locally
285        public void refreshMetadata() {
286            try {
287                int cnt=0;
288                int newCnt=0;
289                InputStream is=getStream("getMetadata=" + domain + ":*");
290                if( is==null ) return;
291    
292                log.info( "Refreshing metadata " + cnt + " " +  newCnt);
293            } catch( Exception ex ) {
294                log.info("Error ", ex);
295            }
296        }
297    
298        public Object invoke(Object oname, String name, Object params[], String signature[])
299            throws MBeanException, ReflectionException {
300            try {
301                // we support only string values
302                InputStream is=this.getStream("invoke=" + name + "&name=" + oname.toString() );
303                if( is==null ) return null;
304    //                String res=is.readLine();
305    //                if( log.isDebugEnabled())
306    //                    log.debug( "Invoking " + jkName + " " + name + " result " + res);
307    
308                //this.refreshMetadata();
309                this.refreshAttributes();
310            } catch( Exception ex ) {
311                throw new MBeanException(ex);
312            }
313            return null;
314        }
315    
316    
317        public void setAttribute(ObjectName oname, Attribute attribute)
318            throws AttributeNotFoundException, MBeanException,
319            ReflectionException
320        {
321            try {
322                // we support only string values
323                String val=(String)attribute.getValue();
324                String name=attribute.getName();
325                InputStream is=this.getStream("set=" + name + "&name=" + oname.toString()
326                        + "&value=" + val);
327                if( is==null ) return;
328    //                String res=is.readLine();
329    //                if( log.isDebugEnabled())
330    //                    log.debug( "Setting " + jkName + " " + name + " result " + res);
331    
332                //this.refreshMetadata();
333                this.refreshAttributes();
334            } catch( Exception ex ) {
335                throw new MBeanException(ex);
336            }
337        }
338    
339        /** connect to apache using http, get a list of mbeans. Can be
340         * overriten to support different protocols ( jk/Unix domain sockets, etc )
341          */
342        protected InputStream getStream(String qry) throws Exception {
343            try {
344                String path=statusPath + "?" + qry;
345                URL url=new URL( "http", webServerHost, webServerPort, path);
346                log.debug( "Connecting to " + url);
347                URLConnection urlc=url.openConnection();
348                InputStream is=urlc.getInputStream();
349                return is;
350            } catch (IOException e) {
351                log.info( "Can't connect to jkstatus " + webServerHost + ":" + webServerPort
352                + " " + e.toString());
353                return null;
354            }
355        }
356    
357    
358    }
359