/*************************************************************************** * * * SocketProxy.java * * ------------------- * * date : 12.08.2004 * * copyright : (C) 2004-2008 Distributed and * * Mobile Systems Group * * Lehrstuhl fuer Praktische Informatik * * Universitaet Bamberg * * http://www.uni-bamberg.de/pi/ * * email : sven.kaffille@uni-bamberg.de * * karsten.loesing@uni-bamberg.de * * * * * ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * A copy of the license can be found in the license.txt file supplied * * with this software or at: http://www.gnu.org/copyleft/gpl.html * * * ***************************************************************************/ package de.uniba.wiai.lspi.chord.com.socket; import static de.uniba.wiai.lspi.util.logging.Logger.LogLevel.DEBUG; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.net.Socket; import java.net.SocketTimeoutException; import java.net.UnknownHostException; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import de.uniba.wiai.lspi.chord.com.CommunicationException; import de.uniba.wiai.lspi.chord.com.Endpoint; import de.uniba.wiai.lspi.chord.com.Entry; import de.uniba.wiai.lspi.chord.com.Node; import de.uniba.wiai.lspi.chord.com.Proxy; import de.uniba.wiai.lspi.chord.com.RefsAndEntries; import de.uniba.wiai.lspi.chord.data.ID; import de.uniba.wiai.lspi.chord.data.URL; import de.uniba.wiai.lspi.util.logging.Logger; /** * This is the implementation of {@link Proxy} for the socket protocol. This * connects to the {@link SocketEndpoint endpoint} of the node it represents by * means of <code>Sockets</code>. * * @author sven * @version 1.0.5 */ public final class SocketProxy extends Proxy implements Runnable { /** * The logger for instances of this class. */ private final static Logger logger = Logger.getLogger(SocketProxy.class); /** * Map of existing proxies. Key: {@link String}, Value: {@link SocketProxy}. * changed on 21.03.2006 by sven. See documentation of method * {@link #createProxyKey(URL, URL)} * */ private static Map<String, SocketProxy> proxies = new HashMap<String, SocketProxy>(); /** * The {@link URL}of the node that uses this proxy to connect to the node, * which is represented by this proxy. * */ private URL urlOfLocalNode = null; /** * Counter for requests that have been made by this proxy. Also required to * create unique identifiers for {@link Request requests}. */ private long requestCounter = -1; /** * The socket that provides the connection to the node that this is the * Proxy for. This is transient as a proxy can be transferred over the * network. After transfer this socket has to be restored by reconnecting to * the node. */ private transient Socket mySocket; /** * The {@link ObjectOutputStream}this Proxy writes objects to. This is * transient as a proxy can be transferred over the network. After transfer * this stream has to be restored. */ private transient ObjectOutputStream out; /** * The {@link ObjectInputStream}this Proxy reads objects from. This is * transient as a proxy can be transferred over the network. After transfer * this stream has to be restored. */ private transient ObjectInputStream in; /** * The {@link ObjectInputStream} this Proxy reads objects from. This is * transient as a proxy can be transferred over the network. After transfer * this stream has to be restored. */ private transient Map<String, Response> responses; /** * {@link Map} where threads are put in that are waiting for a repsonse. * Key: identifier of the request (same as for the response). Value: The * Thread itself. */ private transient Map<String, WaitingThread> waitingThreads; /** * This indicates that an exception occured while waiting for responses and * that the connection to the {@link Node node}, that this is the proxy * for, could not be reestablished. */ private volatile boolean disconnected = false; /** * Establishes a connection from <code>urlOfLocalNode</code> to * <code>url</code>. The connection is represented by the returned * <code>SocketProxy</code>. * * @param url * The {@link URL} to connect to. * @param urlOfLocalNode * {@link URL} of local node that establishes the connection. * @return <code>SocketProxy</code> representing the established * connection. * @throws CommunicationException * Thrown if establishment of connection to <code>url</code> * failed. */ public static SocketProxy create(URL urlOfLocalNode, URL url) throws CommunicationException { synchronized (proxies) { /* * added on 21.03.2006 by sven. See documentation of method * createProxyKey(URL, URL); */ String proxyKey = SocketProxy.createProxyKey(urlOfLocalNode, url); logger.debug("Known proxies " + SocketProxy.proxies.keySet()); if (proxies.containsKey(proxyKey)) { logger.debug("Returning existing proxy for " + url); return proxies.get(proxyKey); } else { logger.debug("Creating new proxy for " + url); SocketProxy newProxy = new SocketProxy(url, urlOfLocalNode); proxies.put(proxyKey, newProxy); return newProxy; } } } /** * Closes all outgoing connections to other peers. Allows the local peer to * shutdown cleanly. * */ static void shutDownAll() { Set<String> keys = proxies.keySet(); for (String key : keys) { proxies.get(key).disconnect(); } proxies.clear(); } /** * Creates a <code>SocketProxy</code> representing the connection from * <code>urlOfLocalNode</code> to <code>url</code>. The connection is * established when the first (remote) invocation with help of the * <code>SocketProxy</code> occurs. * * @param url * The {@link URL} of the remote node. * @param urlOfLocalNode * The {@link URL} of local node. * @param nodeID * The {@link ID} of the remote node. * @return SocketProxy */ protected static SocketProxy create(URL url, URL urlOfLocalNode, ID nodeID) { synchronized (proxies) { /* * added on 21.03.2006 by sven. See documentation of method * createProxyKey(String, String); */ String proxyKey = SocketProxy.createProxyKey(urlOfLocalNode, url); logger.debug("Known proxies " + SocketProxy.proxies.keySet()); if (proxies.containsKey(proxyKey)) { logger.debug("Returning existing proxy for " + url); return proxies.get(proxyKey); } else { logger.debug("Creating new proxy for " + url); SocketProxy proxy = new SocketProxy(url, urlOfLocalNode, nodeID); proxies.put(proxyKey, proxy); return proxy; } } } /** * Method that creates a unique key for a SocketProxy to be stored in * {@link #proxies}. * * This is important for the methods {@link #create(URL, URL)}, * {@link #create(URL, URL, ID)}, and {@link #disconnect()}, so that * socket communication also works when it is used within one JVM. * * Added by sven 21.03.2006, as before SocketProxy were stored in * {@link #proxies} with help of their remote URL as key, so that they were * a kind of singleton for that URL. But the key has to consist of the URL * of the local peer, that uses the proxy, and the remote URL as * SocketProxies must only be (kind of) a singleton per local and remote * URL. * * @param localURL * @param remoteURL * @return The key to store the SocketProxy */ private static String createProxyKey(URL localURL, URL remoteURL) { return localURL.toString() + "->" + remoteURL.toString(); } /** * Corresponding constructor to factory method {@link #create(URL, URL, ID)}. * * @see #create(URL, URL, ID) * @param url * @param urlOfLocalNode1 * @param nodeID1 */ protected SocketProxy(URL url, URL urlOfLocalNode1, ID nodeID1) { super(url); if (url == null || urlOfLocalNode1 == null || nodeID1 == null) { throw new IllegalArgumentException("null"); } this.urlOfLocalNode = urlOfLocalNode1; this.nodeID = nodeID1; } /** * Corresponding constructor to factory method {@link #create(URL, URL)}. * * @see #create(URL, URL) * @param url * @param urlOfLocalNode1 * @throws CommunicationException */ private SocketProxy(URL url, URL urlOfLocalNode1) throws CommunicationException { super(url); if (url == null || urlOfLocalNode1 == null) { throw new IllegalArgumentException( "URLs must not be null!"); } this.urlOfLocalNode = urlOfLocalNode1; this.initializeNodeID(); logger.info("SocketProxy for " + url + " has been created."); } /** * Private method to send requests over the socket. This method is * synchronized to ensure that no other thread concurrently accesses the * {@link ObjectOutputStream output stream}<code>out</code> while sending * {@link Request request}. * * @param request * The {@link Request}to be sent. * @throws CommunicationException * while writing to {@link ObjectOutputStream output stream}. */ private synchronized void send(Request request) throws CommunicationException { try { logger.debug("Sending request " + request.getReplyWith()); this.out.writeObject(request); this.out.flush(); this.out.reset(); } catch (IOException e) { throw new CommunicationException("Could not connect to node " + this.nodeURL, e); } } /** * Private method to create an identifier that enables this to associate a * {@link Response response}with a {@link Request request}made before. * This method is synchronized to protect {@link #requestCounter}from race * conditions. * * @param methodIdentifier * Integer identifying the method this method is called from. * @return Unique Identifier for the request. */ private synchronized String createIdentifier(int methodIdentifier) { /* Create unique identifier from */ StringBuilder uid = new StringBuilder(); /* Time stamp */ uid.append(System.currentTimeMillis()); uid.append("-"); /* counter and */ uid.append(this.requestCounter++); /* methodIdentifier */ uid.append("-"); uid.append(methodIdentifier); return uid.toString(); } /** * Called in a method that is delegated to the {@link Node node}, that this * is the proxy for. This method blocks the thread that calls the particular * method until a {@link Response response} is received. * * @param request * @return The {@link Response} for <code>request</code>. * @throws CommunicationException */ private Response waitForResponse(Request request) throws CommunicationException { String responseIdentifier = request.getReplyWith(); Response response = null; logger.debug("Trying to wait for response with identifier " + responseIdentifier + " for method " + MethodConstants.getMethodName(request.getRequestType())); synchronized (this.responses) { logger.debug("No of responses " + this.responses.size()); /* Test if we got disconnected while waiting for lock on object */ if (this.disconnected) { throw new CommunicationException("Connection to remote host " + " is broken down. "); } /* * Test if response is already available (Maybe response arrived * before we reached this point). */ response = this.responses.remove(responseIdentifier); if (response != null) { return response; } /* WAIT FOR RESPONSE */ /* add current thread to map of threads waiting for a response */ WaitingThread wt = new WaitingThread(Thread.currentThread()); this.waitingThreads.put(responseIdentifier, wt); while (!wt.hasBeenWokenUp()) { try { /* * Wait until notified or time out is reached. */ logger.debug("Waiting for response to arrive."); this.responses.wait(); } catch (InterruptedException e) { /* * does not matter as this is intended Thread is interrupted * if response arrives */ } } logger.debug("Have been woken up from waiting for response."); /* remove thread from map of threads waiting for a response */ this.waitingThreads.remove(responseIdentifier); /* try to get the response if available */ response = this.responses.remove(responseIdentifier); logger.debug("Response for request with identifier " + responseIdentifier + " for method " + MethodConstants.getMethodName(request.getRequestType()) + " received."); /* if no response availabe */ if (response == null) { logger.debug("No response received."); /* we have been disconnected */ if (this.disconnected) { logger.info("Connection to remote host lost."); throw new CommunicationException( "Connection to remote host " + " is broken down. "); } /* or time out has elapsed */ else { logger.error("There is no result, but we have not been " + "disconnected. Something went seriously wrong!"); throw new CommunicationException( "Did not receive a response!"); } } } return response; } /** * This method is called by {@link #run()}when it receives a * {@link Response}. The {@link Thread thread}waiting for response is * woken up and the response is put into {@link Map responses}. * * @param response */ private void responseReceived(Response response) { synchronized (this.responses) { /* Try to fetch thread waiting for this response */ logger.debug("No of waiting threads " + this.waitingThreads); WaitingThread waitingThread = this.waitingThreads.get(response .getInReplyTo()); logger.debug("Response with id " + response.getInReplyTo() + "received."); /* save response */ this.responses.put(response.getInReplyTo(), response); /* if there is a thread waiting for this response */ if (waitingThread != null) { /* wake up the thread */ logger.debug("Waking up thread!"); waitingThread.wakeUp(); } else { // TODO what else? why 'else' anyway? } } } /** * Method to indicate that connection to remote {@link Node node} is broken * down. */ private void connectionBrokenDown() { if (this.responses == null) { /* * Nothing to do! */ return; } /* synchronize on responses, as all threads accessing this proxy do so */ synchronized (this.responses) { logger.info("Connection broken down!"); this.disconnected = true; /* wake up all threads */ for (WaitingThread thread : this.waitingThreads.values()) { logger.debug("Interrupting waiting thread " + thread); thread.wakeUp(); } } } /** * Creates a request for the method identified by * <code>methodIdentifier</code> with the parameters * <code>parameters</code>. Sets also field * {@link Request#getReplyWith()}of created {@link Request request}. * * @param methodIdentifier * The identifier of the method to request. * @param parameters * The parameters for the request. * @return The {@link Request request}created. */ private Request createRequest(int methodIdentifier, Serializable[] parameters) { if (logger.isEnabledFor(DEBUG)) { logger.debug("Creating request for method " + MethodConstants.getMethodName(methodIdentifier) + " with parameters " + java.util.Arrays.deepToString(parameters)); } String responseIdentifier = this.createIdentifier(methodIdentifier); Request request = new Request(methodIdentifier, responseIdentifier); request.setParameters(parameters); logger.debug("Request " + request + " created."); return request; } /** * @param key * @return The successor of <code>key</code>. * @throws CommunicationException */ public Node findSuccessor(ID key) throws CommunicationException { this.makeSocketAvailable(); logger.debug("Trying to find successor for ID " + key); /* prepare request for method findSuccessor */ Request request = this.createRequest(MethodConstants.FIND_SUCCESSOR, new Serializable[] { key }); /* send request */ try { logger.debug("Trying to send request " + request); this.send(request); } catch (CommunicationException ce) { logger.debug("Connection failed!"); throw ce; } /* wait for response */ logger.debug("Waiting for response for request " + request); Response response = this.waitForResponse(request); logger.debug("Response " + response + " arrived."); if (response.isFailureResponse()) { throw new CommunicationException(response.getFailureReason()); } else { try { RemoteNodeInfo nodeInfo = (RemoteNodeInfo) response.getResult(); if (nodeInfo.getNodeURL().equals(this.urlOfLocalNode)) { return Endpoint.getEndpoint(this.urlOfLocalNode).getNode(); } else { return create(nodeInfo.getNodeURL(), this.urlOfLocalNode, nodeInfo.getNodeID()); } } catch (ClassCastException e) { /* * This should not occur as all nodes should have the same * classes! */ String message = "Could not understand result! " + response.getResult(); logger.fatal(message); throw new CommunicationException(message, e); } } } /** * @return The id of the node represented by this proxy. * @throws CommunicationException */ private void initializeNodeID() throws CommunicationException { if (this.nodeID == null) { this.makeSocketAvailable(); logger.debug("Trying to get node ID "); /* prepare request for method findSuccessor */ Request request = this.createRequest(MethodConstants.GET_NODE_ID, new Serializable[0]); /* send request */ try { logger.debug("Trying to send request " + request); this.send(request); } catch (CommunicationException ce) { logger.debug("Connection failed!"); throw ce; } /* wait for response */ logger.debug("Waiting for response for request " + request); Response response = this.waitForResponse(request); logger.debug("Response " + response + " arrived."); if (response.isFailureResponse()) { throw new CommunicationException(response.getFailureReason()); } else { try { this.nodeID = (ID) response.getResult(); } catch (ClassCastException e) { /* * This should not occur as all nodes should have the same * classes! */ String message = "Could not understand result! " + response.getResult(); logger.fatal(message); throw new CommunicationException(message); } } } } /** * @param potentialPredecessor * @return List of references for the node invoking this method. See * {@link Node#notify(Node)}. */ public List<Node> notify(Node potentialPredecessor) throws CommunicationException { this.makeSocketAvailable(); RemoteNodeInfo nodeInfoToSend = new RemoteNodeInfo(potentialPredecessor .getNodeURL(), potentialPredecessor.getNodeID()); Request request = this.createRequest(MethodConstants.NOTIFY, new Serializable[] { nodeInfoToSend }); /* send request to remote node. */ try { this.send(request); } catch (CommunicationException e) { throw e; } /* wait for response to arrive */ Response response = this.waitForResponse(request); if (response.isFailureResponse()) { throw new CommunicationException(response.getFailureReason()); } else { try { List<RemoteNodeInfo> references = (List<RemoteNodeInfo>) response .getResult(); List<Node> nodes = new LinkedList<Node>(); for (RemoteNodeInfo nodeInfo : references) { if (nodeInfo.getNodeURL().equals(this.urlOfLocalNode)) { nodes.add(Endpoint.getEndpoint(this.urlOfLocalNode) .getNode()); } else { nodes.add(create(nodeInfo.getNodeURL(), this.urlOfLocalNode, nodeInfo.getNodeID())); } } return nodes; } catch (ClassCastException cce) { throw new CommunicationException( "Could not understand result! " + response.getResult(), cce); } } } /** * @throws CommunicationException */ public void ping() throws CommunicationException { this.makeSocketAvailable(); boolean debugEnabled = SocketProxy.logger.isEnabledFor(DEBUG); if (debugEnabled) { logger.debug("Trying to ping remote node " + this.nodeURL); } /* prepare request for method findSuccessor */ Request request = this.createRequest(MethodConstants.PING, new Serializable[0]); /* send request */ try { logger.debug("Trying to send request " + request); this.send(request); } catch (CommunicationException ce) { logger.debug("Connection failed!"); throw ce; } /* wait for response */ if (debugEnabled) { logger.debug("Waiting for response for request " + request); } Response response = this.waitForResponse(request); if (debugEnabled) { logger.debug("Response " + response + " arrived."); } if (response.isFailureResponse()) { throw new CommunicationException(response.getFailureReason()); } else { return; } } /** * @param entry * @throws CommunicationException */ public void insertEntry(Entry entry) throws CommunicationException { this.makeSocketAvailable(); logger.debug("Trying to insert entry " + entry + "."); /* prepare request for method insertEntry */ Request request = this.createRequest(MethodConstants.INSERT_ENTRY, new Serializable[] { entry }); /* send request */ try { logger.debug("Trying to send request " + request); this.send(request); } catch (CommunicationException ce) { logger.debug("Connection failed!"); throw ce; } /* wait for response */ logger.debug("Waiting for response for request " + request); Response response = this.waitForResponse(request); logger.debug("Response " + response + " arrived."); if (response.isFailureResponse()) { throw new CommunicationException(response.getFailureReason()); } else { /* No result here */ return; } } /** * @param replicas * @throws CommunicationException */ public void insertReplicas(Set<Entry> replicas) throws CommunicationException { this.makeSocketAvailable(); logger.debug("Trying to insert replicas " + replicas + "."); /* prepare request for method insertEntry */ Request request = this.createRequest(MethodConstants.INSERT_REPLICAS, new Serializable[] { (Serializable) replicas }); /* send request */ try { logger.debug("Trying to send request " + request); this.send(request); } catch (CommunicationException ce) { logger.debug("Connection failed!"); throw ce; } /* wait for response */ logger.debug("Waiting for response for request " + request); Response response = this.waitForResponse(request); logger.debug("Response " + response + " arrived."); if (response.isFailureResponse()) { throw new CommunicationException(response.getFailureReason()); } else { /* No result here */ return; } } /** * @param predecessor * @throws CommunicationException */ public void leavesNetwork(Node predecessor) throws CommunicationException { this.makeSocketAvailable(); logger.debug("Trying to insert notify node that " + predecessor + " leaves network."); RemoteNodeInfo nodeInfo = new RemoteNodeInfo(predecessor.getNodeURL(), predecessor.getNodeID()); /* prepare request for method insertEntry */ Request request = this.createRequest(MethodConstants.LEAVES_NETWORK, new Serializable[] { nodeInfo }); /* send request */ try { logger.debug("Trying to send request " + request); this.send(request); } catch (CommunicationException ce) { logger.debug("Connection failed!"); throw ce; } /* wait for response */ logger.debug("Waiting for response for request " + request); Response response = this.waitForResponse(request); logger.debug("Response " + response + " arrived."); if (response.isFailureResponse()) { throw new CommunicationException(response.getFailureReason()); } else { /* No result here */ return; } } /** * @param entry * @throws CommunicationException */ public void removeEntry(Entry entry) throws CommunicationException { this.makeSocketAvailable(); logger.debug("Trying to remove entry " + entry + "."); /* prepare request for method findSuccessor */ Request request = this.createRequest(MethodConstants.REMOVE_ENTRY, new Serializable[] { entry }); /* send request */ try { logger.debug("Trying to send request " + request); this.send(request); } catch (CommunicationException ce) { logger.debug("Connection failed!"); throw ce; } /* wait for response */ logger.debug("Waiting for response for request " + request); Response response = this.waitForResponse(request); logger.debug("Response " + response + " arrived."); if (response.isFailureResponse()) { throw new CommunicationException(response.getFailureReason()); } else { /* No result here */ return; } } /** * @param sendingNodeID * @param replicas * @throws CommunicationException */ public void removeReplicas(ID sendingNodeID, Set<Entry> replicas) throws CommunicationException { this.makeSocketAvailable(); logger.debug("Trying to remove replicas " + replicas + "."); /* prepare request for method insertEntry */ Request request = this.createRequest(MethodConstants.REMOVE_REPLICAS, new Serializable[] { sendingNodeID, (Serializable) replicas }); /* send request */ try { logger.debug("Trying to send request " + request); this.send(request); } catch (CommunicationException ce) { logger.debug("Connection failed!"); throw ce; } /* wait for response */ logger.debug("Waiting for response for request " + request); Response response = this.waitForResponse(request); logger.debug("Response " + response + " arrived."); if (response.isFailureResponse()) { throw new CommunicationException(response.getFailureReason()); } else { /* No result here */ return; } } public Set<Entry> retrieveEntries(ID id) throws CommunicationException { this.makeSocketAvailable(); logger.debug("Trying to retrieve entries for ID " + id); /* prepare request for method findSuccessor */ Request request = this.createRequest(MethodConstants.RETRIEVE_ENTRIES, new Serializable[] { id }); /* send request */ try { logger.debug("Trying to send request " + request); this.send(request); } catch (CommunicationException ce) { logger.debug("Connection failed!"); throw ce; } /* wait for response */ logger.debug("Waiting for response for request " + request); Response response = this.waitForResponse(request); logger.debug("Response " + response + " arrived."); if (response.isFailureResponse()) { throw new CommunicationException(response.getFailureReason(), response.getThrowable()); } else { try { Set<Entry> result = (Set<Entry>) response.getResult(); return result; } catch (ClassCastException cce) { throw new CommunicationException( "Could not understand result! " + response.getResult()); } } } /** * This method has to be called at first in every method that uses the * socket to connect to the node this is the proxy for. This method * establishes the connection if not already done. This method has to be * called as this proxy can be serialized and the reference to the socket is * transient. So by calling this method after a transfer the connection to * the node is reestablished. The same applies for {@link #logger}and * {@link #responses}. * * @throws CommunicationException */ private void makeSocketAvailable() throws CommunicationException { if (this.disconnected) { throw new CommunicationException("Connection from " + this.urlOfLocalNode + " to remote host " + this.nodeURL + " is broken down. "); } logger.debug("makeSocketAvailable() called. " + "Testing for socket availability"); if (this.responses == null) { this.responses = new HashMap<String, Response>(); } if (this.waitingThreads == null) { this.waitingThreads = new HashMap<String, WaitingThread>(); } if (this.mySocket == null) { try { logger.info("Opening new socket to " + this.nodeURL); this.mySocket = new Socket(this.nodeURL.getHost(), this.nodeURL .getPort()); logger.debug("Socket created: " + this.mySocket); this.mySocket.setSoTimeout(5000); this.out = new ObjectOutputStream(this.mySocket .getOutputStream()); this.in = new ObjectInputStream(this.mySocket.getInputStream()); logger.debug("Sending connection request!"); out.writeObject(new Request(MethodConstants.CONNECT, "Initial Connection")); try { // set time out, in case the other side does not answer! Response resp = null; boolean timedOut = false; try { logger.debug("Waiting for connection response!"); resp = (Response) in.readObject(); } catch (SocketTimeoutException e) { logger.info("Connection timed out!"); timedOut = true; } this.mySocket.setSoTimeout(0); if (timedOut) { throw new CommunicationException( "Connection to remote host timed out!"); } if (resp != null && resp.getStatus() == Response.REQUEST_SUCCESSFUL) { Thread t = new Thread(this, "SocketProxy_Thread_" + this.nodeURL); t.start(); } else { throw new CommunicationException( "Establishing connection failed!"); } } catch (ClassNotFoundException e) { throw new CommunicationException( "Unexpected result received! " + e.getMessage(), e); } catch (ClassCastException e) { throw new CommunicationException( "Unexpected result received! " + e.getMessage(), e); } } catch (UnknownHostException e) { throw new CommunicationException("Unknown host: " + this.nodeURL.getHost()); } catch (IOException ioe) { throw new CommunicationException("Could not set up IO channel " + "to host " + this.nodeURL.getHost(), ioe); } } logger.debug("makeSocketAvailable() finished. Socket " + this.mySocket); } /** * Finalization ensures that the socket is closed if this proxy is not * needed anymore. * * @throws Throwable */ protected void finalize() throws Throwable { logger.debug("Finalization running."); } /** * Tells this proxy that it is not needed anymore. */ public void disconnect() { logger.info("Destroying connection from " + this.urlOfLocalNode + " to " + this.nodeURL); synchronized (proxies) { /* * added on 21.03.2006 by sven. See documentation of method * createProxyKey(String, String); */ String proxyKey = SocketProxy.createProxyKey(this.urlOfLocalNode, this.nodeURL); Object o = proxies.remove(proxyKey); } this.disconnected = true; try { if (this.out != null) { try { /* * notify endpoint this is connected to, about shut down of * this proxy */ logger.debug("Sending shutdown notification to endpoint."); Request request = this.createRequest( MethodConstants.SHUTDOWN, new Serializable[0]); logger.debug("Notification send."); this.out.writeObject(request); this.out.close(); this.out = null; logger.debug("OutputStream " + this.out + " closed."); } catch (IOException e) { /* should not occur */ logger.debug(this + ": Exception during closing of output stream " + this.out, e); } } if (this.in != null) { try { this.in.close(); logger.debug("InputStream " + this.in + " closed."); this.in = null; } catch (IOException e) { /* should not occur */ logger.debug("Exception during closing of input stream" + this.in); } } if (this.mySocket != null) { try { logger.info("Closing socket " + this.mySocket + "."); this.mySocket.close(); } catch (IOException e) { /* should not occur */ logger.debug("Exception during closing of socket " + this.mySocket); } this.mySocket = null; } } catch (Throwable t) { logger.warn("Unexpected exception during disconnection of SocketProxy", t); } this.connectionBrokenDown(); } /** * The run methods waits for incoming * {@link de.uniba.wiai.lspi.chord.com.socket.Response} made by this proxy * and puts them into a datastructure from where the can be collected by the * associated method call that made a * {@link de.uniba.wiai.lspi.chord.com.socket.Request} to the {@link Node}, * that this is the proxy for. */ public void run() { while (!this.disconnected) { try { Response response = (Response) this.in.readObject(); logger.debug("Response " + response + "received!"); this.responseReceived(response); } catch (ClassNotFoundException cnfe) { /* should not occur, as all classes must be locally available */ logger .fatal( "ClassNotFoundException occured during deserialization " + "of response. There is something seriously wrong " + " here! ", cnfe); } catch (IOException e) { if (!this.disconnected) { logger.warn("Could not read response from stream!", e); } else { logger.debug(this + ": Connection has been closed!"); } this.connectionBrokenDown(); } } } /** * @param potentialPredecessor * @return See {@link Node#notifyAndCopyEntries(Node)}. * @throws CommunicationException */ public RefsAndEntries notifyAndCopyEntries(Node potentialPredecessor) throws CommunicationException { this.makeSocketAvailable(); RemoteNodeInfo nodeInfoToSend = new RemoteNodeInfo(potentialPredecessor .getNodeURL(), potentialPredecessor.getNodeID()); /* prepare request for method notifyAndCopyEntries */ Request request = this.createRequest(MethodConstants.NOTIFY_AND_COPY, new Serializable[] { nodeInfoToSend }); /* send request */ try { logger.debug("Trying to send request " + request); this.send(request); } catch (CommunicationException ce) { logger.debug("Connection failed!"); throw ce; } /* wait for response */ logger.debug("Waiting for response for request " + request); Response response = this.waitForResponse(request); logger.debug("Response " + response + " arrived."); if (response.isFailureResponse()) { throw new CommunicationException(response.getFailureReason(), response.getThrowable()); } else { try { RemoteRefsAndEntries result = (RemoteRefsAndEntries) response .getResult(); List<Node> newReferences = new LinkedList<Node>(); List<RemoteNodeInfo> references = result.getNodeInfos(); for (RemoteNodeInfo nodeInfo : references) { if (nodeInfo.getNodeURL().equals(this.urlOfLocalNode)) { newReferences.add(Endpoint.getEndpoint( this.urlOfLocalNode).getNode()); } else { newReferences.add(create(nodeInfo.getNodeURL(), this.urlOfLocalNode, nodeInfo.getNodeID())); } } return new RefsAndEntries(newReferences, result.getEntries()); } catch (ClassCastException cce) { throw new CommunicationException( "Could not understand result! " + response.getResult()); } } } /** * The string representation of this proxy. Created when {@link #toString()} * is invoked for the first time. */ private String stringRepresentation = null; /** * @return String representation of this. */ public String toString() { if (this.nodeID == null || this.mySocket == null) { return "Unconnected SocketProxy from " + this.urlOfLocalNode + " to " + this.nodeURL; } if (this.stringRepresentation == null) { StringBuilder builder = new StringBuilder(); builder.append("Connection from Node[url="); builder.append(this.urlOfLocalNode); builder.append(", socket="); builder.append(this.mySocket); builder.append("] to Node[id="); builder.append(this.nodeID); builder.append(", url="); builder.append(this.nodeURL); builder.append("]"); this.stringRepresentation = builder.toString(); } return this.stringRepresentation; } /** * Wraps a thread, which is waiting for a response. * * @author sven * */ private static class WaitingThread { private boolean hasBeenWokenUp = false; private Thread thread; private WaitingThread(Thread thread) { this.thread = thread; } /** * Returns <code>true</code> when the thread has been woken up by * invoking {@link #wakeUp()} * * @return */ boolean hasBeenWokenUp() { return this.hasBeenWokenUp; } /** * Wake up the thread that is waiting for a response. * */ void wakeUp() { this.hasBeenWokenUp = true; this.thread.interrupt(); } public String toString() { return this.thread.toString() + ": Waiting? " + !this.hasBeenWokenUp(); } } }