/Frameworks/Core/ERExtensions/Sources/er/extensions/foundation/ERXRemoteNotificationCenter.java
Java | 339 lines | 260 code | 45 blank | 34 comment | 36 complexity | 8c4e09c5d1038601e34a15429a8ee82c MD5 | raw file
- package er.extensions.foundation;
- import java.io.ByteArrayInputStream;
- import java.io.ByteArrayOutputStream;
- import java.io.DataInputStream;
- import java.io.DataOutputStream;
- import java.io.IOException;
- import java.net.DatagramPacket;
- import java.net.InetAddress;
- import java.net.InetSocketAddress;
- import java.net.MulticastSocket;
- import java.net.NetworkInterface;
- import java.net.SocketException;
- import java.net.UnknownHostException;
- import org.apache.log4j.Logger;
- import com.webobjects.appserver.WOApplication;
- import com.webobjects.foundation.NSDictionary;
- import com.webobjects.foundation.NSForwardException;
- import com.webobjects.foundation.NSMutableDictionary;
- import com.webobjects.foundation.NSNotification;
- import com.webobjects.foundation.NSNotificationCenter;
- /**
- * NSNotificationCenter that can post simple notifications to other
- * applications. Currently just posts the name, no object and the userInfo as a
- * dictionary of strings. You must specifically register observers and post notifications here, not at
- * <code>NSNotificationCenter.defaultCenter()</code>. <br/>
- * If you don't link ERJGroupsSynchronizer, it will create a simple implementation, which posts via
- * multicast - and is thus not really reliable and can't handle larger useInfo dict
- * (of which keys and values must be strings). Also, you need to explicitely send the
- * notification locally if you want to receive it in the sending application.
- * @property er.extensions.ERXRemoteNotificationCenter.localBindAddress the local address to bind to
- * @property er.extensions.ERXRemoteNotificationCenter.group the multicast address to send to (230.0.0.1)
- * @property er.extensions.ERXRemoteNotificationCenter.port the multicast port to send to (9754)
- * @property er.extensions.ERXRemoteNotificationCenter.maxPacketSize the maximum multicast packet size (1024)
- * @property er.extensions.ERXRemoteNotificationCenter.identifier the unique identifier for this host (autogenerated by default)
- *
- * @author ak
- */
- // TODO subclass of NSNotification that custom-serialize itself to a sparser
- // format
- public abstract class ERXRemoteNotificationCenter extends NSNotificationCenter {
-
- private static final Logger log = Logger.getLogger(ERXRemoteNotificationCenter.class);
- private static ERXRemoteNotificationCenter _sharedInstance;
- private static class SimpleCenter extends ERXRemoteNotificationCenter {
- public static final int IDENTIFIER_LENGTH = 6;
- private static final int JOIN = 1;
- private static final int LEAVE = 2;
- private static final int POST = 3;
- private boolean _postLocal;
- private byte[] _identifier;
- private InetAddress _localBindAddress;
- private NetworkInterface _localNetworkInterface;
- private InetSocketAddress _multicastGroup;
- private int _multicastPort;
- private MulticastSocket _multicastSocket;
- private boolean _listening;
- private int _maxPacketSize;
- protected SimpleCenter() throws IOException {
- init();
- }
- protected void init() throws UnknownHostException, SocketException, IOException {
- String localBindAddressStr = ERXProperties.stringForKey("er.extensions.ERXRemoteNotificationsCenter.localBindAddress");
- if (localBindAddressStr == null) {
- _localBindAddress = WOApplication.application().hostAddress();
- }
- else {
- _localBindAddress = InetAddress.getByName(localBindAddressStr);
- }
- String multicastGroup = ERXProperties.stringForKeyWithDefault("er.extensions.ERXRemoteNotificationsCenter.group", "230.0.0.1");
- _multicastPort = ERXProperties.intForKeyWithDefault("er.extensions.ERXRemoteNotificationsCenter.port", 9754);
- int maxPacketSize = ERXProperties.intForKeyWithDefault("er.extensions.ERXRemoteNotificationsCenter.maxPacketSize", 1024);
- _maxPacketSize = 2 * maxPacketSize;
- String multicastIdentifierStr = ERXProperties.stringForKey("er.extensions.ERXRemoteNotificationsCenter.identifier");
- if (multicastIdentifierStr == null) {
- _identifier = new byte[IDENTIFIER_LENGTH];
- byte[] hostAddressBytes = _localBindAddress.getAddress();
- System.arraycopy(hostAddressBytes, 0, _identifier, 0, hostAddressBytes.length);
- int multicastInstance = WOApplication.application().port().shortValue();
- _identifier[4] = (byte) (multicastInstance & 0xff);
- _identifier[5] = (byte) ((multicastInstance >>> 8) & 0xff);
- }
- else {
- _identifier = ERXStringUtilities.hexStringToByteArray(multicastIdentifierStr);
- }
- _localNetworkInterface = NetworkInterface.getByInetAddress(_localBindAddress);
- _multicastGroup = new InetSocketAddress(InetAddress.getByName(multicastGroup), _multicastPort);
- _multicastSocket = new MulticastSocket(null);
- _multicastSocket.setInterface(_localBindAddress);
- _multicastSocket.setTimeToLive(4);
- _multicastSocket.setReuseAddress(true);
- _multicastSocket.bind(new InetSocketAddress(_multicastPort));
- join();
- }
- public void join() throws IOException {
- if (log.isInfoEnabled()) {
- log.info("Multicast instance " + ERXStringUtilities.byteArrayToHexString(_identifier) + " joining.");
- }
- _multicastSocket.joinGroup(_multicastGroup, _localNetworkInterface);
- MulticastByteArrayOutputStream baos = new MulticastByteArrayOutputStream();
- DataOutputStream dos = new DataOutputStream(baos);
- dos.write(_identifier);
- dos.writeByte(JOIN);
- dos.flush();
- dos.close();
- _multicastSocket.send(baos.createDatagramPacket());
- listen();
- }
- public void leave() throws IOException {
- if (log.isInfoEnabled()) {
- log.info("Multicast instance " + ERXStringUtilities.byteArrayToHexString(_identifier) + " leaving.");
- }
- MulticastByteArrayOutputStream baos = new MulticastByteArrayOutputStream();
- DataOutputStream dos = new DataOutputStream(baos);
- dos.write(_identifier);
- dos.writeByte(LEAVE);
- dos.flush();
- dos.close();
- _multicastSocket.send(baos.createDatagramPacket());
- _multicastSocket.leaveGroup(_multicastGroup, _localNetworkInterface);
- _listening = false;
- }
- @Override
- protected void postRemoteNotification(NSNotification notification) {
- try {
- MulticastByteArrayOutputStream baos = new MulticastByteArrayOutputStream();
- DataOutputStream dos = new DataOutputStream(baos);
- dos.write(_identifier);
- dos.writeByte(POST);
- writeNotification(notification, dos);
- _multicastSocket.send(baos.createDatagramPacket());
- if (log.isDebugEnabled()) {
- log.info("Multicast instance " + ERXStringUtilities.byteArrayToHexString(_identifier) + ": Writing " + notification);
- }
- dos.close();
- if (_postLocal) {
- postLocalNotification(notification);
- }
- }
- catch (Exception e) {
- throw NSForwardException._runtimeExceptionForThrowable(e);
- }
- }
- public void listen() throws IOException {
- Thread listenThread = new Thread(new Runnable() {
- public void run() {
- _listening = true;
- byte[] buffer = new byte[_maxPacketSize];
- while (_listening) {
- DatagramPacket receivePacket = new DatagramPacket(buffer, 0, buffer.length);
- try {
- _multicastSocket.receive(receivePacket);
- handlePacket(receivePacket);
- }
- catch (Throwable t) {
- log.error("Failed to read multicast notification.", t);
- }
- }
- }
- private void handlePacket(DatagramPacket receivePacket) throws IOException {
- ByteArrayInputStream bais = new ByteArrayInputStream(receivePacket.getData(), 0, receivePacket.getLength());
- DataInputStream dis = new DataInputStream(bais);
- byte[] identifier = new byte[IDENTIFIER_LENGTH];
- dis.readFully(identifier);
- String remote = ERXStringUtilities.byteArrayToHexString(identifier);
- byte code = dis.readByte();
- if (code == JOIN) {
- if (log.isDebugEnabled()) {
- log.info("Received JOIN from " + remote);
- }
- }
- else if (code == LEAVE) {
- if (log.isDebugEnabled()) {
- log.info("Received LEAVE from " + remote);
- }
- }
- else if (code == POST) {
- String self = ERXStringUtilities.byteArrayToHexString(_identifier);
- if (log.isDebugEnabled()) {
- log.info("Received POST from " + remote);
- }
- if (log.isDebugEnabled()) {
- log.info("Received POST from " + ERXStringUtilities.byteArrayToHexString(identifier));
- }
- if(!self.equals(remote)) {
- NSNotification notification = readNotification(dis);
- if (log.isDebugEnabled()) {
- log.debug("Received notification: " + notification);
- }
- else if (log.isInfoEnabled()) {
- log.info("Received " + notification.name() + " notification from " + remote);
- }
- postLocalNotification(notification);
- }
- }
- }
- });
- listenThread.start();
- }
- protected class MulticastByteArrayOutputStream extends ByteArrayOutputStream {
- public byte[] buffer() {
- return buf;
- }
- public DatagramPacket createDatagramPacket() throws SocketException {
- return new DatagramPacket(buf, 0, count, _multicastGroup);
- }
- }
- private NSNotification readNotification(DataInputStream dis) throws IOException {
- short nameLen = dis.readShort();
- byte[] nameBytes = new byte[nameLen];
- dis.readFully(nameBytes);
- short objectLen = dis.readShort();
- byte[] objectBytes = new byte[objectLen];
- dis.readFully(objectBytes);
- short userInfoLen = dis.readShort();
- NSMutableDictionary userInfo = new NSMutableDictionary();
- for (int i = 0; i < userInfoLen; i++) {
- short keyLen = dis.readShort();
- byte[] keyBytes = new byte[keyLen];
- dis.readFully(keyBytes);
- short valueLen = dis.readShort();
- byte[] valueBytes = new byte[valueLen];
- dis.readFully(valueBytes);
- userInfo.setObjectForKey(new String(valueBytes), new String(keyBytes));
- }
- NSNotification notification = new NSNotification(new String(nameBytes), null, userInfo);
- return notification;
- }
- private void writeNotification(NSNotification notification, DataOutputStream dos) throws IOException {
- byte[] name = notification.name().getBytes();
- dos.writeShort(name.length);
- dos.write(name);
- byte[] object = new byte[0];
- dos.writeShort(object.length);
- dos.write(object);
- NSDictionary userInfo = notification.userInfo();
- if (userInfo == null) {
- userInfo = NSDictionary.EmptyDictionary;
- }
- dos.writeShort(userInfo.count());
- for (Object key : userInfo.allKeys()) {
- byte[] keyBytes = key.toString().getBytes();
- byte[] valueBytes = userInfo.objectForKey(key).toString().getBytes();
- dos.writeShort(keyBytes.length);
- dos.write(keyBytes);
- dos.writeShort(valueBytes.length);
- dos.write(valueBytes);
- }
- dos.flush();
- if (dos.size() > _maxPacketSize) {
- throw new IllegalArgumentException("More than " + _maxPacketSize + " bytes");
- }
- }
- }
- public static ERXRemoteNotificationCenter defaultCenter() {
- if (_sharedInstance == null) {
- synchronized (ERXRemoteNotificationCenter.class) {
- if (_sharedInstance == null) {
- try {
- _sharedInstance = new SimpleCenter();
- }
- catch (IOException e) {
- throw NSForwardException._runtimeExceptionForThrowable(e);
- }
- }
- }
- }
- return _sharedInstance;
- }
- /**
- * Set the default center
- * @param center the notification center to use as default
- */
- public static void setDefaultCenter(ERXRemoteNotificationCenter center) {
- _sharedInstance = center;
- }
- /**
- * Post a notification to the local app only.
- * @param notification the notification
- */
- public void postLocalNotification(NSNotification notification) {
- super.postNotification(notification);
- }
- /**
- * Post a notification to the remote listeners.
- * @param notification the notification
- */
- protected abstract void postRemoteNotification(NSNotification notification);
- /**
- * Overridden to call {@link #postRemoteNotification(NSNotification)}.
- */
- @Override
- public void postNotification(NSNotification notification) {
- postRemoteNotification(notification);
- }
- }