/*
* Copyright (c) 2008-2012, Hazel Bilisim Ltd. All Rights Reserved.
*
* Licensed 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 com.hazelcast.impl;
import com.hazelcast.cluster.JoinInfo;
import com.hazelcast.config.Config;
import com.hazelcast.logging.ILogger;
import com.hazelcast.nio.Packet;
import com.hazelcast.nio.PipedZipBufferFactory;
import com.hazelcast.nio.PipedZipBufferFactory.DeflatingPipedBuffer;
import com.hazelcast.nio.PipedZipBufferFactory.InflatingPipedBuffer;
import java.io.EOFException;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.util.List;
import java.util.concurrent.*;
import java.util.logging.Level;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
public class MulticastService implements Runnable {
private static final int DATAGRAM_BUFFER_SIZE = 64 * 1024;
private final ILogger logger;
private final BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
private final MulticastSocket multicastSocket;
private final DatagramPacket datagramPacketSend;
private final DatagramPacket datagramPacketReceive;
private final Object sendLock = new Object();
final Node node;
private boolean running = true;
private List<MulticastListener> lsListeners = new CopyOnWriteArrayList<MulticastListener>();
private final InflatingPipedBuffer inflatingBuffer = PipedZipBufferFactory.createInflatingBuffer(DATAGRAM_BUFFER_SIZE);
private final DeflatingPipedBuffer deflatingBuffer = PipedZipBufferFactory.createDeflatingBuffer(DATAGRAM_BUFFER_SIZE, Deflater.BEST_SPEED);
public MulticastService(Node node, MulticastSocket multicastSocket) throws Exception {
this.node = node;
logger = node.getLogger(MulticastService.class.getName());
Config config = node.getConfig();
this.multicastSocket = multicastSocket;
this.datagramPacketReceive = new DatagramPacket(inflatingBuffer.getInputBuffer().array(), DATAGRAM_BUFFER_SIZE);
this.datagramPacketSend = new DatagramPacket(deflatingBuffer.getOutputBuffer().array(), DATAGRAM_BUFFER_SIZE, InetAddress
.getByName(config.getNetworkConfig().getJoin().getMulticastConfig().getMulticastGroup()),
config.getNetworkConfig().getJoin().getMulticastConfig().getMulticastPort());
running = true;
}
public void addMulticastListener(MulticastListener multicastListener) {
lsListeners.add(multicastListener);
}
public void removeMulticastListener(MulticastListener multicastListener) {
lsListeners.remove(multicastListener);
}
public void stop() {
try {
try {
multicastSocket.close();
} catch (Throwable ignored) {
}
final CountDownLatch l = new CountDownLatch(1);
queue.put(new Runnable() {
public void run() {
running = false;
try {
inflatingBuffer.destroy();
deflatingBuffer.destroy();
datagramPacketReceive.setData(new byte[0]);
datagramPacketSend.setData(new byte[0]);
} finally {
l.countDown();
}
}
});
if (!l.await(5, TimeUnit.SECONDS)) {
logger.log(Level.WARNING, "Failed to shutdown MulticastService in 5 seconds!");
}
} catch (Throwable e) {
logger.log(Level.WARNING, e.getMessage(), e);
}
}
@SuppressWarnings("WhileLoopSpinsOnField")
public void run() {
while (running) {
try {
Runnable runnable = queue.poll();
if (runnable != null) {
runnable.run();
return;
}
final JoinInfo joinInfo = receive();
if (joinInfo != null) {
for (MulticastListener multicastListener : lsListeners) {
try {
multicastListener.onMessage(joinInfo);
} catch (Exception e) {
logger.log(Level.WARNING, e.getMessage(), e);
}
}
}
} catch (OutOfMemoryError e) {
node.onOutOfMemory(e);
} catch (Exception e) {
logger.log(Level.WARNING, e.getMessage(), e);
}
}
}
private JoinInfo receive() {
try {
inflatingBuffer.reset();
try {
multicastSocket.receive(datagramPacketReceive);
} catch (IOException ignore) {
return null;
}
try {
inflatingBuffer.inflate(datagramPacketReceive.getLength());
final byte packetVersion = inflatingBuffer.getDataInput().readByte();
if (packetVersion != Packet.PACKET_VERSION) {
logger.log(Level.FINEST, "Received a JoinRequest with different packet version: "
+ packetVersion);
return null;
}
JoinInfo joinInfo = new JoinInfo();
joinInfo.readData(inflatingBuffer.getDataInput());
return joinInfo;
} catch (Exception e) {
if (e instanceof EOFException || e instanceof DataFormatException) {
logger.log(Level.FINEST, "Received data format is invalid." +
" (An old version of Hazelcast may be running here.)", e);
} else {
throw e;
}
}
} catch (Exception e) {
logger.log(Level.WARNING, e.getMessage(), e);
}
return null;
}
public void send(JoinInfo joinInfo) {
if (!running) return;
synchronized (sendLock) {
try {
deflatingBuffer.reset();
deflatingBuffer.getDataOutput().writeByte(Packet.PACKET_VERSION);
joinInfo.writeData(deflatingBuffer.getDataOutput());
final int count = deflatingBuffer.deflate();
datagramPacketSend.setData(deflatingBuffer.getOutputBuffer().array(), 0, count);
multicastSocket.send(datagramPacketSend);
} catch (IOException e) {
logger.log(Level.WARNING, "You probably have too long Hazelcast configuration!", e);
}
}
}
}