/*
* Copyright 2009 Red Hat, Inc.
* Red Hat licenses this file to you under the Apache License, version
* 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package org.hornetq.core.cluster.impl;
import java.io.InterruptedIOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MulticastSocket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.hornetq.api.core.HornetQBuffer;
import org.hornetq.api.core.HornetQBuffers;
import org.hornetq.api.core.Pair;
import org.hornetq.api.core.SimpleString;
import org.hornetq.api.core.TransportConfiguration;
import org.hornetq.api.core.management.NotificationType;
import org.hornetq.core.cluster.DiscoveryEntry;
import org.hornetq.core.cluster.DiscoveryGroup;
import org.hornetq.core.cluster.DiscoveryListener;
import org.hornetq.core.logging.Logger;
import org.hornetq.core.server.management.Notification;
import org.hornetq.core.server.management.NotificationService;
import org.hornetq.utils.TypedProperties;
/**
* A DiscoveryGroupImpl
*
* @author <a href="mailto:tim.fox@jboss.com">Tim Fox</a>
*
* Created 17 Nov 2008 13:21:45
*
*/
public class DiscoveryGroupImpl implements Runnable, DiscoveryGroup
{
private static final Logger log = Logger.getLogger(DiscoveryGroupImpl.class);
private static final int SOCKET_TIMEOUT = 500;
private MulticastSocket socket;
private final List<DiscoveryListener> listeners = new ArrayList<DiscoveryListener>();
private final String name;
private Thread thread;
private boolean received;
private final Object waitLock = new Object();
private final Map<String, DiscoveryEntry> connectors = new HashMap<String, DiscoveryEntry>();
private final long timeout;
private volatile boolean started;
private final String nodeID;
private final InetAddress localBindAddress;
private final InetAddress groupAddress;
private final int groupPort;
private final Map<String, String> uniqueIDMap = new HashMap<String, String>();
private NotificationService notificationService;
public DiscoveryGroupImpl(final String nodeID,
final String name,
final InetAddress localBindAddress,
final InetAddress groupAddress,
final int groupPort,
final long timeout) throws Exception
{
this.nodeID = nodeID;
this.name = name;
this.timeout = timeout;
this.localBindAddress = localBindAddress;
this.groupAddress = groupAddress;
this.groupPort = groupPort;
}
public void setNotificationService(final NotificationService notificationService)
{
this.notificationService = notificationService;
}
public synchronized void start() throws Exception
{
if (started)
{
return;
}
socket = new MulticastSocket(groupPort);
if (localBindAddress != null)
{
socket.setInterface(localBindAddress);
}
socket.joinGroup(groupAddress);
socket.setSoTimeout(DiscoveryGroupImpl.SOCKET_TIMEOUT);
started = true;
thread = new Thread(this, "hornetq-discovery-group-thread-" + name);
thread.setDaemon(true);
thread.start();
if (notificationService != null)
{
TypedProperties props = new TypedProperties();
props.putSimpleStringProperty(new SimpleString("name"), new SimpleString(name));
Notification notification = new Notification(nodeID, NotificationType.DISCOVERY_GROUP_STARTED, props);
notificationService.sendNotification(notification);
}
}
public void stop()
{
synchronized (this)
{
if (!started)
{
return;
}
started = false;
}
try
{
thread.join();
}
catch (InterruptedException e)
{
}
socket.close();
socket = null;
thread = null;
if (notificationService != null)
{
TypedProperties props = new TypedProperties();
props.putSimpleStringProperty(new SimpleString("name"), new SimpleString(name));
Notification notification = new Notification(nodeID, NotificationType.DISCOVERY_GROUP_STOPPED, props);
try
{
notificationService.sendNotification(notification);
}
catch (Exception e)
{
DiscoveryGroupImpl.log.warn("unable to send notification when discovery group is stopped", e);
}
}
}
public boolean isStarted()
{
return started;
}
public String getName()
{
return name;
}
public synchronized Map<String, DiscoveryEntry> getDiscoveryEntryMap()
{
return new HashMap<String, DiscoveryEntry>(connectors);
}
public boolean waitForBroadcast(final long timeout)
{
synchronized (waitLock)
{
long start = System.currentTimeMillis();
long toWait = timeout;
while (!received && toWait > 0)
{
try
{
waitLock.wait(toWait);
}
catch (InterruptedException e)
{
}
long now = System.currentTimeMillis();
toWait -= now - start;
start = now;
}
boolean ret = received;
received = false;
return ret;
}
}
/*
* This is a sanity check to catch any cases where two different nodes are broadcasting the same node id either
* due to misconfiguration or problems in failover
*/
private void checkUniqueID(final String originatingNodeID, final String uniqueID)
{
String currentUniqueID = uniqueIDMap.get(originatingNodeID);
if (currentUniqueID == null)
{
uniqueIDMap.put(originatingNodeID, uniqueID);
}
else
{
if (!currentUniqueID.equals(uniqueID))
{
log.warn("There are more than one servers on the network broadcasting the same node id. " +
"You will see this message exactly once (per node) if a node is restarted, in which case it can be safely " +
"ignored. But if it is logged continuously it means you really do have more than one node on the same network " +
"active concurrently with the same node id. This could occur if you have a backup node active at the same time as " +
"its live node.");
uniqueIDMap.put(originatingNodeID, uniqueID);
}
}
}
public void run()
{
try
{
// TODO - can we use a smaller buffer size?
final byte[] data = new byte[65535];
while (true)
{
if (!started)
{
return;
}
final DatagramPacket packet = new DatagramPacket(data, data.length);
try
{
socket.receive(packet);
}
catch (InterruptedIOException e)
{
if (!started)
{
return;
}
else
{
continue;
}
}
HornetQBuffer buffer = HornetQBuffers.wrappedBuffer(data);
String originatingNodeID = buffer.readString();
String uniqueID = buffer.readString();
checkUniqueID(originatingNodeID, uniqueID);
if (nodeID.equals(originatingNodeID))
{
// Ignore traffic from own node
continue;
}
int size = buffer.readInt();
boolean changed = false;
synchronized (this)
{
for (int i = 0; i < size; i++)
{
TransportConfiguration connector = new TransportConfiguration();
connector.decode(buffer);
boolean existsBackup = buffer.readBoolean();
TransportConfiguration backupConnector = null;
if (existsBackup)
{
backupConnector = new TransportConfiguration();
backupConnector.decode(buffer);
}
Pair<TransportConfiguration, TransportConfiguration> connectorPair = new Pair<TransportConfiguration, TransportConfiguration>(connector,
backupConnector);
DiscoveryEntry entry = new DiscoveryEntry(connectorPair, System.currentTimeMillis());
DiscoveryEntry oldVal = connectors.put(originatingNodeID, entry);
if (oldVal == null)
{
changed = true;
}
}
long now = System.currentTimeMillis();
Iterator<Map.Entry<String, DiscoveryEntry>> iter = connectors.entrySet().iterator();
// Weed out any expired connectors
while (iter.hasNext())
{
Map.Entry<String, DiscoveryEntry> entry = iter.next();
if (entry.getValue().getLastUpdate() + timeout <= now)
{
iter.remove();
changed = true;
}
}
}
if (changed)
{
callListeners();
}
synchronized (waitLock)
{
received = true;
waitLock.notify();
}
}
}
catch (Exception e)
{
DiscoveryGroupImpl.log.error("Failed to receive datagram", e);
}
}
public synchronized void registerListener(final DiscoveryListener listener)
{
listeners.add(listener);
if (!connectors.isEmpty())
{
listener.connectorsChanged();
}
}
public synchronized void unregisterListener(final DiscoveryListener listener)
{
listeners.remove(listener);
}
private void callListeners()
{
for (DiscoveryListener listener : listeners)
{
try
{
listener.connectorsChanged();
}
catch (Throwable t)
{
// Catch it so exception doesn't prevent other listeners from running
DiscoveryGroupImpl.log.error("Failed to call discovery listener", t);
}
}
}
}