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