/*
* ====================================================================
*
* The Apache Software License, Version 1.1
*
* Copyright (c) 1999 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowlegement:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowlegement may appear in the software itself,
* if and wherever such third-party acknowlegements normally appear.
*
* 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
* Foundation" must not be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names without prior written
* permission of the Apache Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
* [Additional notices, if required by prior licensing conditions]
*
*/
package org.apache.commons.modeler.mbeans;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import javax.management.Attribute;
import javax.management.AttributeNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.modeler.Registry;
/**
* Based on jk2 proxy.
*
* Proxy using a very simple HTTP based protocol.
*
* For efficiency, it'll get bulk results and cache them - you
* can force an update by calling the refreshAttributes and refreshMetadata
* operations on this mbean.
*
* TODO: implement the user/pass auth ( right now you must use IP based security )
* TODO: eventually support https
* TODO: support for metadata ( mbean-descriptors ) for description and type conversions
* TODO: filter out trivial components ( mutexes, etc )
*
* @author Costin Manolache
*/
public class SimpleRemoteConnector
{
private static Log log = LogFactory.getLog(SimpleRemoteConnector.class);
// HTTP port of the remote JMX
String webServerHost="localhost";
int webServerPort=8080;
// URL of the remote JMX servlet ( or equivalent )
String statusPath="/jkstatus";
// Not used right now
String user;
String pass;
// Domain we mirror
String domain;
// XXX Not used - allow translations
String localDomain;
String filter;
//
long lastRefresh=0;
long updateInterval=5000; // 5 sec - it's min time between updates
String prefix="";
Registry reg;
MBeanServer mserver;
// Last view
HashMap mbeans=new HashMap();
public SimpleRemoteConnector()
{
}
/* -------------------- Public methods -------------------- */
public String getWebServerHost() {
return webServerHost;
}
public void setWebServerHost(String webServerHost) {
this.webServerHost = webServerHost;
}
public int getWebServerPort() {
return webServerPort;
}
public void setWebServerPort(int webServerPort) {
this.webServerPort = webServerPort;
}
public long getUpdateInterval() {
return updateInterval;
}
public void setUpdateInterval(long updateInterval) {
this.updateInterval = updateInterval;
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getPass() {
return pass;
}
public String getDomain() {
return domain;
}
public void setDomain(String domain) {
this.domain = domain;
}
public void setPass(String pass) {
this.pass = pass;
}
public String getStatusPath() {
return statusPath;
}
public void setStatusPath(String statusPath) {
this.statusPath = statusPath;
}
public String getFilter() {
return filter;
}
public void setFilter(String filter) {
this.filter = filter;
}
/* ==================== Start/stop ==================== */
public void destroy() {
try {
// We should keep track of loaded beans and call stop.
// Modeler should do it...
Iterator mbeansIt=mbeans.values().iterator();
while( mbeansIt.hasNext()) {
MBeanProxy proxy=(MBeanProxy)mbeansIt.next();
ObjectName oname=proxy.getObjectName();
Registry.getRegistry().getMBeanServer().unregisterMBean(oname);
}
} catch( Throwable t ) {
log.error( "Destroy error", t );
}
}
public void init() throws IOException {
try {
//if( log.isDebugEnabled() )
log.info("init " + webServerHost + " " + webServerPort);
reg=Registry.getRegistry();
// Get metadata for all mbeans on the remote side
//refreshMetadata();
// Get current values and mbeans
refreshAttributes();
} catch( Throwable t ) {
log.error( "Init error", t );
}
}
public void start() throws IOException {
System.out.println("XXX start");
if( reg==null)
init();
}
/** Refresh the proxies, if updateInterval passed
*
*/
public void refresh() {
long time=System.currentTimeMillis();
if( time - lastRefresh < updateInterval ) {
return;
}
System.out.println("refresh... ");
lastRefresh=time;
//refreshMetadata();
refreshAttributes();
}
public void refreshAttributes() {
try {
int cnt=0;
// connect to apache, get a list of mbeans
if( filter==null ) {
filter=domain + ":*";
}
InputStream is=getStream( "qry=" + filter);
if( is==null ) return;
Manifest mf=new Manifest(is);
HashMap currentObjects=new HashMap(); // used to remove older ones
Map entries=mf.getEntries();
Iterator it=entries.keySet().iterator();
while( it.hasNext() ) {
String name=(String)it.next();
Attributes attrs=(Attributes)entries.get( name );
ObjectName oname=new ObjectName(name);
currentObjects.put( oname, "");
MBeanProxy proxy=(MBeanProxy)mbeans.get(oname);
if( proxy==null ) {
log.debug( "New object " + name);
String code=attrs.getValue("modelerType");
if(log.isDebugEnabled())
log.debug("Register " + name + " " + code );
proxy= new MBeanProxy(this, code);
mbeans.put( oname, proxy );
// Register
MBeanServer mserver=Registry.getRegistry().getMBeanServer();
if( ! mserver.isRegistered(oname ) ) {
mserver.registerMBean(proxy, oname);
}
}
Iterator it2=attrs.keySet().iterator();
while( it2.hasNext() ) {
Object o=it2.next();
String att=(o==null) ? null : o.toString();
if( "modelerType".equals( att )) continue;
String val=attrs.getValue(att);
proxy.update(att, val);
cnt++;
}
}
// Now we have to remove all the mbeans that are no longer there
Iterator existingIt=mbeans.keySet().iterator();
while( existingIt.hasNext() ) {
ObjectName on=(ObjectName)existingIt.next();
if(currentObjects.get( on ) != null )
continue; // still alive
if( log.isDebugEnabled() )
log.debug("No longer alive " + on);
try {
mserver.unregisterMBean(on);
} catch( Throwable t ) {
log.info("Error unregistering " + on + " " + t.toString());
}
}
log.info( "Refreshing attributes " + cnt);
} catch( Exception ex ) {
log.info("Error ", ex);
}
}
// Not used right now - assume the metadata is available locally
// Could use mbeans-descriptors.xml or other formats.
// Will be called if code= is not found locally
public void refreshMetadata() {
try {
int cnt=0;
int newCnt=0;
InputStream is=getStream("getMetadata=" + domain + ":*");
if( is==null ) return;
log.info( "Refreshing metadata " + cnt + " " + newCnt);
} catch( Exception ex ) {
log.info("Error ", ex);
}
}
public Object invoke(Object oname, String name, Object params[], String signature[])
throws MBeanException, ReflectionException {
try {
// we support only string values
InputStream is=this.getStream("invoke=" + name + "&name=" + oname.toString() );
if( is==null ) return null;
// String res=is.readLine();
// if( log.isDebugEnabled())
// log.debug( "Invoking " + jkName + " " + name + " result " + res);
//this.refreshMetadata();
this.refreshAttributes();
} catch( Exception ex ) {
throw new MBeanException(ex);
}
return null;
}
public void setAttribute(ObjectName oname, Attribute attribute)
throws AttributeNotFoundException, MBeanException,
ReflectionException
{
try {
// we support only string values
String val=(String)attribute.getValue();
String name=attribute.getName();
InputStream is=this.getStream("set=" + name + "&name=" + oname.toString()
+ "&value=" + val);
if( is==null ) return;
// String res=is.readLine();
// if( log.isDebugEnabled())
// log.debug( "Setting " + jkName + " " + name + " result " + res);
//this.refreshMetadata();
this.refreshAttributes();
} catch( Exception ex ) {
throw new MBeanException(ex);
}
}
/** connect to apache using http, get a list of mbeans. Can be
* overriten to support different protocols ( jk/Unix domain sockets, etc )
*/
protected InputStream getStream(String qry) throws Exception {
try {
String path=statusPath + "?" + qry;
URL url=new URL( "http", webServerHost, webServerPort, path);
log.debug( "Connecting to " + url);
URLConnection urlc=url.openConnection();
InputStream is=urlc.getInputStream();
return is;
} catch (IOException e) {
log.info( "Can't connect to jkstatus " + webServerHost + ":" + webServerPort
+ " " + e.toString());
return null;
}
}
}