/***************************************************************************
* *
* Endpoint.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;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import de.uniba.wiai.lspi.chord.com.local.ThreadEndpoint;
import de.uniba.wiai.lspi.chord.com.rmi.RMIEndpoint;
import de.uniba.wiai.lspi.chord.com.socket.SocketEndpoint;
import de.uniba.wiai.lspi.chord.data.URL;
import de.uniba.wiai.lspi.util.logging.Logger;
/**
* <p>
* This class represents an endpoint, which wraps a {@link Node}, so that other
* nodes can connect to the node using a protocol.
* </p>
* <p>
* This class must be extended by endpoints that support a certain protocol as
* e.g. <code>RMI</code> or a protocol over <code>Sockets</code>.
* </p>
* <p>
* This is the abstract class that has to be implemented by all Endpoints. An
* Endpoint enables other chord peers to connect to a {@link Node node} with
* help of a given protocol. Each node in a chord network has to have exactly
* one endpoint.
* </p>
* <p>
* For each protocol that shall be supported a separate endpoint has to be
* implemented. To initialise endpoints for a {@link Node} an {@link URL} has to
* be provided to the {@link #createEndpoint(Node, URL)} endpoint factory
* method. This methods tries to determine the endpoint with help of the
* protocol names defined by the url. Supported protocols can be found in the
* {@link URL} class.
* </p>
* An Endpoint can be in three states:
* <ul>
* <li><code>STARTED</code>,</li>
* <li><code>LISTENING</code>, and</li>
* <li><code>ACCEPT_ENTRIES</code>.</li>
* </ul>
* <p>
* In state <code>STARTED</code> the endpoint has been initialised but does
* not listen to (possibly) incoming messages from the chord network. An
* endpoint gets into this state if it is created with help of its constructor.
* <br/><br/> In state <code>LISTENING</code> the endpoint accepts messages
* that are received from the chord network to update the finger table or
* request the predecessor or successor of the node of this endpoint. The
* transition to this state is made by invocation of {@link #listen()}.
*
*
* <br/><br/>In state <code>ACCEPT_ENTRIES</code>. This endpoint accepts
* messages from the chord network, that request storage or removal of entries
* from the DHT. The transition to this state is made by invocation of
* {@link #acceptEntries()}.
* </p>
*
* @author sven
* @version 1.0.5
*/
public abstract class Endpoint {
/**
* Logger for this class.
*/
private static final Logger logger = Logger.getLogger(Endpoint.class);
/**
* Map containing all endpoints. Key: {@link URL}. Value:
* <code>Endpoint</code>.
*/
protected static final Map<URL, Endpoint> endpoints = new HashMap<URL, Endpoint>();
// TODO: Create enum for state.
/**
* Integer representation of state.
*/
public static final int STARTED = 0;
/**
* Integer representation of state.
*/
public static final int LISTENING = 1;
/**
* Integer representation of state.
*/
public static final int ACCEPT_ENTRIES = 2;
/**
* Integer representation of state.
*/
public static final int DISCONNECTED = 3;
/**
* Array containing names of methods only allowed to be invoked in state
* {@link #ACCEPT_ENTRIES}. Remember to eventually edit this array if you
* change the methods in interface {@link Node}. The method names contained
* in this array must be sorted!
*/
public static final List<String> METHODS_ALLOWED_IN_ACCEPT_ENTRIES;
static {
String[] temp = new String[] { "insertEntry", "removeEntry",
"retrieveEntries" };
Arrays.sort(temp);
List<String> list = new ArrayList<String>(Arrays.asList(temp));
METHODS_ALLOWED_IN_ACCEPT_ENTRIES = Collections.unmodifiableList(list);
}
/**
* The current state of this endpoint.
*/
private int state = -1;
/**
* The {@link URL}that can be used to connect to this endpoint.
*/
protected URL url;
/**
* The {@link Node node}on which this endpoint invokes methods.
*/
protected Node node;
/**
* {@link EndpointStateListener listeners}interested in state changes of
* this endpoint.
*/
private Set<EndpointStateListener> listeners = new HashSet<EndpointStateListener>();
/**
*
* @param node1
* The {@link Node} this is the Endpoint for.
* @param url1
* The {@link URL} that describes the location of this endpoint.
*/
protected Endpoint(Node node1, URL url1) {
logger.info("Endpoint for " + node1 + " with url " + url1 + "created.");
this.node = node1;
this.url = url1;
this.state = STARTED;
}
/**
* @return Returns the node.
*/
public final Node getNode() {
return this.node;
}
/**
* Register a listener that is notified when the state of this endpoint
* changes.
*
* @param listener
* The listener to register.
*/
public final void register(EndpointStateListener listener) {
this.listeners.add(listener);
}
/**
* Remove a listener that listened for state changes of this endpoint.
*
* @param listener
* The listener instance to be removed.
*/
public final void deregister(EndpointStateListener listener) {
this.listeners.remove(listener);
}
// TODO rename!!
/**
* Method to notify listeners about a change in state of this endpoint.
*
* @param s
* The integer identifying the state to that the endpoint
* switched. See {@link Endpoint#ACCEPT_ENTRIES},
* {@link Endpoint#DISCONNECTED}, {@link Endpoint#LISTENING},
* and {@link Endpoint#STARTED}.
*/
protected void notify(int s) {
logger.debug("notifying state change.");
synchronized (this.listeners) {
logger.debug("Size of listeners = " + this.listeners.size());
for (EndpointStateListener listener : this.listeners) {
listener.notify(s);
}
}
}
/**
* Get the {@link URL}of this endpoint.
*
* @return The {@link URL}that can be used to connect to this endpoint.
*/
public URL getURL() {
return this.url;
}
/**
* @return Returns the state.
*/
public final int getState() {
return this.state;
}
/**
* @param state1
* The state to set.
*/
protected final void setState(int state1) {
this.state = state1;
this.notify(state1);
}
/**
* Tell this endpoint that it can listen to incoming messages from other
* chord nodes. TODO: This method may throw an exception when starting to
* listen for incoming connections.
*/
public final void listen() {
this.state = LISTENING;
this.notify(this.state);
this.openConnections();
}
/**
* To implemented by sub classes. This method is called by {@link #listen()}to
* make it possible for other chord nodes to connect to the node on that
* this endpoint invocates methods.
*
* TODO: This method may throw an exception when starting to listen for
* incoming connections.
*/
protected abstract void openConnections();
/**
* Tell this endpoint that the node is now able to receive messages that
* request the storage and removal of entries.
*
*/
public final void acceptEntries() {
logger.info("acceptEntries() called.");
this.state = ACCEPT_ENTRIES;
this.notify(this.state);
this.entriesAcceptable();
}
/**
* This method has to be overwritten by subclasses. It is called from
* {@link #acceptEntries()}to indicate that entries can now be accepted. So
* maybe if an endpoint queues incoming requests for storage or removal of
* entries this requests can be answered when endpoint changes it state to
* <code>ACCEPT_ENTRIES</code>.
*/
protected abstract void entriesAcceptable();
/**
* Tell this endpoint to disconnect and close all connections. If this
* method has been invoked the endpoint must be not reused!!!
*/
public final void disconnect() {
this.state = STARTED;
logger.info("Disconnecting.");
this.notify(this.state);
this.closeConnections();
synchronized (endpoints) {
endpoints.remove(this.node.nodeURL);
}
}
/**
* This method has to be overwritten by sub classes and is invoked by
* {@link #disconnect()}to close all connections from the chord network.
*
*/
protected abstract void closeConnections();
/**
* Create the endpoints for the protocol given by <code>url</code>. An
* URL must have a known protocol. An endpoint for an {@link URL} can only
* be create once and then be obtained with help of
* {@link Endpoint#getEndpoint(URL)}. An endpoint for an url must again be
* created if the {@link Endpoint#disconnect()} has been invoked.
*
* @param node
* The node to which this endpoint delegates incoming requests.
* @param url
* The URL under which <code>node</code> will be reachable by
* other nodes.
* @return The endpoint created for <code>node</code> for the protocol
* specified in <code>url</code>.
* @throws RuntimeException
* This can occur if any error that cannot be handled by this
* method occurs during endpoint creation.
*/
public static Endpoint createEndpoint(Node node, URL url) {
synchronized (endpoints) {
if (endpoints.containsKey(url)) {
throw new RuntimeException("Endpoint already created!");
}
Endpoint endpoint = null;
// TODO irgendwann �ber properties l�sen
if (url == null) {
throw new IllegalArgumentException("Url must not be null! ");
}
if (url.getProtocol().equals(
URL.KNOWN_PROTOCOLS.get(URL.SOCKET_PROTOCOL))) {
endpoint = new SocketEndpoint(node, url);
} else if (url.getProtocol().equals(
URL.KNOWN_PROTOCOLS.get(URL.LOCAL_PROTOCOL))) {
endpoint = new ThreadEndpoint(node, url);
} else if (url.getProtocol().equals(
URL.KNOWN_PROTOCOLS.get(URL.RMI_PROTOCOL))) {
endpoint = new RMIEndpoint(node, url);
} else {
// does not happen ??
throw new IllegalArgumentException("Url does not contain a "
+ "supported protocol " + "(" + url.getProtocol()
+ ")!");
}
endpoints.put(url, endpoint);
return endpoint;
}
}
/**
* Get the <code>Endpoint</code> for the given <code>url</code>.
*
* @param url
* @return The endpoint for provided <code>url</code>.
*/
public static Endpoint getEndpoint(URL url) {
synchronized (endpoints) {
Endpoint ep = endpoints.get(url);
logger.debug("Endpoint for URL " + url + ": " + ep);
return ep;
}
}
/**
* Overwritten from {@link java.lang.Object}.
*
* @return String representation of this endpoint.
*/
public String toString() {
StringBuilder buffer = new StringBuilder();
buffer.append("[Endpoint for ");
buffer.append(this.node);
buffer.append(" with URL ");
buffer.append(this.url);
return buffer.toString();
}
}