package ibis.ipl.registry.gossip;
import ibis.ipl.Credentials;
import ibis.ipl.IbisCapabilities;
import ibis.ipl.IbisConfigurationException;
import ibis.ipl.IbisProperties;
import ibis.ipl.NoSuchPropertyException;
import ibis.ipl.RegistryEventHandler;
import ibis.ipl.impl.IbisIdentifier;
import ibis.ipl.impl.Location;
import ibis.ipl.registry.statistics.Statistics;
import ibis.util.ThreadPool;
import ibis.util.TypedProperties;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Registry extends ibis.ipl.registry.Registry implements Runnable {
private final Logger logger = LoggerFactory.getLogger(Registry.class);
private final IbisCapabilities capabilities;
private final TypedProperties properties;
private final String poolName;
private final IbisIdentifier identifier;
private final Statistics statistics;
private final MemberSet members;
private final ElectionSet elections;
private final CommunicationHandler commHandler;
private final Upcaller upcaller;
// data structures the user can poll
private final ArrayList<IbisIdentifier> joinedIbises;
private final ArrayList<IbisIdentifier> leftIbises;
private final ArrayList<IbisIdentifier> diedIbises;
private final ArrayList<String> signals;
private boolean stopped;
/**
* Creates a Gossip Registry.
*
* @param capabilities
* capabilities required of this registry
* @param eventHandler
* Registry handler to pass events to.
* @param userProperties
* properties of this registry.
* @param ibisData
* Ibis implementation data to attach to the IbisIdentifier.
* @param credentials
* credentials used for authenticating this ibis at the server
* @param implementationVersion
* the identification of this ibis implementation, including
* version, class and such. Must be identical for all Ibises in a
* single poolName.
* @param applicationTag
* A tag provided by the application constructing this ibis.
* @throws IOException
* in case of trouble.
* @throws IbisConfigurationException
* In case invalid properties were given.
*/
public Registry(IbisCapabilities capabilities,
RegistryEventHandler eventHandler, Properties userProperties,
byte[] ibisData, String implementationVersion,
Credentials credentials, byte[] applicationTag)
throws IbisConfigurationException, IOException,
IbisConfigurationException {
this.capabilities = capabilities;
if (capabilities
.hasCapability(IbisCapabilities.MEMBERSHIP_TOTALLY_ORDERED)) {
throw new IbisConfigurationException(
"gossip registry does not support totally ordered membership");
}
if (capabilities.hasCapability(IbisCapabilities.CLOSED_WORLD)) {
throw new IbisConfigurationException(
"gossip registry does not support closed world");
}
if (capabilities.hasCapability(IbisCapabilities.ELECTIONS_STRICT)) {
throw new IbisConfigurationException(
"gossip registry does not support strict elections");
}
if (capabilities.hasCapability(IbisCapabilities.TERMINATION)) {
throw new IbisConfigurationException(
"gossip registry does not support termination");
}
properties = RegistryProperties.getHardcodedProperties();
properties.addProperties(userProperties);
if ((capabilities.hasCapability(IbisCapabilities.MEMBERSHIP_UNRELIABLE))
&& eventHandler == null) {
joinedIbises = new ArrayList<IbisIdentifier>();
leftIbises = new ArrayList<IbisIdentifier>();
diedIbises = new ArrayList<IbisIdentifier>();
} else {
joinedIbises = null;
leftIbises = null;
diedIbises = null;
}
if (capabilities.hasCapability(IbisCapabilities.SIGNALS)
&& eventHandler == null) {
signals = new ArrayList<String>();
} else {
signals = null;
}
if (eventHandler != null) {
upcaller = new Upcaller(eventHandler);
} else {
upcaller = null;
}
UUID id = UUID.randomUUID();
poolName = properties.getProperty(IbisProperties.POOL_NAME);
if (poolName == null) {
throw new IbisConfigurationException(
"cannot initialize registry, property "
+ IbisProperties.POOL_NAME + " is not specified");
}
Location location = Location.defaultLocation(properties, null);
if (properties.getBooleanProperty(RegistryProperties.STATISTICS)) {
statistics = new Statistics(Protocol.OPCODE_NAMES);
statistics.setID(id.toString() + "@" + location.toString(),
poolName);
long interval = properties
.getIntProperty(RegistryProperties.STATISTICS_INTERVAL) * 1000;
statistics.startWriting(interval);
} else {
statistics = null;
}
members = new MemberSet(properties, this, statistics);
elections = new ElectionSet(properties, this);
commHandler = new CommunicationHandler(properties, this, members,
elections, statistics);
identifier = new IbisIdentifier(id.toString(), ibisData, commHandler
.getAddress().toBytes(), location, poolName, applicationTag);
commHandler.start();
members.start();
boolean printMembers = properties
.getBooleanProperty(RegistryProperties.PRINT_MEMBERS);
if (printMembers) {
new MemberPrinter(members);
}
ThreadPool.createNew(this, "pool management thread");
logger.debug("registry for " + identifier + " initiated");
}
@Override
public IbisIdentifier getIbisIdentifier() {
return identifier;
}
CommunicationHandler getCommHandler() {
return commHandler;
}
public IbisIdentifier elect(String electionName) throws IOException {
if (!capabilities.hasCapability(IbisCapabilities.ELECTIONS_UNRELIABLE)) {
throw new IbisConfigurationException(
"No election support requested");
}
IbisIdentifier[] candidates = elections.elect(electionName);
return members.getFirstLiving(candidates);
}
public IbisIdentifier elect(String electionName, long timeoutMillis)
throws IOException {
if (!capabilities.hasCapability(IbisCapabilities.ELECTIONS_UNRELIABLE)) {
throw new IbisConfigurationException(
"No election support requested");
}
IbisIdentifier[] candidates = elections.elect(electionName,
timeoutMillis);
return members.getFirstLiving(candidates);
}
public IbisIdentifier getElectionResult(String election) throws IOException {
if (!capabilities.hasCapability(IbisCapabilities.ELECTIONS_UNRELIABLE)) {
throw new IbisConfigurationException(
"No election support requested");
}
IbisIdentifier[] candidates = elections.getElectionResult(election);
return members.getFirstLiving(candidates);
}
public String[] wonElections() {
ArrayList<String> result = new ArrayList<String>();
synchronized(elections) {
for (Election e : elections) {
if (e.getWinner().equals(identifier)) {
result.add(e.getName());
}
}
}
return result.toArray(new String[result.size()]);
}
public IbisIdentifier getElectionResult(String electionName,
long timeoutMillis) throws IOException {
if (!capabilities.hasCapability(IbisCapabilities.ELECTIONS_UNRELIABLE)) {
throw new IbisConfigurationException(
"No election support requested");
}
IbisIdentifier[] candidates = elections.getElectionResult(electionName,
timeoutMillis);
return members.getFirstLiving(candidates);
}
public void maybeDead(ibis.ipl.IbisIdentifier suspect) throws IOException {
try {
members.maybeDead((IbisIdentifier) suspect);
} catch (ClassCastException e) {
logger.error("illegal ibis identifier given: " + e);
}
}
public void assumeDead(ibis.ipl.IbisIdentifier deceased) throws IOException {
try {
members.assumeDead((IbisIdentifier) deceased);
} catch (ClassCastException e) {
logger.error("illegal ibis identifier given: " + e);
}
}
public void signal(String signal,
ibis.ipl.IbisIdentifier... ibisIdentifiers) throws IOException {
if (!capabilities.hasCapability(IbisCapabilities.SIGNALS)) {
throw new IbisConfigurationException("No string support requested");
}
try {
IbisIdentifier[] implIdentifiers = (IbisIdentifier[]) ibisIdentifiers;
commHandler.sendSignals(signal, implIdentifiers);
} catch (ClassCastException e) {
throw new IOException("wrong type of identifiers given: " + e);
}
}
public synchronized ibis.ipl.IbisIdentifier[] joinedIbises() {
if (joinedIbises == null) {
throw new IbisConfigurationException(
"Resize downcalls not configured");
}
ibis.ipl.IbisIdentifier[] result = joinedIbises
.toArray(new ibis.ipl.IbisIdentifier[0]);
joinedIbises.clear();
return result;
}
public synchronized ibis.ipl.IbisIdentifier[] leftIbises() {
if (leftIbises == null) {
throw new IbisConfigurationException(
"Resize downcalls not configured");
}
ibis.ipl.IbisIdentifier[] result = leftIbises
.toArray(new ibis.ipl.IbisIdentifier[0]);
leftIbises.clear();
return result;
}
public synchronized ibis.ipl.IbisIdentifier[] diedIbises() {
if (diedIbises == null) {
throw new IbisConfigurationException(
"Resize downcalls not configured");
}
ibis.ipl.IbisIdentifier[] result = diedIbises
.toArray(new ibis.ipl.IbisIdentifier[0]);
diedIbises.clear();
return result;
}
public synchronized String[] receivedSignals() {
if (signals == null) {
throw new IbisConfigurationException(
"Registry downcalls not configured");
}
String[] result = signals.toArray(new String[0]);
signals.clear();
return result;
}
public int getPoolSize() {
throw new IbisConfigurationException(
"getPoolSize not supported by gossip registry");
}
public synchronized void waitUntilPoolClosed() {
throw new IbisConfigurationException(
"waitForAll not supported by gossip registry");
}
public void enableEvents() {
if (upcaller == null) {
throw new IbisConfigurationException("Registry not configured to "
+ "produce events");
}
upcaller.enableEvents();
}
public void disableEvents() {
if (upcaller == null) {
throw new IbisConfigurationException("Registry not configured to "
+ "produce events");
}
upcaller.disableEvents();
}
@Override
public long getSequenceNumber(String name) throws IOException {
throw new IbisConfigurationException(
"Sequence numbers not supported by" + "gossip registry");
}
public Map<String, String> managementProperties() {
// no properties (as of yet)
return new HashMap<String, String>();
}
public String getManagementProperty(String key)
throws NoSuchPropertyException {
String result = managementProperties().get(key);
if (result == null) {
throw new NoSuchPropertyException(key + " is not a valid property");
}
return result;
}
public void setManagementProperties(Map<String, String> properties)
throws NoSuchPropertyException {
throw new NoSuchPropertyException(
"gossip registry does not have any properties that can be set");
}
public void setManagementProperty(String key, String value)
throws NoSuchPropertyException {
throw new NoSuchPropertyException(
"gossip registry does not have any properties that can be set");
}
public void printManagementProperties(PrintStream stream) {
// NOTHING
}
// functions called by pool to tell the registry an event has occured
synchronized void ibisJoined(IbisIdentifier ibis) {
if (joinedIbises != null) {
joinedIbises.add(ibis);
}
if (upcaller != null) {
upcaller.ibisJoined(ibis);
}
}
synchronized void ibisLeft(IbisIdentifier ibis) {
if (leftIbises != null) {
leftIbises.add(ibis);
}
if (upcaller != null) {
upcaller.ibisLeft(ibis);
}
}
synchronized void ibisDied(IbisIdentifier ibis) {
if (diedIbises != null) {
diedIbises.add(ibis);
}
if (upcaller != null) {
upcaller.ibisDied(ibis);
}
}
synchronized void signal(String signal, IbisIdentifier source) {
if (signals != null) {
signals.add(signal);
}
if (upcaller != null) {
upcaller.signal(signal, source);
}
}
synchronized void electionResult(String name, IbisIdentifier winner) {
if (upcaller != null) {
upcaller.electionResult(name, winner);
}
}
public boolean isStopped() {
return stopped;
}
public String getPoolName() {
return poolName;
}
@Override
public void leave() throws IOException {
logger.debug("leaving: setting stopped state");
synchronized (this) {
stopped = true;
notifyAll();
}
logger.debug("leaving: telling pool we are leaving");
members.leave(identifier);
members.leave();
// logger.debug("leaving: broadcasting leave");
commHandler.broadcastLeave();
logger.debug("leaving: writing statistics");
if (statistics != null) {
statistics.write();
statistics.end();
}
logger.debug("leaving: done!");
}
public void run() {
long interval = properties
.getIntProperty(RegistryProperties.GOSSIP_INTERVAL) * 1000;
while (!isStopped()) {
commHandler.gossip();
int timeout = (int) (Math.random() * interval);
synchronized (this) {
try {
wait(timeout);
} catch (InterruptedException e) {
// IGNORE
}
}
}
}
public boolean hasTerminated() {
throw new IbisConfigurationException(
"gossip registry does not support termination");
}
public boolean isClosed() {
throw new IbisConfigurationException(
"gossip registry does not support closed world");
}
public void terminate() throws IOException {
throw new IbisConfigurationException(
"gossip registry does not support termination");
}
public ibis.ipl.IbisIdentifier waitUntilTerminated() {
throw new IbisConfigurationException(
"gossip registry does not support termination");
}
@Override
public IbisIdentifier getRandomPoolMember() {
Member[] random = members.getRandomMembers(1);
if (random.length == 1) {
return random[0].getIdentifier();
} else {
return null;
}
}
}