/*****************************************************************
* JADE - Java Agent DEvelopment Framework is a framework to develop
* multi-agent systems in compliance with the FIPA specifications.
* Copyright (C) 2000 CSELT S.p.A.
*
* The updating of this file to JADE 2.0 has been partially supported by the
* IST-1999-10211 LEAP Project
*
* This file refers to parts of the FIPA 99/00 Agent Message Transport
* Implementation Copyright (C) 2000, Laboratoire d'Intelligence
* Artificielle, Ecole Polytechnique Federale de Lausanne
*
* GNU Lesser General Public License
*
* This library is free software; you can redistribute it sand/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.
*****************************************************************/
/**
* KeepAlive.java
*
* @author Jose Antonio Exposito
* @author MARISM-A Development group ( marisma-info@ccd.uab.es )
* @version 0.1
* @author Nicolas Lhuillier (Motorola Labs)
* @version 1.0
*/
package jade.mtp.http;
import jade.mtp.MTPException;
import jade.util.leap.HashMap;
import jade.util.Logger;
import java.io.*;
import java.util.Vector;
import java.net.Socket;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* This class represents a connection to a remote server
*/
public class KeepAlive {
private static Logger logger = Logger.getMyLogger(KeepAlive.class.getName());
/*
* Inner structure to contain all connection information
*/
public static class KAConnection {
private OutputStream out;
private InputStream in;
private HTTPAddress address;
private Vector connections;
private int outPort;
KAConnection(HTTPAddress a, int outPort) {
address = a;
this.outPort = outPort;
}
public void open() throws IOException {
// The address is new or the KA connection was closed
//HTTPAddress is new or cached missed;
// Open a new connection
Socket client;
//#PJAVA_EXCLUDE_BEGIN
HTTPSocketFactory sfac = HTTPSocketFactory.getInstance();
if (outPort > 0) {
client = sfac.createSocket(address.getHost(),address.getPortNo(),InetAddress.getLocalHost(),outPort);
} else {
client = sfac.createSocket(address.getHost(),address.getPortNo());
}
//#PJAVA_EXCLUDE_END
/*#PJAVA_INCLUDE_BEGIN
if (outPort > 0)
{
client = new Socket(address.getHost(),address.getPortNo(),InetAddress.getLocalHost(),outPort);
}
else {
client = new Socket(address.getHost(),address.getPortNo());
}
#PJAVA_INCLUDE_END*/
out = new BufferedOutputStream(client.getOutputStream());
in = new BufferedInputStream(client.getInputStream());
}
OutputStream getOut() {
return out;
}
InputStream getIn() {
return in;
}
public HTTPAddress getAddress() {
return address;
}
public boolean equals(HTTPAddress a) {
return address.equals(a);
}
void close() {
if (isOpen()) {
try {
in.close();
out.close();
} catch(IOException ioe) {
if(logger.isLoggable(Logger.WARNING))
logger.log(Logger.WARNING,"Exception while closing KA connection: "+ioe);
}
in = null;
out = null;
}
}
boolean isOpen() {
return in != null;
}
void send(byte[] req) throws MTPException {
try {
if(logger.isLoggable(Logger.FINER))
logger.log(Logger.FINER,"Sending HTTP message to: "+ address);
HTTPIO.writeAll(out,req);
//Capture the HTTPresponse
StringBuffer typeConnection = new StringBuffer();
int code = HTTPIO.getResponseCode(in, typeConnection);
if (!HTTPIO.KA.equals(typeConnection.toString())) {
// Close the connection
if(logger.isLoggable(Logger.FINER))
logger.log(Logger.FINER,"Closing " + typeConnection +" connection to " + address);
close();
}
if (code != 200) {
if(logger.isLoggable(Logger.FINER))
logger.log(Logger.FINER,"Not OK: " + code +", Closing connection to " + address);
close();
throw new MTPException("Description: ResponseMessage is not OK");
}
} catch (IOException e) {
close();
throw new MTPException(e.getMessage(), e);
}
}
} // End of KAConnection inner class
private final Vector connections;
private final int dim;
private final int outPort;
private final boolean agressive;
private final HashMap locks = new HashMap();
/** Constructor */
public KeepAlive(int dim, int outPort, boolean agressive) {
connections = new Vector(dim);
this.dim = dim;
this.outPort = outPort;
this.agressive = agressive;
}
/** add a new connection */
public synchronized void add(KAConnection c) {
try {
//The vectors are full.
if (connections.size() == dim) {
remove(0); //Remove the first element of vectors, is the older element
}
connections.addElement(c);
//System.out.println("DEBUG: Added Ka conn: "+connections.size()+"/"+dim+" with "+c.getAddress().getPortNo());
} catch(Exception ioe) {
if(logger.isLoggable(Logger.WARNING))
logger.log(Logger.WARNING,ioe.getMessage());
}
}
/** delete an exisiting connection, based on position */
private void remove(int pos) {
try {
KAConnection old = getConnection(pos);
connections.removeElementAt(pos);
old.close();
} catch(Exception ioe) {
if(logger.isLoggable(Logger.WARNING))
logger.log(Logger.WARNING,ioe.getMessage());
}
}
/** delete an exisiting connection, based on its address */
public synchronized void remove(HTTPAddress addr) {
connections.removeElement(search(addr));
}
/** delete an exisiting connection*/
public synchronized void remove(KAConnection ka) {
connections.removeElement(ka);
}
/** get the socket of the connection when addr make matching */
private KAConnection getConnection(int pos) {
return (KAConnection)connections.elementAt(pos);
}
private KAConnection search(HTTPAddress addr) {
if (addr != null) {
KAConnection c;
for(int i=(connections.size()-1); i >= 0; i--) {
if ((c=(KAConnection)getConnection(i)).equals(addr)) {
return c;
}
}
}
return null;
}
/** get the socket of the connection when addr make matching */
public KAConnection getConnection(HTTPAddress addr) {
return search(addr);
}
private KAConnection createConnection(final HTTPAddress url) throws MTPException {
KeepAlive.KAConnection kac = null;
try {
kac = new KAConnection(url, outPort);
kac.open();
return kac;
} catch (IOException e) {
//Remove the inputs of KA object for the current address
if (kac != null) {
kac.close();
}
throw new MTPException(e.getMessage(), e);
}
}
/** get the dimension of Vectors */
public int getDim(){
return dim;
}
/** get the capacity of Vectors */
public int capacity() {
//System.out.println("DIMENSION: "+dim+" "+"TAMVECT: "+addresses.size());
return (dim - connections.size());
}
public synchronized void swap(KAConnection c) {
try {
//if only have 1 socket isn't necessary make swap function
if ((dim > 1)&&(!(connections.indexOf(c)==(connections.size()-1)))) {
//remove the elements at former position
connections.removeElement(c);
//put the elements at the end
connections.addElement(c);
}
} catch(Exception ioe) {
if(logger.isLoggable(Logger.WARNING))
logger.log(Logger.WARNING,ioe.getMessage());
}
}
public void send(HTTPAddress url, byte[] request) throws MTPException {
Object lock = getLock(url);
synchronized(lock) {
KeepAlive.KAConnection kac = null;
// Try to re-use an existing socket
if (dim > 0) {
//Search the address in Keep-Alive object
kac = getConnection(url);
if (kac != null) {
try {
if(logger.isLoggable(Logger.FINER))
logger.log(Logger.FINER,"Reusing keepAlive for " + url);
kac.send(request);
if (kac.isOpen()) {
//change the priority of socket & another components of keep-alive object
//Only the policy == AGGRESSIVE
//HTTPAddress is cached;
if (agressive) {
swap(kac);
}
} else {
if(logger.isLoggable(Logger.FINER))
logger.log(Logger.FINER,"Removing keepAlive for " + url);
remove(kac);
}
} catch (MTPException e) {
if(logger.isLoggable(Logger.FINER))
logger.log(Logger.FINER,"Removing keepAlive for " + url);
remove(kac);
// retry with new connection
kac = null;
}
}
}
if (kac == null) {
if(logger.isLoggable(Logger.FINER))
logger.log(Logger.FINER,"Creating connection to " + url);
kac = createConnection(url);
// Send out and check response code
kac.send(request);
if (kac.isOpen()) {
if (dim > 0) {
// Store the new connection
if(logger.isLoggable(Logger.FINER))
logger.log(Logger.FINER,"Adding keepAlive for " + url);
add(kac);
} else {
if(logger.isLoggable(Logger.FINER))
logger.log(Logger.FINER,"Closing open connection for " + url);
kac.close();
}
}
}
}
}
private Object getLock(final HTTPAddress url) {
Object lock = locks.get(url.getHost());
if (lock == null) {
synchronized(this) {
lock = locks.get(url.getHost());
if (lock == null) {
lock = new Object();
locks.put(url.getHost(), lock);
}
}
}
return lock;
}
} //End of class KeepAlive