mx4j/src/tools/mx4j/tools/remote/http HTTPClientInvoker.java,NONE,1.1 HTTPConnection.java,NONE,1.1 HTTPConnectionHandler.java,NONE,1.1 HTTPConnectionMBeanServerConnection.java,NONE,1.1 HTTPConnectionManager.java,NONE,1.1 HTTPConnector.java,NONE,1.1 HTTPConnectorServer.java,NONE,1.1 HTTPHeartBeat.java,NONE,1.1 HTTPRemoteNotificationClientHandler.java,NONE,1.1 HTTPResolver.java,NONE,1.1 HTTPServerInvoker.java,NONE,1.1 HTTPService.java,NONE,1.1 HTTPSubjectInvoker.java,NONE,1.1 WebContainer.java,NONE,1.1

Simone Bordet <[email protected]>
Newsgroups gmane.comp.java.mx4j.cvs
Message-ID <[email protected]>
Update of /cvsroot/mx4j/mx4j/src/tools/mx4j/tools/remote/http
In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv25164/src/tools/mx4j/tools/remote/http

Added Files:
	HTTPClientInvoker.java HTTPConnection.java 
	HTTPConnectionHandler.java 
	HTTPConnectionMBeanServerConnection.java 
	HTTPConnectionManager.java HTTPConnector.java 
	HTTPConnectorServer.java HTTPHeartBeat.java 
	HTTPRemoteNotificationClientHandler.java HTTPResolver.java 
	HTTPServerInvoker.java HTTPService.java 
	HTTPSubjectInvoker.java WebContainer.java 
Log Message:
Refactoring of JSR 160 JMXConnector and JMXConnectorServer for protocols that runs over HTTP: SOAP, Hessian and Burlap

--- NEW FILE: HTTPServerInvoker.java ---
/*
 * Copyright (C) MX4J.
 * All rights reserved.
 *
 * This software is distributed under the terms of the MX4J License version 1.0.
 * See the terms of the MX4J License in the documentation provided with this software.
 */

package mx4j.tools.remote.http;

import java.io.IOException;
import javax.management.InstanceNotFoundException;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanServerConnection;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import javax.management.remote.NotificationResult;
import javax.security.auth.Subject;

import mx4j.remote.NotificationTuple;
import mx4j.remote.RemoteNotificationServerHandler;
import mx4j.tools.remote.AbstractServerInvoker;

/**
 * Implementation of the HTTPConnector interface that forwards the calls
 * to an MBeanServerConnection object.
 * It handles remote notifications, but it does not handle unmarshalling of
 * arguments (and all related classloading problems).
 *
 * @author <a href="mailto:[email protected]">Simone Bordet</a>
 * @version $Revision: 1.1 $
 */
public class HTTPServerInvoker extends AbstractServerInvoker implements HTTPConnection
{
   private final RemoteNotificationServerHandler notificationHandler;

   public HTTPServerInvoker(MBeanServerConnection server, RemoteNotificationServerHandler handler)
   {
      super(server);
      this.notificationHandler = handler;
   }

   public String connect(Object credentials) throws IOException, SecurityException
   {
      return null;
   }

   public void close() throws IOException
   {
      NotificationTuple[] tuples = notificationHandler.close();
      for (int i = 0; i < tuples.length; ++i)
      {
         NotificationTuple tuple = tuples[i];
         try
         {
            getServer().removeNotificationListener(tuple.getObjectName(), tuple.getNotificationListener(), tuple.getNotificationFilter(), tuple.getHandback());
         }
         catch (InstanceNotFoundException ignored)
         {
         }
         catch (ListenerNotFoundException ignored)
         {
         }
      }
   }

   public Integer addNotificationListener(ObjectName name, Object filter, Subject delegate) throws InstanceNotFoundException, IOException
   {
      Integer id = notificationHandler.generateListenerID(name, null);
      NotificationListener listener = notificationHandler.getServerNotificationListener();
      getServer().addNotificationListener(name, listener, null, id);
      notificationHandler.addNotificationListener(id, new NotificationTuple(name, listener, null, id));
      return id;
   }

   public void removeNotificationListeners(ObjectName name, Integer[] listenerIDs, Subject delegate) throws InstanceNotFoundException, ListenerNotFoundException, IOException
   {
      for (int i = 0; i < listenerIDs.length; ++i)
      {
         Integer id = listenerIDs[i];
         NotificationTuple tuple = notificationHandler.removeNotificationListener(id);
         getServer().removeNotificationListener(name, tuple.getNotificationListener(), tuple.getNotificationFilter(), tuple.getHandback());
      }
   }

   public NotificationResult fetchNotifications(long clientSequenceNumber, int maxNotifications, long timeout) throws IOException
   {
      return notificationHandler.fetchNotifications(clientSequenceNumber, maxNotifications, timeout);
   }
}

--- NEW FILE: HTTPConnector.java ---
/*
 * Copyright (C) MX4J.
 * All rights reserved.
 *
 * This software is distributed under the terms of the MX4J License version 1.0.
 * See the terms of the MX4J License in the documentation provided with this software.
 */

package mx4j.tools.remote.http;

import java.io.IOException;
import java.net.MalformedURLException;
import java.util.Map;
import javax.management.remote.JMXServiceURL;

import mx4j.remote.ConnectionNotificationEmitter;
import mx4j.remote.ConnectionResolver;
import mx4j.remote.HeartBeat;
import mx4j.remote.RemoteNotificationClientHandler;
import mx4j.tools.remote.AbstractJMXConnector;

/**
 * @author <a href="mailto:[email protected]">Simone Bordet</a>
 * @version $
 */
public abstract class HTTPConnector extends AbstractJMXConnector
{
   private transient HTTPConnection connection;
   private transient String connectionId;
   private transient HeartBeat heartbeat;
   private transient RemoteNotificationClientHandler notificationHandler;

   protected HTTPConnector(JMXServiceURL address) throws IOException
   {
      super(address);
   }

   protected void doConnect(Map environment) throws IOException, SecurityException
   {
      JMXServiceURL address = getAddress();
      String protocol = address.getProtocol();
      ConnectionResolver resolver = ConnectionResolver.newConnectionResolver(protocol, environment);
      if (resolver == null) throw new MalformedURLException("Unsupported protocol: " + protocol);

      HTTPConnection temp = (HTTPConnection)resolver.lookupClient(address, environment);
      connection = (HTTPConnection)resolver.bindClient(temp, environment);

      Object credentials = environment == null ? null : environment.get(CREDENTIALS);
      connectionId = connection.connect(credentials);

      this.heartbeat = createHeartBeat(connection, getConnectionNotificationEmitter(), environment);
      this.notificationHandler = createRemoteNotificationClientHandler(connection, getConnectionNotificationEmitter(), heartbeat, environment);

      this.heartbeat.start();
      this.notificationHandler.start();
   }

   protected HeartBeat createHeartBeat(HTTPConnection connection, ConnectionNotificationEmitter emitter, Map environment)
   {
      return new HTTPHeartBeat(connection, emitter, environment);
   }

   protected RemoteNotificationClientHandler createRemoteNotificationClientHandler(HTTPConnection connection, ConnectionNotificationEmitter emitter, HeartBeat heartbeat, Map environment)
   {
      return new HTTPRemoteNotificationClientHandler(connection, emitter, heartbeat, environment);
   }

   protected void doClose() throws IOException
   {
      if (notificationHandler != null) notificationHandler.stop();
      if (heartbeat != null) heartbeat.stop();
      if (connection != null) connection.close();
   }

   public String getConnectionId() throws IOException
   {
      return connectionId;
   }

   protected HTTPConnection getHTTPConnection()
   {
      return connection;
   }

   public RemoteNotificationClientHandler getRemoteNotificationClientHandler()
   {
      return notificationHandler;
   }
}

--- NEW FILE: HTTPClientInvoker.java ---
/*
 * Copyright (C) MX4J.
 * All rights reserved.
 *
 * This software is distributed under the terms of the MX4J License version 1.0.
 * See the terms of the MX4J License in the documentation provided with this software.
 */

package mx4j.tools.remote.http;

import java.io.IOException;
import java.util.Set;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.InstanceAlreadyExistsException;
import javax.management.InstanceNotFoundException;
import javax.management.IntrospectionException;
import javax.management.InvalidAttributeValueException;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanRegistrationException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import javax.management.remote.NotificationResult;
import javax.security.auth.Subject;

/**
 * @author <a href="mailto:[email protected]">Simone Bordet</a>
 * @version $Revision: 1.1 $
 */
public abstract class HTTPClientInvoker implements HTTPConnection
{
   private String connectionId;

   protected abstract HTTPConnection getService();

   public String connect(Object credentials) throws IOException, SecurityException
   {
      connectionId = getService().connect(credentials);
      return connectionId;
   }

   public void close() throws IOException
   {
      getService().close();
   }

   public String getConnectionId() throws IOException
   {
      return connectionId;
   }

   public ObjectInstance createMBean(String className, ObjectName name, Object params, String[] signature, Subject delegate) throws ReflectionException, InstanceAlreadyExistsException, MBeanRegistrationException, MBeanException, NotCompliantMBeanException, IOException
   {
      return getService().createMBean(className, name, params, signature, delegate);
   }

   public ObjectInstance createMBean(String className, ObjectName name, ObjectName loaderName, Object params, String[] signature, Subject delegate) throws ReflectionException, InstanceAlreadyExistsException, MBeanRegistrationException, MBeanException, NotCompliantMBeanException, InstanceNotFoundException, IOException
   {
      return getService().createMBean(className, name, loaderName, params, signature, delegate);
   }

   public void unregisterMBean(ObjectName name, Subject delegate) throws InstanceNotFoundException, MBeanRegistrationException, IOException
   {
      getService().unregisterMBean(name, delegate);
   }

   public ObjectInstance getObjectInstance(ObjectName name, Subject delegate) throws InstanceNotFoundException, IOException
   {
      return getService().getObjectInstance(name, delegate);
   }

   public Set queryMBeans(ObjectName name, Object query, Subject delegate) throws IOException
   {
      return getService().queryMBeans(name, query, delegate);
   }

   public Set queryNames(ObjectName name, Object query, Subject delegate) throws IOException
   {
      return getService().queryNames(name, query, delegate);
   }

   public boolean isRegistered(ObjectName name, Subject delegate) throws IOException
   {
      return getService().isRegistered(name, delegate);
   }

   public Integer getMBeanCount(Subject delegate) throws IOException
   {
      return getService().getMBeanCount(delegate);
   }

   public Object getAttribute(ObjectName name, String attribute, Subject delegate) throws MBeanException, AttributeNotFoundException, InstanceNotFoundException, ReflectionException, IOException
   {
      return getService().getAttribute(name, attribute, delegate);
   }

   public AttributeList getAttributes(ObjectName name, String[] attributes, Subject delegate) throws InstanceNotFoundException, ReflectionException, IOException
   {
      return getService().getAttributes(name, attributes, delegate);
   }

   public void setAttribute(ObjectName name, Object attribute, Subject delegate) throws InstanceNotFoundException, AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException, IOException
   {
      getService().setAttribute(name, attribute, delegate);
   }

   public AttributeList setAttributes(ObjectName name, Object attributes, Subject delegate) throws InstanceNotFoundException, ReflectionException, IOException
   {
      return getService().setAttributes(name, attributes, delegate);
   }

   public Object invoke(ObjectName name, String operationName, Object params, String[] signature, Subject delegate) throws InstanceNotFoundException, MBeanException, ReflectionException, IOException
   {
      return getService().invoke(name, operationName, params, signature, delegate);
   }

   public String getDefaultDomain(Subject delegate) throws IOException
   {
      return getService().getDefaultDomain(delegate);
   }

   public String[] getDomains(Subject delegate) throws IOException
   {
      return getService().getDomains(delegate);
   }

   public MBeanInfo getMBeanInfo(ObjectName name, Subject delegate) throws InstanceNotFoundException, IntrospectionException, ReflectionException, IOException
   {
      return getService().getMBeanInfo(name, delegate);
   }

   public boolean isInstanceOf(ObjectName name, String className, Subject delegate) throws InstanceNotFoundException, IOException
   {
      return getService().isInstanceOf(name, className, delegate);
   }

   public void addNotificationListener(ObjectName name, ObjectName listener, Object filter, Object handback, Subject delegate) throws InstanceNotFoundException, IOException
   {
      getService().addNotificationListener(name, listener, filter, handback, delegate);
   }

   public void removeNotificationListener(ObjectName name, ObjectName listener, Subject delegate) throws InstanceNotFoundException, ListenerNotFoundException, IOException
   {
      getService().removeNotificationListener(name, listener, delegate);
   }

   public void removeNotificationListener(ObjectName name, ObjectName listener, Object filter, Object handback, Subject delegate) throws InstanceNotFoundException, ListenerNotFoundException, IOException
   {
      getService().removeNotificationListener(name, listener, filter, handback, delegate);
   }

   public Integer addNotificationListener(ObjectName name, Object filter, Subject delegate) throws InstanceNotFoundException, IOException
   {
      return getService().addNotificationListener(name, filter, delegate);
   }

   public void removeNotificationListeners(ObjectName name, Integer[] listenerIDs, Subject delegate) throws InstanceNotFoundException, ListenerNotFoundException, IOException
   {
      getService().removeNotificationListeners(name, listenerIDs, delegate);
   }

   public NotificationResult fetchNotifications(long clientSequenceNumber, int maxNotifications, long timeout) throws IOException
   {
      return getService().fetchNotifications(clientSequenceNumber, maxNotifications, timeout);
   }
}

--- NEW FILE: HTTPHeartBeat.java ---
/*
 * Copyright (C) MX4J.
 * All rights reserved.
 *
 * This software is distributed under the terms of the MX4J License version 1.0.
 * See the terms of the MX4J License in the documentation provided with this software.
 */

package mx4j.tools.remote.http;

import java.io.IOException;
import java.util.Map;

import mx4j.remote.AbstractHeartBeat;
import mx4j.remote.ConnectionNotificationEmitter;

/**
 * @author <a href="mailto:[email protected]">Simone Bordet</a>
 * @version $Revision: 1.1 $
 */
public class HTTPHeartBeat extends AbstractHeartBeat
{
   private final HTTPConnection connection;

   public HTTPHeartBeat(HTTPConnection connection, ConnectionNotificationEmitter emitter, Map environment)
   {
      super(emitter, environment);
      this.connection = connection;
   }

   protected void pulse() throws IOException
   {
      connection.getDefaultDomain(null);
   }
}

--- NEW FILE: HTTPConnectionManager.java ---
/*
 * Copyright (C) MX4J.
 * All rights reserved.
 *
 * This software is distributed under the terms of the MX4J License version 1.0.
 * See the terms of the MX4J License in the documentation provided with this software.
 */

package mx4j.tools.remote.http;

import java.io.IOException;
import java.util.Map;
import javax.management.MBeanServerConnection;
import javax.security.auth.Subject;

import mx4j.remote.DefaultRemoteNotificationServerHandler;
import mx4j.remote.RemoteNotificationServerHandler;
import mx4j.tools.remote.AbstractConnectionManager;
import mx4j.tools.remote.AbstractJMXConnectorServer;
import mx4j.tools.remote.Connection;

/**
 * @author <a href="mailto:[email protected]">Simone Bordet</a>
 * @version $Revision: 1.1 $
 */
public class HTTPConnectionManager extends AbstractConnectionManager
{
   private final MBeanServerConnection mbeanServerConnection;
   private final String protocol;

   public HTTPConnectionManager(AbstractJMXConnectorServer server, String protocol, Map environment)
   {
      super(server, environment);
      this.mbeanServerConnection = server.getMBeanServer();
      this.protocol = protocol;
   }

   public String getProtocol()
   {
      return protocol;
   }

   protected Connection doConnect(String connectionId, Subject subject) throws IOException
   {
      RemoteNotificationServerHandler notificationHandler = new DefaultRemoteNotificationServerHandler(getEnvironment());
      HTTPConnection invoker = new HTTPServerInvoker(mbeanServerConnection, notificationHandler);
      HTTPConnection subjectInvoker = HTTPSubjectInvoker.newInstance(invoker, subject, getSecurityContext());
      Connection handler = new HTTPConnectionHandler(subjectInvoker, this, connectionId);
      return handler;
   }

   /**
    * HTTPConnectionManager does not really manages connections,
    * so this method does nothing by default
    */
   protected void doClose() throws IOException
   {
   }

   /**
    * HTTPConnectionManager does not really manages connections,
    * so this method does nothing by default
    */
   protected void doCloseConnection(Connection connection) throws IOException
   {
   }
}

--- NEW FILE: HTTPResolver.java ---
/*
 * Copyright (C) MX4J.
 * All rights reserved.
 *
 * This software is distributed under the terms of the MX4J License version 1.0.
 * See the terms of the MX4J License in the documentation provided with this software.
 */

package mx4j.tools.remote.http;

import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;

import mx4j.remote.ConnectionResolver;

/**
 * @author <a href="mailto:[email protected]">Simone Bordet</a>
 * @version $Revision: 1.1 $
 */
public abstract class HTTPResolver extends ConnectionResolver
{
   // TODO: maybe worth to use weak references to hold web containers
   private static Map webContainers = new HashMap();
   private static Map deployedURLs = new HashMap();
   private static final WebContainer EXTERNAL_WEB_CONTAINER = new ExternalWebContainer();

   public Object bindClient(Object client, Map environment) throws IOException
   {
      return client;
   }

   protected String getEndpoint(JMXServiceURL address, Map environment)
   {
      String transport = getEndpointProtocol(environment);
      return transport + getEndpointPath(address);
   }

   protected String getEndpointProtocol(Map environment)
   {
      return "http";
   }

   private String getEndpointPath(JMXServiceURL url)
   {
      String address = url.toString();
      String prefix = "service:jmx:" + url.getProtocol();
      return address.substring(prefix.length());
   }

   public Object createServer(JMXServiceURL url, Map environment) throws IOException
   {
      WebContainer result = null;
      boolean useExternalWebContainer = environment == null ? false : Boolean.valueOf(String.valueOf(environment.get(HTTPConnectorServer.USE_EXTERNAL_WEB_CONTAINER))).booleanValue();
      if (!useExternalWebContainer)
      {
         // Create and start an embedded web container
         String webContainerClassName = environment == null ? null : (String)environment.get(HTTPConnectorServer.EMBEDDED_WEB_CONTAINER_CLASS);
         // Not present, by default use Jetty
         if (webContainerClassName == null || webContainerClassName.length() == 0) webContainerClassName = "mx4j.tools.remote.http.jetty.JettyWebContainer";

         result = findWebContainer(url, webContainerClassName);
         if (result == null)
         {
            result = createWebContainer(url, webContainerClassName, environment);
            if (result != null) result.start(url, environment);
         }

         // Nothing present, give up
         if (result == null) throw new IOException("Could not start embedded web container");
      }
      return result;
   }

   private WebContainer findWebContainer(JMXServiceURL url, String webContainerClassName)
   {
      String key = createWebContainerKey(url, webContainerClassName);
      return (WebContainer)webContainers.get(key);
   }

   private String createWebContainerKey(JMXServiceURL url, String webContainerClassName)
   {
      return new StringBuffer(webContainerClassName).append("|").append(url.getHost()).append("|").append(url.getPort()).toString();
   }

   public JMXServiceURL bindServer(Object server, JMXServiceURL url, Map environment) throws IOException
   {
      WebContainer webContainer = (WebContainer)server;
      if (!isDeployed(webContainer, url))
      {
         if (webContainer != null) webContainer.deploy(getServletClassName(), url, environment);
         if (!hasDeployed(webContainer))
         {
            // The jmxconnector web service has never been deployed, deploy it now
            deploy(url, environment);
         }
         addDeployed(webContainer, url);
      }
      return url;
   }

   protected abstract String getServletClassName();

   protected void deploy(JMXServiceURL address, Map environment) throws IOException
   {
   }

   public void unbindServer(Object server, JMXServiceURL address, Map environment) throws IOException
   {
      WebContainer webContainer = (WebContainer)server;
      if (isDeployed(webContainer, address))
      {
         // First undeploy the jmxconnector web service, then undeploy the webContainer: otherwise the service cannot be undeployed
         removeDeployed(webContainer, address);
         if (!hasDeployed(webContainer))
         {
            undeploy(address, environment);
         }
         if (webContainer != null) webContainer.undeploy(getServletClassName(), address, environment);
      }
   }

   protected void undeploy(JMXServiceURL address, Map environment) throws IOException
   {
   }

   public void destroyServer(Object server, JMXServiceURL url, Map environment) throws IOException
   {
      WebContainer webContainer = (WebContainer)server;
      if (webContainer != null && !hasDeployed(webContainer))
      {
         // No more deployed stuff here, shutdown also the web container
         String key = createWebContainerKey(url, server.getClass().getName());
         WebContainer container = (WebContainer)webContainers.remove(key);
         if (webContainer != container) throw new IOException("Trying to stop the wrong web container: " + server + " should be: " + container);
         webContainer.stop();
      }
   }

   private WebContainer createWebContainer(JMXServiceURL url, String webContainerClassName, Map environment)
   {
      ClassLoader loader = Thread.currentThread().getContextClassLoader();
      if (environment != null)
      {
         Object cl = environment.get(JMXConnectorServerFactory.PROTOCOL_PROVIDER_CLASS_LOADER);
         if (cl instanceof ClassLoader) loader = (ClassLoader)cl;
      }

      try
      {
         WebContainer webContainer = (WebContainer)loader.loadClass(webContainerClassName).newInstance();
         String key = createWebContainerKey(url, webContainerClassName);
         webContainers.put(key, webContainer);
         return webContainer;
      }
      catch (Exception x)
      {
      }
      return null;
   }

   private boolean isDeployed(WebContainer webContainer, JMXServiceURL url)
   {
      if (webContainer == null) webContainer = EXTERNAL_WEB_CONTAINER;
      Set urls = (Set)deployedURLs.get(webContainer);
      if (urls == null) return false;
      return urls.contains(url);
   }

   private boolean hasDeployed(WebContainer webContainer)
   {
      if (webContainer == null) webContainer = EXTERNAL_WEB_CONTAINER;
      Set urls = (Set)deployedURLs.get(webContainer);
      if (urls == null) return false;
      return !urls.isEmpty();
   }

   private void addDeployed(WebContainer webContainer, JMXServiceURL url)
   {
      if (webContainer == null) webContainer = EXTERNAL_WEB_CONTAINER;
      Set urls = (Set)deployedURLs.get(webContainer);
      if (urls == null)
      {
         urls = new HashSet();
         deployedURLs.put(webContainer, urls);
      }
      urls.add(url);
   }

   private void removeDeployed(WebContainer webContainer, JMXServiceURL url)
   {
      if (webContainer == null) webContainer = EXTERNAL_WEB_CONTAINER;
      Set urls = (Set)deployedURLs.get(webContainer);
      if (urls != null)
      {
         urls.remove(url);
         if (urls.isEmpty()) deployedURLs.remove(webContainer);
      }
   }

   private static class ExternalWebContainer implements WebContainer
   {
      public void start(JMXServiceURL url, Map environment) throws IOException
      {
      }

      public void stop() throws IOException
      {
      }

      public void deploy(String servletClassName, JMXServiceURL url, Map environment) throws IOException
      {
      }

      public void undeploy(String servletClassName, JMXServiceURL url, Map environment)
      {
      }

      public String toString()
      {
         return "External WebContainer";
      }
   }
}

--- NEW FILE: HTTPConnectorServer.java ---
/*
 * Copyright (C) MX4J.
 * All rights reserved.
 *
 * This software is distributed under the terms of the MX4J License version 1.0.
 * See the terms of the MX4J License in the documentation provided with this software.
 */

package mx4j.tools.remote.http;

import java.io.IOException;
import java.net.MalformedURLException;
import java.util.HashMap;
import java.util.Map;
import javax.management.MBeanServer;
import javax.management.remote.JMXServiceURL;

import mx4j.log.Log;
import mx4j.log.Logger;
import mx4j.remote.ConnectionResolver;
import mx4j.tools.remote.AbstractJMXConnectorServer;
import mx4j.tools.remote.ConnectionManager;

/**
 * @author <a href="mailto:[email protected]">Simone Bordet</a>
 * @version $Revision: 1.1 $
 */
public abstract class HTTPConnectorServer extends AbstractJMXConnectorServer
{
   public static final String USE_EXTERNAL_WEB_CONTAINER = "jmx.remote.x.http.use.external.web.container";
   public static final String EMBEDDED_WEB_CONTAINER_CLASS = "jmx.remote.x.http.embedded.web.container.class";

   private static Map instances = new HashMap();

   private WebContainer webContainer;
   private ConnectionManager connectionManager;

   public HTTPConnectorServer(JMXServiceURL url, Map environment, MBeanServer server)
   {
      super(url, environment, server);
   }

   protected void doStart() throws IOException, IllegalStateException
   {
      MBeanServer server = getMBeanServer();
      if (server == null) throw new IllegalStateException("This JMXConnectorServer is not attached to an MBeanServer");

      JMXServiceURL address = getAddress();
      String protocol = address.getProtocol();
      Map environment = getEnvironment();
      ConnectionResolver resolver = ConnectionResolver.newConnectionResolver(protocol, environment);
      if (resolver == null) throw new MalformedURLException("Unsupported protocol: " + protocol);

      webContainer = (WebContainer)resolver.createServer(address, environment);

      setAddress(resolver.bindServer(webContainer, address, environment));

      connectionManager = createConnectionManager(this, environment);

      // Here is where we give to clients the possibility to access us
      register(getAddress(), connectionManager);
   }

   protected abstract ConnectionManager createConnectionManager(AbstractJMXConnectorServer server, Map environment);

   private void register(JMXServiceURL url, ConnectionManager manager) throws IOException
   {
      synchronized (HTTPConnectorServer.class)
      {
         // TODO: must use weak references to connection managers, otherwise they're not GC'ed
         // TODO: in case the connector server is not stopped cleanly
         if (instances.get(url) != null) throw new IOException("A JMXConnectorServer is already serving at address " + url);
         instances.put(url, manager);
      }
   }

   private void unregister(JMXServiceURL url) throws IOException
   {
      synchronized (HTTPConnectorServer.class)
      {
         Object removed = instances.remove(url);
         if (removed == null) throw new IOException("No JMXConnectorServer is present for address " + url);
      }
   }

   static ConnectionManager find(JMXServiceURL address)
   {
      synchronized (HTTPConnectorServer.class)
      {
         ConnectionManager manager = (ConnectionManager)instances.get(address);
         if (manager != null) return manager;

         Logger logger = Log.getLogger(HTTPConnectorServer.class.getName());
         if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("Known HTTPConnectorServers bound at " + instances.keySet());
         return null;
      }
   }

   protected void doStop() throws IOException
   {
      JMXServiceURL url = getAddress();
      unregister(url);

      if (connectionManager != null)
      {
         connectionManager.close();
         connectionManager = null;
      }

      String protocol = url.getProtocol();
      Map environment = getEnvironment();
      ConnectionResolver resolver = ConnectionResolver.newConnectionResolver(protocol, environment);
      if (resolver == null) throw new MalformedURLException("Unsupported protocol: " + protocol);

      resolver.unbindServer(webContainer, url, environment);

      resolver.destroyServer(webContainer, url, environment);
   }
}

--- NEW FILE: HTTPRemoteNotificationClientHandler.java ---
/*
 * Copyright (C) MX4J.
 * All rights reserved.
 *
 * This software is distributed under the terms of the MX4J License version 1.0.
 * See the terms of the MX4J License in the documentation provided with this software.
 */

package mx4j.tools.remote.http;

import java.io.IOException;
import java.util.Map;
import javax.management.remote.NotificationResult;

import mx4j.remote.AbstractRemoteNotificationClientHandler;
import mx4j.remote.ConnectionNotificationEmitter;
import mx4j.remote.HeartBeat;

/**
 * @author <a href="mailto:[email protected]">Simone Bordet</a>
 * @version $Revision: 1.1 $
 */
public class HTTPRemoteNotificationClientHandler extends AbstractRemoteNotificationClientHandler
{
   private final HTTPConnection connection;

   public HTTPRemoteNotificationClientHandler(HTTPConnection connection, ConnectionNotificationEmitter emitter, HeartBeat heartbeat, Map environment)
   {
      super(emitter, heartbeat, environment);
      this.connection = connection;
   }

   protected NotificationResult fetchNotifications(long sequence, int maxNumber, long timeout) throws IOException
   {
      return connection.fetchNotifications(sequence, maxNumber, timeout);
   }
}

--- NEW FILE: WebContainer.java ---
/*
 * Copyright (C) MX4J.
 * All rights reserved.
 *
 * This software is distributed under the terms of the MX4J License version 1.0.
 * See the terms of the MX4J License in the documentation provided with this software.
 */

package mx4j.tools.remote.http;

import java.io.IOException;
import java.util.Map;
import javax.management.remote.JMXServiceURL;

/**
 * An Interface for the Web Container so that we can plug in any web container.
 *
 * @author <a href="mailto:[email protected]">Alireza Taherkordi</a>
 * @version $Revision: 1.1 $
 */
public interface WebContainer
{
   /**
    * Starts the web container
    */
   public void start(JMXServiceURL url, Map environment) throws IOException;

   /**
    * Stops the web container
    */
   public void stop() throws IOException;

   /**
    * Deploys the given servlet class mapping it to the URL specified by the given JMXServiceURL.
    */
   public void deploy(String servletClassName, JMXServiceURL url, Map environment) throws IOException;

   /**
    * Undeploys the servlet mapped to the URL specified by the given JMXServiceURL.
    */
   public void undeploy(String servletClassName, JMXServiceURL url, Map environment);
}

--- NEW FILE: HTTPConnection.java ---
/*
 * Copyright (C) MX4J.
 * All rights reserved.
 *
 * This software is distributed under the terms of the MX4J License version 1.0.
 * See the terms of the MX4J License in the documentation provided with this software.
 */

package mx4j.tools.remote.http;

import java.io.IOException;
import javax.management.InstanceNotFoundException;
import javax.management.ListenerNotFoundException;
import javax.management.ObjectName;
import javax.management.remote.NotificationResult;
import javax.security.auth.Subject;

import mx4j.tools.remote.JMXConnection;

/**
 * @author <a href="mailto:[email protected]">Simone Bordet</a>
 * @version $Revision: 1.1 $
 */
public interface HTTPConnection extends JMXConnection
{
   /**
    * This method is called when a call initiated by {@link javax.management.remote.JMXConnector#connect}
    * arrives on server side. For HTTP connections, the socket is handled by the web container, but
    * the remote procedure call that arrives along with the HTTP request is parsed and then (normally)
    * forwarded to a JavaBean (that will implement this interface).
    * Implementations of this method will normally call {@link mx4j.tools.remote.ConnectionManager#connect}.
    *
    * @param credentials The credential for authentication
    * @return The connection id for the newly created connection
    * @throws IOException       If a communication problem occurs
    * @throws SecurityException If the authentication fails
    */
   public String connect(Object credentials)
           throws IOException,
                  SecurityException;

   public Integer addNotificationListener(ObjectName name, Object filter, Subject delegate)
           throws InstanceNotFoundException,
                  IOException;

   public void removeNotificationListeners(ObjectName name, Integer[] listenerIDs, Subject delegate)
           throws InstanceNotFoundException,
                  ListenerNotFoundException,
                  IOException;

   public NotificationResult fetchNotifications(long clientSequenceNumber, int maxNotifications, long timeout)
           throws IOException;
}

--- NEW FILE: HTTPSubjectInvoker.java ---
/*
 * Copyright (C) MX4J.
 * All rights reserved.
 *
 * This software is distributed under the terms of the MX4J License version 1.0.
 * See the terms of the MX4J License in the documentation provided with this software.
 */

package mx4j.tools.remote.http;

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.security.AccessControlContext;
import javax.security.auth.Subject;

import mx4j.tools.remote.SubjectInvoker;

/**
 * @author <a href="mailto:[email protected]">Simone Bordet</a>
 * @version $Revision: 1.1 $
 */
public class HTTPSubjectInvoker extends SubjectInvoker
{
   public static HTTPConnection newInstance(HTTPConnection target, Subject subject, AccessControlContext context)
   {
      HTTPSubjectInvoker handler = new HTTPSubjectInvoker(target, subject, context);
      return (HTTPConnection)Proxy.newProxyInstance(target.getClass().getClassLoader(), new Class[]{HTTPConnection.class}, handler);
   }

   private HTTPSubjectInvoker(HTTPConnection target, Subject subject, AccessControlContext context)
   {
      super(target, subject, context);
   }

   protected boolean isPlainInvoke(Method method)
   {
      boolean plain = super.isPlainInvoke(method);
      if (plain) return plain;

      String methodName = method.getName();
      // HTTPConnection methods that does not require the delegate subject
      if ("fetchNotifications".equals(methodName)) return true;
      if ("close".equals(methodName)) return true;
      return false;
   }
}

--- NEW FILE: HTTPConnectionHandler.java ---
/*
 * Copyright (C) MX4J.
 * All rights reserved.
 *
 * This software is distributed under the terms of the MX4J License version 1.0.
 * See the terms of the MX4J License in the documentation provided with this software.
 */

package mx4j.tools.remote.http;

import java.io.IOException;
import javax.management.InstanceNotFoundException;
import javax.management.ListenerNotFoundException;
import javax.management.ObjectName;
import javax.management.remote.NotificationResult;
import javax.security.auth.Subject;

import mx4j.tools.remote.ConnectionManager;
import mx4j.tools.remote.JMXConnection;
import mx4j.tools.remote.JMXConnectionHandler;

/**
 * @author <a href="mailto:[email protected]">Simone Bordet</a>
 * @version $
 */
public class HTTPConnectionHandler extends JMXConnectionHandler implements HTTPConnection
{
   public HTTPConnectionHandler(JMXConnection connection, ConnectionManager manager, String connectionId)
   {
      super(connection, manager, connectionId);
   }

   public String connect(Object credentials) throws IOException, SecurityException
   {
      throw new Error("Method connect() must not be forwarded to the invocation chain");
   }

   public Integer addNotificationListener(ObjectName name, Object filter, Subject delegate) throws InstanceNotFoundException, IOException
   {
      if (isClosed()) throw new IOException("Connection has been closed");
      return ((HTTPConnection)getConnection()).addNotificationListener(name, filter, delegate);
   }

   public void removeNotificationListeners(ObjectName name, Integer[] listenerIDs, Subject delegate) throws InstanceNotFoundException, ListenerNotFoundException, IOException
   {
      if (isClosed()) throw new IOException("Connection has been closed");
      ((HTTPConnection)getConnection()).removeNotificationListeners(name, listenerIDs, delegate);
   }

   public NotificationResult fetchNotifications(long clientSequenceNumber, int maxNotifications, long timeout) throws IOException
   {
      if (isClosed()) throw new IOException("Connection has been closed");
      return ((HTTPConnection)getConnection()).fetchNotifications(clientSequenceNumber, maxNotifications, timeout);
   }
}

--- NEW FILE: HTTPConnectionMBeanServerConnection.java ---
/*
 * Copyright (C) MX4J.
 * All rights reserved.
 *
 * This software is distributed under the terms of the MX4J License version 1.0.
 * See the terms of the MX4J License in the documentation provided with this software.
 */

package mx4j.tools.remote.http;

import java.io.IOException;
import javax.management.InstanceNotFoundException;
import javax.management.ListenerNotFoundException;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import javax.security.auth.Subject;

import mx4j.remote.NotificationTuple;
import mx4j.remote.RemoteNotificationClientHandler;
import mx4j.tools.remote.JMXConnection;
import mx4j.tools.remote.JMXConnectionMBeanServerConnection;

/**
 * Implementation of an adapter that converts MBeanServerConnection calls
 * to HTTPConnection calls.
 * It handles remote notifications, but it does not handle unmarshalling of
 * arguments (and all related classloading problems).
 * NotificationFilters are always invoked on client side.
 *
 * @author <a href="mailto:[email protected]">Simone Bordet</a>
 * @version $Revision: 1.1 $
 */
public class HTTPConnectionMBeanServerConnection extends JMXConnectionMBeanServerConnection
{
   private final RemoteNotificationClientHandler notificationHandler;

   public HTTPConnectionMBeanServerConnection(JMXConnection connection, Subject delegate, RemoteNotificationClientHandler notificationHandler)
   {
      super(connection, delegate);
      this.notificationHandler = notificationHandler;
   }

   public void addNotificationListener(ObjectName observed, NotificationListener listener, NotificationFilter filter, Object handback) throws InstanceNotFoundException, IOException
   {
      NotificationTuple tuple = new NotificationTuple(observed, listener, filter, handback);
      // Filters are always invoked on client side, for now
      tuple.setInvokeFilter(true);
      if (notificationHandler.contains(tuple)) return;
      Integer id = ((HTTPConnection)getConnection()).addNotificationListener(observed, null, getDelegateSubject());
      notificationHandler.addNotificationListener(id, tuple);
   }

   public void removeNotificationListener(ObjectName observed, NotificationListener listener) throws InstanceNotFoundException, ListenerNotFoundException, IOException
   {
      Integer[] ids = notificationHandler.getNotificationListeners(new NotificationTuple(observed, listener));
      if (ids == null) throw new ListenerNotFoundException("Could not find listener " + listener);
      ((HTTPConnection)getConnection()).removeNotificationListeners(observed, ids, getDelegateSubject());
      notificationHandler.removeNotificationListeners(ids);
   }

   public void removeNotificationListener(ObjectName observed, NotificationListener listener, NotificationFilter filter, Object handback) throws InstanceNotFoundException, ListenerNotFoundException, IOException
   {
      Integer id = notificationHandler.getNotificationListener(new NotificationTuple(observed, listener, filter, handback));
      if (id == null) throw new ListenerNotFoundException("Could not find listener " + listener + " with filter " + filter + " and handback " + handback);
      Integer[] ids = new Integer[]{id};
      ((HTTPConnection)getConnection()).removeNotificationListeners(observed, ids, getDelegateSubject());
      notificationHandler.removeNotificationListeners(ids);
   }
}

--- NEW FILE: HTTPService.java ---
/*
 * Copyright (C) MX4J.
 * All rights reserved.
 *
 * This software is distributed under the terms of the MX4J License version 1.0.
 * See the terms of the MX4J License in the documentation provided with this software.
 */

package mx4j.tools.remote.http;

import java.io.IOException;
import java.net.MalformedURLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.InstanceAlreadyExistsException;
import javax.management.InstanceNotFoundException;
import javax.management.IntrospectionException;
import javax.management.InvalidAttributeValueException;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanRegistrationException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import javax.management.remote.JMXServiceURL;
import javax.management.remote.NotificationResult;
import javax.security.auth.Subject;

import mx4j.log.Log;
import mx4j.log.Logger;
import mx4j.tools.remote.Connection;
import mx4j.tools.remote.ConnectionManager;

/**
 * @author <a href="mailto:[email protected]">Simone Bordet</a>
 * @version $Revision: 1.1 $
 */
public abstract class HTTPService implements HTTPConnection
{
   private final Map connections = new HashMap();

   protected Logger getLogger()
   {
      return Log.getLogger(getClass().getName());
   }

   public String connect(Object credentials) throws IOException, SecurityException
   {
      JMXServiceURL address = findJMXServiceURL();

      // Lookup the ConnectionManager
      ConnectionManager connectionManager = HTTPConnectorServer.find(address);
      if (connectionManager == null) throw new IOException("Could not find ConnectionManager. Make sure a SOAPConnectorServer is in classloader scope and bound at this address " + address);

      Connection connection = connectionManager.connect(credentials);
      addConnection(connection);
      return connection.getConnectionId();
   }

   protected JMXServiceURL findJMXServiceURL() throws MalformedURLException
   {
      String url = findRequestURL();
      JMXServiceURL temp = new JMXServiceURL("service:jmx:" + url);
      int port = temp.getPort();
      if ("http".equals(temp.getProtocol()) && port == 0)
      {
         // Default HTTP port, set it to 80
         port = 80;
      }
      else if ("https".equals(temp.getProtocol()) && port == 0)
      {
         // Default HTTPS port, set it to 443
         port = 443;
      }
      return new JMXServiceURL(getProtocol(), temp.getHost(), port, temp.getURLPath());
   }

   protected abstract String findRequestURL();

   protected abstract String getProtocol();

   protected void addConnection(Connection connection) throws IOException
   {
      String connectionId = connection.getConnectionId();
      synchronized (this)
      {
         if (connections.containsKey(connectionId)) throw new IOException("Connection '" + connection + "' already connected");
         connections.put(connectionId, connection);

         Logger logger = getLogger();
         if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("Added connection '" + connectionId + "', known connections are " + connections.keySet());
      }
   }

   protected void removeConnection(Connection connection) throws IOException
   {
      String connectionId = connection.getConnectionId();
      synchronized (this)
      {
         if (!connections.containsKey(connectionId)) throw new IOException("Connection '" + connection + "' unknown");
         connections.remove(connectionId);

         Logger logger = getLogger();
         if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("Removed connection '" + connectionId + "', known connections are " + connections.keySet());
      }
   }

   protected Connection findConnection() throws IOException
   {
      String connectionId = findConnectionId();
      synchronized (this)
      {
         Connection connection = (Connection)connections.get(connectionId);
         if (connection != null) return connection;

         Logger logger = getLogger();
         if (logger.isEnabledFor(Logger.DEBUG)) logger.debug("Unknown connection '" + connectionId + "', known connections are " + connections.keySet());
         throw new IOException("Connection ID '" + connectionId + "' unknown");
      }
   }

   protected abstract String findConnectionId();

   public void close() throws IOException
   {
      Connection connection = findConnection();
      removeConnection(connection);
      connection.close();
   }

   public String getConnectionId() throws IOException
   {
      Connection connection = findConnection();
      return connection.getConnectionId();
   }

   public ObjectInstance createMBean(String className, ObjectName name, Object params, String[] signature, Subject delegate) throws ReflectionException, InstanceAlreadyExistsException, MBeanRegistrationException, MBeanException, NotCompliantMBeanException, IOException
   {
      HTTPConnection connection = (HTTPConnection)findConnection();
      return connection.createMBean(className, name, params, signature, delegate);
   }

   public ObjectInstance createMBean(String className, ObjectName name, ObjectName loaderName, Object params, String[] signature, Subject delegate) throws ReflectionException, InstanceAlreadyExistsException, MBeanRegistrationException, MBeanException, NotCompliantMBeanException, InstanceNotFoundException, IOException
   {
      HTTPConnection connection = (HTTPConnection)findConnection();
      return connection.createMBean(className, name, loaderName, params, signature, delegate);
   }

   public void unregisterMBean(ObjectName name, Subject delegate) throws InstanceNotFoundException, MBeanRegistrationException, IOException
   {
      HTTPConnection connection = (HTTPConnection)findConnection();
      connection.unregisterMBean(name, delegate);
   }

   public ObjectInstance getObjectInstance(ObjectName name, Subject delegate) throws InstanceNotFoundException, IOException
   {
      HTTPConnection connection = (HTTPConnection)findConnection();
      return connection.getObjectInstance(name, delegate);
   }

   public Set queryMBeans(ObjectName name, Object query, Subject delegate) throws IOException
   {
      HTTPConnection connection = (HTTPConnection)findConnection();
      return connection.queryMBeans(name, query, delegate);
   }

   public Set queryNames(ObjectName name, Object query, Subject delegate) throws IOException
   {
      HTTPConnection connection = (HTTPConnection)findConnection();
      return connection.queryNames(name, query, delegate);
   }

   public boolean isRegistered(ObjectName name, Subject delegate) throws IOException
   {
      HTTPConnection connection = (HTTPConnection)findConnection();
      return connection.isRegistered(name, delegate);
   }

   public Integer getMBeanCount(Subject delegate) throws IOException
   {
      HTTPConnection connection = (HTTPConnection)findConnection();
      return connection.getMBeanCount(delegate);
   }

   public Object getAttribute(ObjectName name, String attribute, Subject delegate) throws MBeanException, AttributeNotFoundException, InstanceNotFoundException, ReflectionException, IOException
   {
      HTTPConnection connection = (HTTPConnection)findConnection();
      return connection.getAttribute(name, attribute, delegate);
   }

   public AttributeList getAttributes(ObjectName name, String[] attributes, Subject delegate) throws InstanceNotFoundException, ReflectionException, IOException
   {
      HTTPConnection connection = (HTTPConnection)findConnection();
      return connection.getAttributes(name, attributes, delegate);
   }

   public void setAttribute(ObjectName name, Object attribute, Subject delegate) throws InstanceNotFoundException, AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException, IOException
   {
      HTTPConnection connection = (HTTPConnection)findConnection();
      connection.setAttribute(name, attribute, delegate);
   }

   public AttributeList setAttributes(ObjectName name, Object attributes, Subject delegate) throws InstanceNotFoundException, ReflectionException, IOException
   {
      HTTPConnection connection = (HTTPConnection)findConnection();
      return connection.setAttributes(name, attributes, delegate);
   }

   public Object invoke(ObjectName name, String operationName, Object params, String[] signature, Subject delegate) throws InstanceNotFoundException, MBeanException, ReflectionException, IOException
   {
      HTTPConnection connection = (HTTPConnection)findConnection();
      return connection.invoke(name, operationName, params, signature, delegate);
   }

   public String getDefaultDomain(Subject delegate) throws IOException
   {
      HTTPConnection connection = (HTTPConnection)findConnection();
      return connection.getDefaultDomain(delegate);
   }

   public String[] getDomains(Subject delegate) throws IOException
   {
      HTTPConnection connection = (HTTPConnection)findConnection();
      return connection.getDomains(delegate);
   }

   public MBeanInfo getMBeanInfo(ObjectName name, Subject delegate) throws InstanceNotFoundException, IntrospectionException, ReflectionException, IOException
   {
      HTTPConnection connection = (HTTPConnection)findConnection();
      return connection.getMBeanInfo(name, delegate);
   }

   public boolean isInstanceOf(ObjectName name, String className, Subject delegate) throws InstanceNotFoundException, IOException
   {
      HTTPConnection connection = (HTTPConnection)findConnection();
      return connection.isInstanceOf(name, className, delegate);
   }

   public void addNotificationListener(ObjectName name, ObjectName listener, Object filter, Object handback, Subject delegate) throws InstanceNotFoundException, IOException
   {
      HTTPConnection connection = (HTTPConnection)findConnection();
      connection.addNotificationListener(name, listener, filter, handback, delegate);
   }

   public void removeNotificationListener(ObjectName name, ObjectName listener, Subject delegate) throws InstanceNotFoundException, ListenerNotFoundException, IOException
   {
      HTTPConnection connection = (HTTPConnection)findConnection();
      connection.removeNotificationListener(name, listener, delegate);
   }

   public void removeNotificationListener(ObjectName name, ObjectName listener, Object filter, Object handback, Subject delegate) throws InstanceNotFoundException, ListenerNotFoundException, IOException
   {
      HTTPConnection connection = (HTTPConnection)findConnection();
      connection.removeNotificationListener(name, listener, filter, handback, delegate);
   }

   public Integer addNotificationListener(ObjectName name, Object filter, Subject delegate) throws InstanceNotFoundException, IOException
   {
      HTTPConnection connection = (HTTPConnection)findConnection();
      return connection.addNotificationListener(name, filter, delegate);
   }

   public void removeNotificationListeners(ObjectName name, Integer[] listenerIDs, Subject delegate) throws InstanceNotFoundException, ListenerNotFoundException, IOException
   {
      HTTPConnection connection = (HTTPConnection)findConnection();
      connection.removeNotificationListeners(name, listenerIDs, delegate);
   }

   public NotificationResult fetchNotifications(long clientSequenceNumber, int maxNotifications, long timeout) throws IOException
   {
      HTTPConnection connection = (HTTPConnection)findConnection();
      return connection.fetchNotifications(clientSequenceNumber, maxNotifications, timeout);
   }
}



-------------------------------------------------------
SF.Net email is sponsored by Shop4tech.com-Lowest price on Blank Media
100pk Sonic DVD-R 4x for only $29 -100pk Sonic DVD+R for only $33
Save 50% off Retail on Ink & Toner - Free Shipping and Free Gift.
http://www.shop4tech.com/z/Inkjet_Cartridges/9_108_r285
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.