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