package hep.io.root.daemon.xrootd;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.StandardMBean;
import hep.io.root.daemon.xrootd.LoginOperation.LoginSession;
import java.util.ArrayList;
import java.util.concurrent.ScheduledThreadPoolExecutor;
/**
* Manages the creation and destruction of multiplexors.
* @author tonyj
*/
class MultiplexorManager {
private enum Stage {
CONNECT, LOGIN, AUTH
}
private static Logger logger = Logger.getLogger(MultiplexorManager.class.getName());
private static MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
private Map<Destination, Multiplexor> multiplexorMap = new HashMap<Destination, Multiplexor>();
private Map<Destination, List<MultiplexorReadyCallback>> inProgressConnections = new HashMap<Destination, List<MultiplexorReadyCallback>>();
private ScheduledThreadPoolExecutor scheduler;
MultiplexorManager(ScheduledThreadPoolExecutor scheduler) {
this.scheduler = scheduler;
scheduler.scheduleAtFixedRate(new IdleConnectionCloser(), 5, 5, TimeUnit.SECONDS);
}
private void createMultiplexor(Destination destination, int attempt) {
Destination actualDestination = destination.getAlternateDestination(attempt);
try {
Multiplexor multiplexor = new Multiplexor(actualDestination);
multiplexor.connect(new LoginResponseListener(actualDestination, attempt));
} catch (IOException x) {
connectionFailed(actualDestination,attempt,x);
}
}
private synchronized void connectionComplete(Multiplexor multiplexor) {
multiplexorMap.put(multiplexor.getDestination(), multiplexor);
registerMultiplexor(multiplexor);
List<MultiplexorReadyCallback> callbacks = inProgressConnections.get(multiplexor.getDestination());
for (MultiplexorReadyCallback callback : callbacks) {
callback.multiplexorReady(multiplexor);
}
inProgressConnections.remove(multiplexor.getDestination());
}
private synchronized void connectionFailed(Destination destination, int attempt, IOException iOException) {
logger.log(Level.WARNING, String.format("Connection to %s failed (attempt %d) ",destination,attempt), iOException);
scheduler.schedule(new Reconnect(destination,attempt+1), 2, TimeUnit.SECONDS);
}
/** If a multiplexor is ready to be used for the given destination, return it immediately
* otherwise return <code>void</code> and call the given callback when the connection
* becomes ready.
*/
synchronized Multiplexor getMultiplexor(Destination destination, MultiplexorReadyCallback callback) {
Multiplexor result = multiplexorMap.get(destination);
if (result != null && result.isSocketClosed()) {
multiplexorMap.remove(result);
unregisterMultiplexor(result);
result = null;
}
if (result == null) {
List<MultiplexorReadyCallback> callbacks = inProgressConnections.get(destination);
if (callbacks == null) {
callbacks = new ArrayList<MultiplexorReadyCallback>();
inProgressConnections.put(destination, callbacks);
callbacks.add(callback);
createMultiplexor(destination,0);
} else {
callbacks.add(callback);
}
}
return result;
}
private ObjectName getObjectNameForMultiplexor(Multiplexor m) throws MalformedObjectNameException {
return new ObjectName("hep.io.root.daemon.xrootd:type=Multiplexor,name=" + m.toString().replace(":", ";"));
}
private void registerMultiplexor(Multiplexor result) {
try {
mbs.registerMBean(new StandardMBean(result, MultiplexorMBean.class), getObjectNameForMultiplexor(result));
} catch (Exception x) {
logger.log(Level.WARNING, "Could not register multiplexor mbean", x);
}
}
private void unregisterMultiplexor(Multiplexor m) {
try {
mbs.unregisterMBean(getObjectNameForMultiplexor(m));
} catch (Exception x) {
logger.log(Level.WARNING, "Could not unregister multiplexor mbean", x);
}
}
static interface MultiplexorReadyCallback {
void multiplexorReady(Multiplexor multiplexor);
}
private class LoginResponseListener implements ResponseListener {
private Destination destination;
private LoginOperation login;
private AuthOperation auth;
private Stage stage = Stage.CONNECT;
private int attempt;
LoginResponseListener(Destination destination, int attempt) {
this.attempt = attempt;
this.destination = destination;
login = new LoginOperation(destination.getUserName());
}
public void reschedule(long seconds, TimeUnit SECONDS) {
throw new UnsupportedOperationException("Not supported during login.");
}
public void handleError(IOException iOException) {
throw new UnsupportedOperationException("Not supported during login.");
}
public void handleRedirect(String host, int port) throws UnknownHostException {
throw new UnsupportedOperationException("Not supported during login.");
}
public void handleResponse(Response response) throws IOException {
switch (stage) {
case CONNECT:
response.getMultiplexor().handleInitialHandshakeResponse(response);
stage = Stage.LOGIN;
response.getMultiplexor().sendMessage(login.getMessage(), this);
break;
case LOGIN:
LoginSession session = login.getCallback().responseReady(response);
if (session.getSecurity() != null) {
stage = Stage.AUTH;
auth = new AuthOperation();
response.getMultiplexor().sendMessage(auth.getMessage(), this);
} else {
connectionComplete(response.getMultiplexor());
}
break;
case AUTH:
auth.getCallback().responseReady(response);
connectionComplete(response.getMultiplexor());
}
}
public void handleSocketError(IOException iOException) {
connectionFailed(destination,attempt,iOException);
}
}
private class Reconnect implements Runnable {
private Destination destination;
private int attempt;
public Reconnect(Destination destination, int attempt) {
this.destination = destination;
this.attempt = attempt;
}
public void run() {
createMultiplexor(destination, attempt);
}
}
private class IdleConnectionCloser implements Runnable {
public void run() {
for (Iterator<Map.Entry<Destination, Multiplexor>> i = multiplexorMap.entrySet().iterator(); i.hasNext();) {
Map.Entry<Destination, Multiplexor> entry = i.next();
Multiplexor m = entry.getValue();
if (m.isIdle()) {
i.remove();
unregisterMultiplexor(m);
m.close();
logger.log(Level.FINE, "Closed idle connection: " + m);
}
}
}
}
}