PageRenderTime 66ms CodeModel.GetById 37ms RepoModel.GetById 0ms app.codeStats 1ms

/Frameworks/Core/ERExtensions/Sources/er/extensions/foundation/ERXRemoteNotificationCenter.java

https://bitbucket.org/molequedeideias/wonder
Java | 339 lines | 260 code | 45 blank | 34 comment | 36 complexity | 8c4e09c5d1038601e34a15429a8ee82c MD5 | raw file
  1. package er.extensions.foundation;
  2. import java.io.ByteArrayInputStream;
  3. import java.io.ByteArrayOutputStream;
  4. import java.io.DataInputStream;
  5. import java.io.DataOutputStream;
  6. import java.io.IOException;
  7. import java.net.DatagramPacket;
  8. import java.net.InetAddress;
  9. import java.net.InetSocketAddress;
  10. import java.net.MulticastSocket;
  11. import java.net.NetworkInterface;
  12. import java.net.SocketException;
  13. import java.net.UnknownHostException;
  14. import org.apache.log4j.Logger;
  15. import com.webobjects.appserver.WOApplication;
  16. import com.webobjects.foundation.NSDictionary;
  17. import com.webobjects.foundation.NSForwardException;
  18. import com.webobjects.foundation.NSMutableDictionary;
  19. import com.webobjects.foundation.NSNotification;
  20. import com.webobjects.foundation.NSNotificationCenter;
  21. /**
  22. * NSNotificationCenter that can post simple notifications to other
  23. * applications. Currently just posts the name, no object and the userInfo as a
  24. * dictionary of strings. You must specifically register observers and post notifications here, not at
  25. * <code>NSNotificationCenter.defaultCenter()</code>. <br/>
  26. * If you don't link ERJGroupsSynchronizer, it will create a simple implementation, which posts via
  27. * multicast - and is thus not really reliable and can't handle larger useInfo dict
  28. * (of which keys and values must be strings). Also, you need to explicitely send the
  29. * notification locally if you want to receive it in the sending application.
  30. * @property er.extensions.ERXRemoteNotificationCenter.localBindAddress the local address to bind to
  31. * @property er.extensions.ERXRemoteNotificationCenter.group the multicast address to send to (230.0.0.1)
  32. * @property er.extensions.ERXRemoteNotificationCenter.port the multicast port to send to (9754)
  33. * @property er.extensions.ERXRemoteNotificationCenter.maxPacketSize the maximum multicast packet size (1024)
  34. * @property er.extensions.ERXRemoteNotificationCenter.identifier the unique identifier for this host (autogenerated by default)
  35. *
  36. * @author ak
  37. */
  38. // TODO subclass of NSNotification that custom-serialize itself to a sparser
  39. // format
  40. public abstract class ERXRemoteNotificationCenter extends NSNotificationCenter {
  41. private static final Logger log = Logger.getLogger(ERXRemoteNotificationCenter.class);
  42. private static ERXRemoteNotificationCenter _sharedInstance;
  43. private static class SimpleCenter extends ERXRemoteNotificationCenter {
  44. public static final int IDENTIFIER_LENGTH = 6;
  45. private static final int JOIN = 1;
  46. private static final int LEAVE = 2;
  47. private static final int POST = 3;
  48. private boolean _postLocal;
  49. private byte[] _identifier;
  50. private InetAddress _localBindAddress;
  51. private NetworkInterface _localNetworkInterface;
  52. private InetSocketAddress _multicastGroup;
  53. private int _multicastPort;
  54. private MulticastSocket _multicastSocket;
  55. private boolean _listening;
  56. private int _maxPacketSize;
  57. protected SimpleCenter() throws IOException {
  58. init();
  59. }
  60. protected void init() throws UnknownHostException, SocketException, IOException {
  61. String localBindAddressStr = ERXProperties.stringForKey("er.extensions.ERXRemoteNotificationsCenter.localBindAddress");
  62. if (localBindAddressStr == null) {
  63. _localBindAddress = WOApplication.application().hostAddress();
  64. }
  65. else {
  66. _localBindAddress = InetAddress.getByName(localBindAddressStr);
  67. }
  68. String multicastGroup = ERXProperties.stringForKeyWithDefault("er.extensions.ERXRemoteNotificationsCenter.group", "230.0.0.1");
  69. _multicastPort = ERXProperties.intForKeyWithDefault("er.extensions.ERXRemoteNotificationsCenter.port", 9754);
  70. int maxPacketSize = ERXProperties.intForKeyWithDefault("er.extensions.ERXRemoteNotificationsCenter.maxPacketSize", 1024);
  71. _maxPacketSize = 2 * maxPacketSize;
  72. String multicastIdentifierStr = ERXProperties.stringForKey("er.extensions.ERXRemoteNotificationsCenter.identifier");
  73. if (multicastIdentifierStr == null) {
  74. _identifier = new byte[IDENTIFIER_LENGTH];
  75. byte[] hostAddressBytes = _localBindAddress.getAddress();
  76. System.arraycopy(hostAddressBytes, 0, _identifier, 0, hostAddressBytes.length);
  77. int multicastInstance = WOApplication.application().port().shortValue();
  78. _identifier[4] = (byte) (multicastInstance & 0xff);
  79. _identifier[5] = (byte) ((multicastInstance >>> 8) & 0xff);
  80. }
  81. else {
  82. _identifier = ERXStringUtilities.hexStringToByteArray(multicastIdentifierStr);
  83. }
  84. _localNetworkInterface = NetworkInterface.getByInetAddress(_localBindAddress);
  85. _multicastGroup = new InetSocketAddress(InetAddress.getByName(multicastGroup), _multicastPort);
  86. _multicastSocket = new MulticastSocket(null);
  87. _multicastSocket.setInterface(_localBindAddress);
  88. _multicastSocket.setTimeToLive(4);
  89. _multicastSocket.setReuseAddress(true);
  90. _multicastSocket.bind(new InetSocketAddress(_multicastPort));
  91. join();
  92. }
  93. public void join() throws IOException {
  94. if (log.isInfoEnabled()) {
  95. log.info("Multicast instance " + ERXStringUtilities.byteArrayToHexString(_identifier) + " joining.");
  96. }
  97. _multicastSocket.joinGroup(_multicastGroup, _localNetworkInterface);
  98. MulticastByteArrayOutputStream baos = new MulticastByteArrayOutputStream();
  99. DataOutputStream dos = new DataOutputStream(baos);
  100. dos.write(_identifier);
  101. dos.writeByte(JOIN);
  102. dos.flush();
  103. dos.close();
  104. _multicastSocket.send(baos.createDatagramPacket());
  105. listen();
  106. }
  107. public void leave() throws IOException {
  108. if (log.isInfoEnabled()) {
  109. log.info("Multicast instance " + ERXStringUtilities.byteArrayToHexString(_identifier) + " leaving.");
  110. }
  111. MulticastByteArrayOutputStream baos = new MulticastByteArrayOutputStream();
  112. DataOutputStream dos = new DataOutputStream(baos);
  113. dos.write(_identifier);
  114. dos.writeByte(LEAVE);
  115. dos.flush();
  116. dos.close();
  117. _multicastSocket.send(baos.createDatagramPacket());
  118. _multicastSocket.leaveGroup(_multicastGroup, _localNetworkInterface);
  119. _listening = false;
  120. }
  121. @Override
  122. protected void postRemoteNotification(NSNotification notification) {
  123. try {
  124. MulticastByteArrayOutputStream baos = new MulticastByteArrayOutputStream();
  125. DataOutputStream dos = new DataOutputStream(baos);
  126. dos.write(_identifier);
  127. dos.writeByte(POST);
  128. writeNotification(notification, dos);
  129. _multicastSocket.send(baos.createDatagramPacket());
  130. if (log.isDebugEnabled()) {
  131. log.info("Multicast instance " + ERXStringUtilities.byteArrayToHexString(_identifier) + ": Writing " + notification);
  132. }
  133. dos.close();
  134. if (_postLocal) {
  135. postLocalNotification(notification);
  136. }
  137. }
  138. catch (Exception e) {
  139. throw NSForwardException._runtimeExceptionForThrowable(e);
  140. }
  141. }
  142. public void listen() throws IOException {
  143. Thread listenThread = new Thread(new Runnable() {
  144. public void run() {
  145. _listening = true;
  146. byte[] buffer = new byte[_maxPacketSize];
  147. while (_listening) {
  148. DatagramPacket receivePacket = new DatagramPacket(buffer, 0, buffer.length);
  149. try {
  150. _multicastSocket.receive(receivePacket);
  151. handlePacket(receivePacket);
  152. }
  153. catch (Throwable t) {
  154. log.error("Failed to read multicast notification.", t);
  155. }
  156. }
  157. }
  158. private void handlePacket(DatagramPacket receivePacket) throws IOException {
  159. ByteArrayInputStream bais = new ByteArrayInputStream(receivePacket.getData(), 0, receivePacket.getLength());
  160. DataInputStream dis = new DataInputStream(bais);
  161. byte[] identifier = new byte[IDENTIFIER_LENGTH];
  162. dis.readFully(identifier);
  163. String remote = ERXStringUtilities.byteArrayToHexString(identifier);
  164. byte code = dis.readByte();
  165. if (code == JOIN) {
  166. if (log.isDebugEnabled()) {
  167. log.info("Received JOIN from " + remote);
  168. }
  169. }
  170. else if (code == LEAVE) {
  171. if (log.isDebugEnabled()) {
  172. log.info("Received LEAVE from " + remote);
  173. }
  174. }
  175. else if (code == POST) {
  176. String self = ERXStringUtilities.byteArrayToHexString(_identifier);
  177. if (log.isDebugEnabled()) {
  178. log.info("Received POST from " + remote);
  179. }
  180. if (log.isDebugEnabled()) {
  181. log.info("Received POST from " + ERXStringUtilities.byteArrayToHexString(identifier));
  182. }
  183. if(!self.equals(remote)) {
  184. NSNotification notification = readNotification(dis);
  185. if (log.isDebugEnabled()) {
  186. log.debug("Received notification: " + notification);
  187. }
  188. else if (log.isInfoEnabled()) {
  189. log.info("Received " + notification.name() + " notification from " + remote);
  190. }
  191. postLocalNotification(notification);
  192. }
  193. }
  194. }
  195. });
  196. listenThread.start();
  197. }
  198. protected class MulticastByteArrayOutputStream extends ByteArrayOutputStream {
  199. public byte[] buffer() {
  200. return buf;
  201. }
  202. public DatagramPacket createDatagramPacket() throws SocketException {
  203. return new DatagramPacket(buf, 0, count, _multicastGroup);
  204. }
  205. }
  206. private NSNotification readNotification(DataInputStream dis) throws IOException {
  207. short nameLen = dis.readShort();
  208. byte[] nameBytes = new byte[nameLen];
  209. dis.readFully(nameBytes);
  210. short objectLen = dis.readShort();
  211. byte[] objectBytes = new byte[objectLen];
  212. dis.readFully(objectBytes);
  213. short userInfoLen = dis.readShort();
  214. NSMutableDictionary userInfo = new NSMutableDictionary();
  215. for (int i = 0; i < userInfoLen; i++) {
  216. short keyLen = dis.readShort();
  217. byte[] keyBytes = new byte[keyLen];
  218. dis.readFully(keyBytes);
  219. short valueLen = dis.readShort();
  220. byte[] valueBytes = new byte[valueLen];
  221. dis.readFully(valueBytes);
  222. userInfo.setObjectForKey(new String(valueBytes), new String(keyBytes));
  223. }
  224. NSNotification notification = new NSNotification(new String(nameBytes), null, userInfo);
  225. return notification;
  226. }
  227. private void writeNotification(NSNotification notification, DataOutputStream dos) throws IOException {
  228. byte[] name = notification.name().getBytes();
  229. dos.writeShort(name.length);
  230. dos.write(name);
  231. byte[] object = new byte[0];
  232. dos.writeShort(object.length);
  233. dos.write(object);
  234. NSDictionary userInfo = notification.userInfo();
  235. if (userInfo == null) {
  236. userInfo = NSDictionary.EmptyDictionary;
  237. }
  238. dos.writeShort(userInfo.count());
  239. for (Object key : userInfo.allKeys()) {
  240. byte[] keyBytes = key.toString().getBytes();
  241. byte[] valueBytes = userInfo.objectForKey(key).toString().getBytes();
  242. dos.writeShort(keyBytes.length);
  243. dos.write(keyBytes);
  244. dos.writeShort(valueBytes.length);
  245. dos.write(valueBytes);
  246. }
  247. dos.flush();
  248. if (dos.size() > _maxPacketSize) {
  249. throw new IllegalArgumentException("More than " + _maxPacketSize + " bytes");
  250. }
  251. }
  252. }
  253. public static ERXRemoteNotificationCenter defaultCenter() {
  254. if (_sharedInstance == null) {
  255. synchronized (ERXRemoteNotificationCenter.class) {
  256. if (_sharedInstance == null) {
  257. try {
  258. _sharedInstance = new SimpleCenter();
  259. }
  260. catch (IOException e) {
  261. throw NSForwardException._runtimeExceptionForThrowable(e);
  262. }
  263. }
  264. }
  265. }
  266. return _sharedInstance;
  267. }
  268. /**
  269. * Set the default center
  270. * @param center the notification center to use as default
  271. */
  272. public static void setDefaultCenter(ERXRemoteNotificationCenter center) {
  273. _sharedInstance = center;
  274. }
  275. /**
  276. * Post a notification to the local app only.
  277. * @param notification the notification
  278. */
  279. public void postLocalNotification(NSNotification notification) {
  280. super.postNotification(notification);
  281. }
  282. /**
  283. * Post a notification to the remote listeners.
  284. * @param notification the notification
  285. */
  286. protected abstract void postRemoteNotification(NSNotification notification);
  287. /**
  288. * Overridden to call {@link #postRemoteNotification(NSNotification)}.
  289. */
  290. @Override
  291. public void postNotification(NSNotification notification) {
  292. postRemoteNotification(notification);
  293. }
  294. }