/*--- formatted by Jindent 2.1, (www.c-lab.de/~jindent) ---*/

/**
 * ***************************************************************
 * The LEAP libraries, when combined with certain JADE platform components,
 * provide a run-time environment for enabling FIPA agents to execute on
 * lightweight devices running Java. LEAP and JADE teams have jointly
 * designed the API for ease of integration and hence to take advantage
 * of these dual developments and extensions so that users only see
 * one development platform and a
 * single homogeneous set of APIs. Enabling deployment to a wide range of
 * devices whilst still having access to the full development
 * environment and functionalities that JADE provides.
 * Copyright (C) 2001 Telecom Italia LAB S.p.A.
 * Copyright (C) 2001 Motorola.
 * 
 * GNU Lesser General Public License
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation,
 * version 2.1 of the License.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA  02111-1307, USA.
 * **************************************************************
 */
package jade.imtp.leap.JICP;

//#MIDP_EXCLUDE_FILE

import jade.core.Profile;
import jade.imtp.leap.*;
import jade.security.JADESecurityException;
import jade.util.Logger;
import jade.util.leap.Properties;

import java.io.EOFException;
import java.io.IOException;
import java.io.InterruptedIOException;

import java.net.*;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

/**
 * Class declaration
 * @author Giovanni Caire - TILAB
 * @author Ronnie Taib - Motorola
 * @author Nicolas Lhuillier - Motorola
 * @author Steffen Rusitschka - Siemens
 */
public class JICPServer extends Thread 
//#J2ME_EXCLUDE_BEGIN
implements PDPContextManager.Listener, JICPMediatorManager 
//#J2ME_EXCLUDE_END
{	
	private static final int INIT = 0;
	private static final int REQUEST_READ = 1;
	private static final int REQUEST_SERVED = 2;
	private static final int RESPONSE_SENT = 3;
	
	public static final String ACCEPT_LOCAL_HOST_ONLY = "jade_imtp_leap_JICP_JICPServer_acceptlocalhostonly";
	public static final String UNCHECK_LOCAL_HOST = "jade_imtp_leap_JICP_JICPServer_unchecklocalhost";
	
	private static final int LISTENING = 0;
	private static final int TERMINATING = 1;
	
	private int      state = LISTENING;
	
	private String host;
	private String exportHost;
	private Integer exportPort;
	
	private ServerSocket server;
	private ICP.Listener cmdListener;
	
	private int            mediatorCnt = 1;
	private Hashtable      mediators = new Hashtable();
	//#J2ME_EXCLUDE_BEGIN
	public static final String ACCEPT_MEDIATORS = "jade_imtp_leap_JICP_JICPServer_acceptmediators";
	private boolean acceptMediators = true;
	private Properties     leapProps = new Properties();
	private PDPContextManager  myPDPContextManager;
	//#J2ME_EXCLUDE_END
	
	private int maxHandlers;
	private Vector connectionHandlers;
	
	private ConnectionFactory connFactory;
	
	private Logger myLogger;
	
	/**
	 * Constructor declaration
	 */
	public JICPServer(Profile p, JICPPeer myPeer, ICP.Listener l, ConnectionFactory f, int max) throws ICPException {
		
		connectionHandlers = new Vector();
		cmdListener = l;
		connFactory = f;
		maxHandlers = max;
		myLogger = Logger.getMyLogger(getClass().getName());
		
		StringBuffer sb = null;
		int idLength;
		String peerID = myPeer.getID();
		if (peerID != null) {
			sb = new StringBuffer(peerID);
			sb.append('-');
			idLength = sb.length();
		} 
		else {
			sb = new StringBuffer();
			idLength = 0;
		}
		
		// Local host
		sb.append(JICPProtocol.LOCAL_HOST_KEY);
		host = p.getParameter(sb.toString(), null);
		boolean acceptLocalHostOnly = false;
		if (host == null || host.equals(Profile.LOCALHOST_CONSTANT)) {
			// Local host not specified --> Get it automatically
			sb.setLength(idLength);
			sb.append(JICPProtocol.REMOTE_URL_KEY);
			String remoteURL = p.getParameter(sb.toString(), null);
			if (remoteURL != null) {
				// Retrieve the local host address by means of the GET_ADDRESS JICP functionality
				host = myPeer.getAddress(remoteURL);
			} 
			else {
				// Retrieve the host address/name from the underlying operating system
				host = Profile.getDefaultNetworkName(p.getBooleanProperty(Profile.PRIVILEDGE_LOGICAL_NAME, false));
			}
		}
		else {
			// Unless the UNCKECK_LOCAL_HOST property is set, if a local-host is explicitly specified check 
			// that it is a valid local address
			if (!p.getBooleanProperty(UNCHECK_LOCAL_HOST, false) && !Profile.isLocalHost(host)) {
				throw new ICPException("Error: Not possible to launch JADE on a remote host ("+host+"). Check the -host and -local-host options.");
			}
			// Then if the ACCEPT_LOCAL_HOST_ONLY property is specified,
			// we will accept connections only on the specified local network address 
			acceptLocalHostOnly = p.getBooleanProperty(ACCEPT_LOCAL_HOST_ONLY, false);
		}
		
		// Local port: a peripheral container can change it if busy...
		int port = JICPProtocol.DEFAULT_PORT;
		boolean changePortIfBusy = !p.getBooleanProperty(Profile.MAIN, true) || p.getBooleanProperty(LEAPIMTPManager.CHANGE_PORT_IF_BUSY, false);

		sb.setLength(idLength);
		sb.append(JICPProtocol.LOCAL_PORT_KEY);
		String strPort = p.getParameter(sb.toString(), null);
		try {
			port = Integer.parseInt(strPort);
		} 
		catch (Exception e) {
			// Try to use the Peer-ID as the port number
			try {
				port = Integer.parseInt(peerID);
			} 
			catch (Exception e1) {
				// Keep default
			}
		}
		
		//#J2ME_EXCLUDE_BEGIN
		// Get the accept-mediators option
		acceptMediators = p.getBooleanProperty(ACCEPT_MEDIATORS, true);
		
		if (acceptMediators) {
			// Read the LEAP configuration properties
			sb.setLength(idLength);
			sb.append(LEAP_PROPERTY_FILE);
			String fileName = p.getParameter(sb.toString(), LEAP_PROPERTY_FILE_DEFAULT);
			try {
				leapProps.load(fileName);
			} catch (Exception e) {
				myLogger.log(Logger.FINE, "Can't read LEAP property file " + fileName + ". " + e);
				// Ignore: no back end properties specified
			}
			
			// Initialize the PDPContextManager if specified
			String pdpContextManagerClass = leapProps.getProperty(PDP_CONTEXT_MANAGER_CLASS);
			if (pdpContextManagerClass != null) {
				try {
					myLogger.log(Logger.INFO, "Loading PDPContextManager of class " + pdpContextManagerClass);
					myPDPContextManager = (PDPContextManager) Class.forName(pdpContextManagerClass).newInstance();
					myPDPContextManager.init(leapProps);
					myPDPContextManager.registerListener(this);
				} catch (Throwable t) {
					t.printStackTrace();
					myPDPContextManager = null;
				}
			}
		}
		//#J2ME_EXCLUDE_END
		
		// Create the ServerSocket.  
		server = myPeer.getServerSocket((acceptLocalHostOnly ? host : null), port, changePortIfBusy);
		
		setDaemon(true);
		setName("JICPServer-" + getLocalPort());
		
		exportHost = p.getParameter(Profile.EXPORT_HOST, null);
		String exportPortStr = p.getParameter(Profile.EXPORT_PORT, null);
		try {
			exportPort = Integer.getInteger(exportPortStr);
		}
		catch (Exception e) {}
	}
	
	public int getLocalPort() {
		return exportPort != null ? exportPort.intValue() : server.getLocalPort();
	}
	
	public String getLocalHost() {
		// If a local-host was not specified, we accept connection on all local network addresses,
		// but we expose the local host address we "prefer".
		return exportHost != null ? exportHost : host;
	}
	
	/**
	 Shut down this JICP server
	 */
	public synchronized void shutdown() {
		
		if(myLogger.isLoggable(Logger.FINE))
			myLogger.log(Logger.FINE,"Shutting down JICPServer...");
		
		state = TERMINATING;
		
		try {
			// Force the listening thread (this) to exit from the accept()
			// Calling this.interrupt(); should be the right way, but it seems
			// not to work...so do that by closing the server socket.
			server.close();
			
			// Wait for the listening thread to complete
			this.join();
		} 
		catch (IOException ioe) {
			ioe.printStackTrace();
		} 
		catch (InterruptedException ie) {
			ie.printStackTrace();
		} 
	} 
	
	/**
	 * JICPServer thread entry point. Accept incoming connections 
	 * and for each of them start a ConnectionHandler that handles it. 
	 */
	public void run() {
		int chCount = 0;
		while (state != TERMINATING) {
			try {
				// Accept connection
				Socket s = server.accept();
				Connection.socketCnt++;
				InetAddress addr = s.getInetAddress();
				int port = s.getPort();
				if(myLogger.isLoggable(Logger.FINEST))
					myLogger.log(Logger.FINEST,"Incoming connection from "+addr+":"+port);
				
				Connection c = connFactory.createConnection(s);
				ConnectionHandler ch = new ConnectionHandler(c, addr, port, chCount++);

				if(myLogger.isLoggable(Logger.FINEST))
					myLogger.log(Logger.FINEST,"Create new ConnectionHandler ("+ch+")");
				
				connectionHandlers.addElement(ch);
				
				ch.start();    // start a handler and go back to listening
			} 
			catch (InterruptedIOException e) {
				// These can be generated by socket timeout (just ignore
				// the exception) or by a call to the shutdown()
				// method (the state has been set to TERMINATING and the
				// server will exit).
			} 
			catch (Exception e) {
				if (state == LISTENING) {
					if(myLogger.isLoggable(Logger.WARNING))
						myLogger.log(Logger.WARNING,"Problems accepting a new connection");
					e.printStackTrace();
					
					// Stop listening
					state = TERMINATING;
				}
			} 
		} // END of while(listen) 
		
		if(myLogger.isLoggable(Logger.FINE))
			myLogger.log(Logger.FINE,"JICPServer terminated");
		
		// release socket
		try {
			server.close();
		} 
		catch (IOException io) {
			if(myLogger.isLoggable(Logger.WARNING))
				myLogger.log(Logger.WARNING,"I/O error closing the server socket");
			io.printStackTrace();
		} 
		
		server = null;

		// Close all connection handler
		synchronized (connectionHandlers) {
			ConnectionHandler ch;
			Enumeration en = connectionHandlers.elements();
			while(en.hasMoreElements()) {
				ch = (ConnectionHandler) en.nextElement();
				ch.close();
			}
		}
		
		//#J2ME_EXCLUDE_BEGIN
		// Close all mediators
		Enumeration e = mediators.elements();
		while (e.hasMoreElements()) {
			JICPMediator m = (JICPMediator) e.nextElement();
			m.kill();
		} 
		mediators.clear();
		//#J2ME_EXCLUDE_END
	} 
	
	/**
	 Called by the JICPPeer ticker at each tick
	 */
	public void tick(long currentTime) {
		//#J2ME_EXCLUDE_BEGIN
		synchronized (mediators) {
			Enumeration e = mediators.elements();
			while (e.hasMoreElements()) {
				JICPMediator m = (JICPMediator) e.nextElement();
				m.tick(currentTime);
			}
		}
		//#J2ME_EXCLUDE_END
	}
	
	/**
	 Inner class ConnectionHandler.
	 Handle a connection accepted by this JICPServer
	 */
	class ConnectionHandler extends Thread {
		private Connection c;
		private InetAddress addr;
		private int port;
		private boolean loop = false;
		private int status = INIT;
		private boolean closeConnection = true;
		
		
		/**
		 * Constructor declaration
		 * @param s
		 */
		public ConnectionHandler(Connection c, InetAddress addr, int port, int count) {
			this.c = c;
			this.addr = addr;
			this.port = port;
			setName("JICP-CH-"+count);
		}

		/**
		 * close connection handler
		 */
		public void close() {
			
			if (status != RESPONSE_SENT) {
				// We are serving a request --> Prepare to close connection handler
				loop = false;
				closeConnection = true;

				if(myLogger.isLoggable(Logger.FINEST))
					myLogger.log(Logger.FINEST,"Predispose to close connection handler ("+this+")");

			} else {
				// We are waiting for the next request --> Close connection to force connection handler termination
				try {
					if(myLogger.isLoggable(Logger.FINEST))
						myLogger.log(Logger.FINEST,"Close connection socket to force exit from connection handler ("+this+")");
					
					c.close();
				} catch (IOException e) {
					if(myLogger.isLoggable(Logger.FINEST))
						myLogger.log(Logger.FINEST,"Exception closing connection with "+addr+":"+port);
				}
			}
		}
		
		/**
		 * Thread entry point
		 */
		public void run() {
			if(myLogger.isLoggable(Logger.FINEST))
				myLogger.log(Logger.FINEST,"CommandHandler started");
			
			byte type = (byte) 0;
			try {
				do {
					// Read the incoming JICPPacket
					JICPPacket pkt = c.readPacket();
					JICPPacket reply = null;
					status = REQUEST_READ;
					
					type = pkt.getType();
					switch (type) {
					case JICPProtocol.COMMAND_TYPE:
					case JICPProtocol.RESPONSE_TYPE:
						// Get the right recipient and let it process the command.
						String recipientID = pkt.getRecipientID();
						if(myLogger.isLoggable(Logger.FINEST))
							myLogger.log(Logger.FINEST,"Recipient: "+recipientID);
						if (recipientID != null) {
							//#J2ME_EXCLUDE_BEGIN
							// The recipient is one of the mediators
							JICPMediator m = (JICPMediator) mediators.get(recipientID);
							if (m != null) {
								if(myLogger.isLoggable(Logger.FINEST))
									myLogger.log(Logger.FINEST,"Passing incoming packet to mediator "+recipientID);
								reply = m.handleJICPPacket(pkt, addr, port);
							} 
							else {
								// If the packet is a response we don't need to reply
								if (type == JICPProtocol.COMMAND_TYPE) { 
									reply = new JICPPacket("Unknown recipient "+recipientID, null);
								}
							} 
							//#J2ME_EXCLUDE_END
						} 
						else {
							// The recipient is my ICP.Listener (the local CommandDispatcher)
							loop = true;
							if (type == JICPProtocol.COMMAND_TYPE) { 
								if(myLogger.isLoggable(Logger.FINEST))
									myLogger.log(Logger.FINEST,"Passing incoming COMMAND to local listener");
								
								byte[] rsp = cmdListener.handleCommand(pkt.getData());
								byte dataInfo = JICPProtocol.DEFAULT_INFO;
								if (connectionHandlers.size() >= maxHandlers) {
									// Too many connections open --> close the connection as soon as the command has been served
									dataInfo |= JICPProtocol.TERMINATED_INFO;
									loop = false;
								}
								reply = new JICPPacket(JICPProtocol.RESPONSE_TYPE, dataInfo, rsp);
							}

							if ((pkt.getInfo() & JICPProtocol.TERMINATED_INFO) != 0) {
								loop = false;
							}
						} 
						break;
						
						//#J2ME_EXCLUDE_BEGIN
					case JICPProtocol.GET_ADDRESS_TYPE:
						// Respond sending back the caller address
						if(myLogger.isLoggable(Logger.INFO))
							myLogger.log(Logger.INFO,"Received a GET_ADDRESS request from "+addr+":"+port);
						String addressStr = addr.getHostAddress();
						if (pkt.getData() != null ) {
							addressStr += ":"+port;
						}
						reply = new JICPPacket(JICPProtocol.RESPONSE_TYPE, JICPProtocol.DEFAULT_INFO, addressStr.getBytes());
						break;
						
					case JICPProtocol.CREATE_MEDIATOR_TYPE:
						if (acceptMediators) {
							if(myLogger.isLoggable(Logger.INFO))
								myLogger.log(Logger.INFO,"Received a CREATE_MEDIATOR request from "+ addr + ":" + port);
							
							// Starts a new Mediator and sends back its ID
							String s = new String(pkt.getData());
							Properties p = FrontEndStub.parseCreateMediatorRequest(s);

							// If the platform-name is specified refuse the request: JICPServer does not accept this kind of mediator creation request
							String pn = p.getProperty(Profile.PLATFORM_ID);
							if (pn != null) {
								myLogger.log(Logger.WARNING, "CREATE_MEDIATOR request with specified platform-name: "+pn);
								reply = new JICPPacket(JICPProtocol.NOT_AUTHORIZED_ERROR, new JADESecurityException("Platform-name specified"));
								break;
							}

							// If there is a PDPContextManager add the PDP context properties
							if (myPDPContextManager != null) {
								try{
									//FIXME: Get username and password specified among the CREATE_MEDIATOR properties
									Properties pdpContextInfo = myPDPContextManager.getPDPContextInfo(addr, p.getProperty(Profile.OWNER));
									myLogger.log(Logger.FINE, "PDPContext properties = "+pdpContextInfo);
									mergeProperties(p, pdpContextInfo);
								}catch(JADESecurityException jse){
									if(myLogger.isLoggable(Logger.WARNING))
										myLogger.log(Logger.WARNING,"CREATE_MEDIATOR request from non authorized address: "+addr);
									reply = new JICPPacket(JICPProtocol.NOT_AUTHORIZED_ERROR, jse);
									break;
								}
							}
							
							// Get mediator ID from the passed properties (if present)
							String id = p.getProperty(JICPProtocol.MEDIATOR_ID_KEY); 
							String msisdn = p.getProperty(PDPContextManager.MSISDN);
							if(id != null) {
								if (msisdn != null && !msisdn.equals(id)) {
									// Security attack: Someone is pretending to be someone other
									if(myLogger.isLoggable(Logger.WARNING))
										myLogger.log(Logger.WARNING,"CREATE_MEDIATOR request with mediator-id != MSISDN. Address is: "+addr);
									reply = new JICPPacket(JICPProtocol.NOT_AUTHORIZED_ERROR, new JADESecurityException("Inconsistent mediator-id and msisdn"));
									break;
								}	
								// An existing front-end whose back-end was lost. The BackEnd must resynch 
								p.setProperty(jade.core.BackEndContainer.RESYNCH, "true");
							}
							else {
								// Use the MSISDN (if present) 
								id = msisdn;
								if (id == null) {
									// Construct a default id using the string representation of the server's TCP endpoint
									id = "BE-"+getLocalHost() + ':' + getLocalPort() + '-' + String.valueOf(mediatorCnt++);
								}
							}
							
							// If last connection from the same device aborted, the old 
							// BackEnd may still exist as a zombie. In case ids are assigned
							// using the MSISDN the new name is equals to the old one.
							if (id.equals(msisdn)) {
								JICPMediator old = (JICPMediator) mediators.get(id);
								
								if (old != null) {
									// This is a zombie mediator --> kill it
									myLogger.log(Logger.INFO, "Replacing old mediator "+id);
									old.kill();
									// Be sure the zombie container has been removed
									waitABit(1000);
								}
							}
							
							// Start the mediator
							JICPMediator m = startMediator(id, p);
							closeConnection = !m.handleIncomingConnection(c, pkt, addr, port);
							mediators.put(m.getID(), m);
							
							// Create an ad-hoc reply including the assigned mediator-id and the IP address
							p.setProperty(JICPProtocol.MEDIATOR_ID_KEY, m.getID());
							p.setProperty(JICPProtocol.LOCAL_HOST_KEY, addr.getHostAddress());
							String replyMsg = FrontEndStub.encodeCreateMediatorResponse(p);
							reply = new JICPPacket(JICPProtocol.RESPONSE_TYPE, JICPProtocol.DEFAULT_INFO, replyMsg.getBytes());
							reply.setSessionID((byte) 31); // Dummy session ID != from valid ones
						}
						else {
							myLogger.log(Logger.WARNING,"CREATE_MEDIATOR request received with accept-mediator option set to false. Address is: "+addr);
							reply = new JICPPacket(JICPProtocol.NOT_AUTHORIZED_ERROR, null);
						}
						break;
						
					case JICPProtocol.CONNECT_MEDIATOR_TYPE:
						if (acceptMediators) {
							// A mediated container is (re)connecting to its mediator
							recipientID = pkt.getRecipientID();
							
							// FIXME: If there is a PDPContextManager  check that the recipientID is the MSISDN
							
							if(myLogger.isLoggable(Logger.INFO))
								myLogger.log(Logger.INFO,"Received a CONNECT_MEDIATOR request from "+addr+":"+port+". Mediator ID is "+recipientID);
							JICPMediator m = (JICPMediator) mediators.get(recipientID);
							if (m != null) {
								// Don't close the connection, but pass it to the proper 
								// mediator. 
								closeConnection = !m.handleIncomingConnection(c, pkt, addr, port);
								reply = new JICPPacket(JICPProtocol.RESPONSE_TYPE, JICPProtocol.DEFAULT_INFO, addr.getHostAddress().getBytes());
							}
							else {
								if(myLogger.isLoggable(Logger.INFO))
									myLogger.log(Logger.INFO,"Mediator "+recipientID+" not found");
								reply = new JICPPacket(JICPProtocol.NOT_FOUND_ERROR, null);
							}
						}
						else {
							myLogger.log(Logger.WARNING,"CONNECT_MEDIATOR request received with accept-mediator option set to false. Address is: "+addr);
							reply = new JICPPacket(JICPProtocol.NOT_AUTHORIZED_ERROR, null);
						}
						break;
						//#J2ME_EXCLUDE_END
						
					default:
						// Send back an error response
						if(myLogger.isLoggable(Logger.WARNING))
							myLogger.log(Logger.WARNING,"Uncorrect JICP data type: "+pkt.getType());
					reply = new JICPPacket("Uncorrect JICP data type: "+pkt.getType(), null);
					}
					status = REQUEST_SERVED;
					
					// Send the actual response data
					if (reply != null) {
						//reply.writeTo(out);
						c.writePacket(reply);
					}
					status = RESPONSE_SENT;
				} while (loop); 
			} 
			catch (Exception e) {
				switch (status) {
				case INIT:{
					if(myLogger.isLoggable(Logger.SEVERE))
						myLogger.log(Logger.SEVERE,"Communication error reading incoming packet from "+addr+":"+port);
					e.printStackTrace();
				}
				break;
				case REQUEST_READ:
					if(myLogger.isLoggable(Logger.SEVERE))
						myLogger.log(Logger.SEVERE,"Error handling incoming packet");
					e.printStackTrace();
					// If the incoming packet was a command, try 
					// to send back a generic error response
					if (type == JICPProtocol.COMMAND_TYPE && c != null) {
						try {
							c.writePacket(new JICPPacket("Unexpected error", e));
						} 
						catch (IOException ioe) {   
							// Just print a warning
							if(myLogger.isLoggable(Logger.WARNING))
								myLogger.log(Logger.WARNING,"Can't send back error indication "+ioe);
						} 
					}
					break;
				case REQUEST_SERVED:
					if(myLogger.isLoggable(Logger.SEVERE))
						myLogger.log(Logger.SEVERE,"Communication error writing return packet to "+addr+":"+port+" ["+e.toString()+"]");
					break;
				case RESPONSE_SENT:
					// This is a re-used connection waiting for the next incoming packet
					if (e instanceof EOFException) {
						if(myLogger.isLoggable(Logger.FINE))
							myLogger.log(Logger.FINE,"Client "+addr+":"+port+" has closed the connection.");
					}
					else {
						if(myLogger.isLoggable(Logger.FINE))
							myLogger.log(Logger.FINE,"Unexpected client "+addr+":"+port+" termination. "+e.toString());
					}
				}
			} 
			finally {
				try {
					if (closeConnection) {
						// Close connection
						if(myLogger.isLoggable(Logger.FINEST))
							myLogger.log(Logger.FINEST,"Closing connection with "+addr+":"+port);
						
						c.close();
					} 
				} 
				catch (IOException io) {
					if(myLogger.isLoggable(Logger.INFO))
						myLogger.log(Logger.INFO,"I/O error while closing the connection");
					io.printStackTrace();
				} 

				connectionHandlers.remove(this);

				if(myLogger.isLoggable(Logger.FINEST))
					myLogger.log(Logger.FINEST,"ConnectionHandler closed ("+this+")");
			} 
		} 
	} // END of inner class ConnectionHandler
	
	//#J2ME_EXCLUDE_BEGIN
	/**
	 * Called by a Mediator to notify that it is no longer active
	 */
	public void deregisterMediator(String id) {
		myLogger.log(Logger.FINE, "Deregistering mediator "+id);
		mediators.remove(id);
	} 
	
	/**
	 Called by the PDPContextManager (if any)
	 */
	public void handlePDPContextClosed(String id) {
		// FIXME: to be implemented
	}
	
	private void mergeProperties(Properties p1, Properties p2) {
		Enumeration e = p2.propertyNames();
		while (e.hasMoreElements()) {
			String key = (String) e.nextElement();
			p1.setProperty(key, p2.getProperty(key));
		}
	}
	
	private JICPMediator startMediator(String id, Properties p) throws Exception {
		String className = p.getProperty(JICPProtocol.MEDIATOR_CLASS_KEY);
		if (className != null) {
			JICPMediator m = (JICPMediator) Class.forName(className).newInstance();
			mergeProperties(p, leapProps);
			myLogger.log(Logger.FINE, "Initializing mediator "+id+" with properties "+p);
			m.init(this, id, p);
			return m;
		}
		else {
			throw new ICPException("No JICPMediator class specified.");
		}
	}
	
	private void waitABit(long t) {
		try {
			Thread.sleep(t);
		}
		catch (InterruptedException ie) {
		}
	}
	//#J2ME_EXCLUDE_END
}

