1 /*
   2  * Copyright (c) 2012, 2014, Oracle and/or its affiliates.
   3  * All rights reserved. Use is subject to license terms.
   4  *
   5  * This file is available and licensed under the following license:
   6  *
   7  * Redistribution and use in source and binary forms, with or without
   8  * modification, are permitted provided that the following conditions
   9  * are met:
  10  *
  11  *  - Redistributions of source code must retain the above copyright
  12  *    notice, this list of conditions and the following disclaimer.
  13  *  - Redistributions in binary form must reproduce the above copyright
  14  *    notice, this list of conditions and the following disclaimer in
  15  *    the documentation and/or other materials provided with the distribution.
  16  *  - Neither the name of Oracle Corporation nor the names of its
  17  *    contributors may be used to endorse or promote products derived
  18  *    from this software without specific prior written permission.
  19  *
  20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  24  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  31  */
  32 package com.oracle.javafx.scenebuilder.app.util;
  33 
  34 import java.io.ByteArrayInputStream;
  35 import java.io.ByteArrayOutputStream;
  36 import java.io.IOException;
  37 import java.io.ObjectInputStream;
  38 import java.io.ObjectOutputStream;
  39 import java.io.Serializable;
  40 import java.nio.file.AccessDeniedException;
  41 import java.nio.file.Files;
  42 import java.nio.file.Path;
  43 import java.nio.file.Paths;
  44 import java.nio.file.StandardCopyOption;
  45 
  46 /**
  47  * This class implements an simple IPC.
  48  *
  49  * If n processes start together and performs:
  50  *      1) MessageBox mb = new MessageBox()
  51  *      2) boolean grabbed = mb.grab(...)
  52  * only one of the n processed will get grabbed = true : it's message box owner.
  53  *
  54  * Other processes (which got grabbed == false) can then perform:
  55  *      3) mb.sendMessage(myMessage)
  56  *
  57  * The message box owner will then receive the messages through
  58  * its Delegate instance.
  59  *
  60  * @param <T>
  61  */
  62 public class MessageBox<T extends Serializable> {
  63 
  64     public static final long NAP_TIME = 100; // ms
  65 
  66     final private String folder;
  67     final private Class<T> messageClass;
  68     final int pollingTime; // milliseconds
  69     final Path messageFile;
  70     final FileMutex boxMutex;
  71     final FileMutex messageMutex;
  72 
  73     private PollingThread<T> pollingThread;
  74     private Delegate<T> delegate;
  75 
  76     public MessageBox(String folder, Class<T> messageClass, int pollingTime) {
  77         assert folder != null;
  78         assert messageClass != null;
  79         assert pollingTime > 0;
  80 
  81         this.folder = folder;
  82         this.messageClass = messageClass;
  83         this.pollingTime = pollingTime;
  84         this.messageFile = Paths.get(folder, "message.dat"); //NOI18N
  85         this.boxMutex = new FileMutex(Paths.get(folder, "box.mtx")); //NOI18N
  86         this.messageMutex = new FileMutex(Paths.get(folder,"message.mtx")); //NOI18N
  87 
  88         if (Files.exists(Paths.get(folder)) == false) {
  89             throw new IllegalArgumentException(folder + " does not exist"); //NOI18N
  90         }
  91     }
  92 
  93     public String getFolder() {
  94         return folder;
  95     }
  96 
  97     public boolean grab(Delegate<T> delegate)
  98     throws IOException {
  99         assert boxMutex.isLocked() == false;
 100         assert pollingThread == null;
 101         assert delegate != null;
 102 
 103         if (boxMutex.tryLock()) {
 104             this.delegate = delegate;
 105             this.pollingThread = new PollingThread<>(this);
 106             this.pollingThread.setDaemon(true);
 107             this.pollingThread.start();
 108         }
 109 
 110         return boxMutex.isLocked();
 111     }
 112 
 113 
 114     public void release() {
 115         assert boxMutex.isLocked();
 116         assert pollingThread != null;
 117         assert pollingThread.isAlive();
 118 
 119         pollingThread.interrupt();
 120         pollingThread = null;
 121 
 122         try {
 123             boxMutex.unlock();
 124         } catch(IOException x) {
 125             // Strange
 126             x.printStackTrace();
 127         }
 128     }
 129 
 130 
 131     public void sendMessage(T message) throws IOException, InterruptedException {
 132         assert boxMutex.isLocked() == false;
 133         assert messageMutex.isLocked() == false;
 134 
 135         final Path transientFile = Files.createTempFile(Paths.get(folder), null, null);
 136         Files.write(transientFile, serializeMessage(message));
 137 
 138         messageMutex.lock(100L * pollingTime);
 139         boolean retry;
 140         int accessDeniedCount = 0;
 141         do {
 142             if (Files.exists(messageFile) == false) {
 143                 try {
 144                     Files.move(transientFile, messageFile, StandardCopyOption.ATOMIC_MOVE);
 145                     retry = false;
 146                 } catch(AccessDeniedException x) {
 147                     // Sometime on Windows, move is denied (?).
 148                     // So we retry a few times...
 149                     if (accessDeniedCount++ <= 10) {
 150                         retry = true;
 151                     } else {
 152                         throw x;
 153                     }
 154                 }
 155             } else {
 156                 retry = true;
 157             }
 158             if (retry) {
 159                 Thread.sleep(NAP_TIME);
 160             }
 161         } while (retry);
 162         messageMutex.unlock();
 163     }
 164 
 165     public interface Delegate<T> {
 166         public void messageBoxDidGetMessage(T message);
 167         public void messageBoxDidCatchException(Exception x);
 168     }
 169 
 170     public Path getMessagePath() {
 171         return messageFile;
 172     }
 173 
 174     public Path getBoxMutexPath() {
 175         return boxMutex.getLockFile();
 176     }
 177 
 178     public Path getMessageMutexPath() {
 179         return messageMutex.getLockFile();
 180     }
 181 
 182 
 183     /*
 184      * Private
 185      */
 186 
 187     private byte[] serializeMessage(T message) throws IOException {
 188         final byte[] result;
 189 
 190         try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
 191             try (ObjectOutputStream oos = new ObjectOutputStream(bos)) {
 192                 oos.writeObject(message);
 193                 result = bos.toByteArray();
 194             }
 195         }
 196 
 197         return result;
 198     }
 199 
 200 
 201     private T unserializeMessage(byte[] bytes) throws IOException {
 202         final T result;
 203 
 204         try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes)) {
 205             try (ObjectInputStream ois = new ObjectInputStream(bis)) {
 206                 try {
 207                     result = messageClass.cast(ois.readObject());
 208                 } catch(ClassNotFoundException x) {
 209                     // Strange
 210                     throw new IOException(x);
 211                 }
 212             }
 213         }
 214 
 215         return result;
 216     }
 217 
 218 
 219     private static class PollingThread<T extends Serializable> extends Thread {
 220 
 221         private final MessageBox<T> messageBox;
 222 
 223         public PollingThread(MessageBox<T> messageBox) {
 224             super("MessageBox[" + messageBox.getFolder() + "]"); //NOI18N
 225             this.messageBox = messageBox;
 226         }
 227 
 228         @Override
 229         public void run() {
 230 
 231             try {
 232                 do {
 233                     if (Files.exists(messageBox.messageFile)) {
 234                         try {
 235                             final byte[] messageBytes = Files.readAllBytes(messageBox.messageFile);
 236                             final T message = messageBox.unserializeMessage(messageBytes);
 237                             messageBox.delegate.messageBoxDidGetMessage(message);
 238                         } catch(IOException x) {
 239                             messageBox.delegate.messageBoxDidCatchException(x);
 240                         } finally {
 241                             try {
 242                                 Files.delete(messageBox.messageFile);
 243                             } catch(IOException x) {
 244                                 messageBox.delegate.messageBoxDidCatchException(x);
 245                             }
 246                         }
 247                         Thread.sleep(NAP_TIME);
 248                     } else {
 249                         Thread.sleep(messageBox.pollingTime);
 250                     }
 251                 } while (true);
 252 
 253             } catch(InterruptedException x) {
 254             }
 255         }
 256     }
 257 }