src/jdk.jpackager.runtime/share/classes/jdk/jpackager/runtime/singleton/SingleInstanceImpl.java
branchJDK-8200758-branch
changeset 57017 1b08af362a30
parent 56982 e094d5483bd6
child 57028 51cc1a1f91f3
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackager.runtime/share/classes/jdk/jpackager/runtime/singleton/SingleInstanceImpl.java	Mon Nov 05 17:32:00 2018 -0500
@@ -0,0 +1,455 @@
+/*
+ * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.jpackager.runtime.singleton;
+
+import java.awt.Desktop;
+import java.awt.desktop.OpenFilesHandler;
+import java.awt.desktop.OpenFilesEvent;
+import java.net.ServerSocket;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.io.File;
+import java.io.PrintStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.List;
+import java.security.PrivilegedAction;
+import java.security.AccessController;
+import java.security.SecureRandom;
+
+
+class SingleInstanceImpl {
+
+    static final String SI_FILEDIR = getTmpDir() + File.separator
+           + "si" + File.separator;
+    static final String SI_MAGICWORD = "jpackager.singleinstance.init";
+    static final String SI_ACK = "jpackager.singleinstance.ack";
+    static final String SI_STOP = "jpackager.singleinstance.stop";
+    static final String SI_EOF = "jpackager.singleinstance.EOF";
+
+    private final ArrayList<SingleInstanceListener> siListeners =
+            new ArrayList<>();
+    private SingleInstanceServer siServer;
+
+    private static final SecureRandom random = new SecureRandom();
+    private static volatile boolean serverStarted = false;
+    private static int randomNumber;
+
+    private final Object lock = new Object();
+
+    static String getSingleInstanceFilePrefix(final String stringId) {
+        String filePrefix = stringId.replace('/','_');
+        filePrefix = filePrefix.replace(':','_');
+        return filePrefix;
+    }
+
+    static String getTmpDir() {
+        String os = System.getProperty("os.name").toLowerCase();
+        if (os.contains("win")) {
+            return System.getProperty("user.home")
+                    + "\\AppData\\LocalLow\\Sun\\Java\\JPackager\\tmp";
+        } else if (os.contains("mac") || os.contains("os x")) {
+            return System.getProperty("user.home")
+                    + "/Library/Application Support/Oracle/Java/JPackager/tmp";
+        } else if (os.contains("nix") || os.contains("nux")
+                || os.contains("aix")) {
+            return System.getProperty("user.home") + "/.java/jpackager/tmp";
+        }
+
+        return System.getProperty("java.io.tmpdir");
+    }
+
+    void addSingleInstanceListener(SingleInstanceListener sil, String id) {
+
+        if (sil == null || id == null) {
+            return;
+        }
+
+        // start a new server thread for this unique id
+        // first time
+        synchronized (lock) {
+            if (!serverStarted) {
+                SingleInstanceService.trace("unique id: " + id);
+                try {
+                    siServer = new SingleInstanceServer(id);
+                    siServer.start();
+                } catch (Exception e) {
+                    SingleInstanceService.trace(
+                            "addSingleInstanceListener failed");
+                    SingleInstanceService.trace(e);
+                    return; // didn't start
+                }
+                serverStarted = true;
+            }
+        }
+
+        synchronized (siListeners) {
+            // add the sil to the arrayList
+            if (!siListeners.contains(sil)) {
+                siListeners.add(sil);
+            }
+        }
+    }
+
+    class SingleInstanceServer {
+
+        private final SingleInstanceServerRunnable runnable;
+        private final Thread thread;
+
+        SingleInstanceServer(SingleInstanceServerRunnable runnable)
+                throws IOException {
+            thread = new Thread(null, runnable, "JPackagerSIThread",
+                                0, false);
+            thread.setDaemon(true);
+            this.runnable = runnable;
+        }
+
+        SingleInstanceServer(String stringId) throws IOException {
+            this(new SingleInstanceServerRunnable(stringId));
+        }
+
+        int getPort() {
+            return runnable.getPort();
+        }
+
+        void start() {
+            thread.start();
+        }
+    }
+
+    private class SingleInstanceServerRunnable implements Runnable {
+
+        ServerSocket ss;
+        int port;
+        String stringId;
+        String[] arguments;
+
+        int getPort() {
+            return port;
+        }
+
+        SingleInstanceServerRunnable(String id) throws IOException {
+            stringId = id;
+
+            // open a free ServerSocket
+            ss = null;
+
+            // we should bind the server to the local InetAddress 127.0.0.1
+            // port number is automatically allocated for current SI
+            ss = new ServerSocket(0, 0, InetAddress.getByName("127.0.0.1"));
+
+            // get the port number
+            port = ss.getLocalPort();
+            SingleInstanceService.trace("server port at: " + port);
+
+            // create the single instance file with canonical home and port num
+            createSingleInstanceFile(stringId, port);
+        }
+
+        private String getSingleInstanceFilename(final String id,
+                final int port) {
+            String name = SI_FILEDIR + getSingleInstanceFilePrefix(id)
+                    + "_" + port;
+            SingleInstanceService.trace("getSingleInstanceFilename: " + name);
+            return name;
+        }
+
+        private void removeSingleInstanceFile(final String id, final int port) {
+            new File(getSingleInstanceFilename(id, port)).delete();
+            SingleInstanceService.trace("removed SingleInstanceFile: "
+                                        + getSingleInstanceFilename(id, port));
+        }
+
+        private void createSingleInstanceFile(final String id, final int port) {
+            String filename = getSingleInstanceFilename(id, port);
+            final File siFile = new File(filename);
+            final File siDir = new File(SI_FILEDIR);
+            AccessController.doPrivileged(new PrivilegedAction<Void>() {
+                @Override
+                public Void run() {
+                    siDir.mkdirs();
+                    String[] fList = siDir.list();
+                    if (fList != null) {
+                        String prefix = getSingleInstanceFilePrefix(id);
+                        for (String file : fList) {
+                            // if file with the same prefix exist, remove it
+                            if (file.startsWith(prefix)) {
+                                SingleInstanceService.trace(
+                                        "file should be removed: "
+                                         + SI_FILEDIR + file);
+                                new File(SI_FILEDIR + file).delete();
+                            }
+                        }
+                    }
+
+                    PrintStream out = null;
+                    try {
+                        siFile.createNewFile();
+                        siFile.deleteOnExit();
+                        // write random number to single instance file
+                        out = new PrintStream(new FileOutputStream(siFile));
+                        randomNumber = random.nextInt();
+                        out.print(randomNumber);
+                    } catch (IOException ioe) {
+                        SingleInstanceService.trace(ioe);
+                    } finally {
+                        if (out != null) {
+                            out.close();
+                        }
+                    }
+                    return null;
+                }
+            });
+        }
+
+        @Override
+        public void run() {
+            // start sil to handle all the incoming request
+            // from the server port of the current url
+            AccessController.doPrivileged(new PrivilegedAction<Void>() {
+                @Override
+                public Void run() {
+                    List<String> recvArgs = new ArrayList<>();
+                    while (true) {
+                        recvArgs.clear();
+                        InputStream is = null;
+                        BufferedReader in = null;
+                        InputStreamReader isr = null;
+                        Socket s = null;
+                        String line = null;
+                        boolean sendAck = false;
+                        int port = -1;
+                        String charset = null;
+                        try {
+                            SingleInstanceService.trace("waiting connection");
+                            s = ss.accept();
+                            is = s.getInputStream();
+                            // read first byte for encoding type
+                            int encoding = is.read();
+                            if (encoding ==
+                                SingleInstanceService.ENCODING_PLATFORM) {
+                                charset = Charset.defaultCharset().name();
+                            } else if (encoding ==
+                                    SingleInstanceService.ENCODING_UNICODE) {
+                                charset =
+                                    SingleInstanceService.ENCODING_UNICODE_NAME;
+                            } else {
+                                SingleInstanceService.trace(
+                                    "SingleInstanceImpl - unknown encoding");
+                                return null;
+                            }
+                            isr = new InputStreamReader(is, charset);
+                            in = new BufferedReader(isr);
+                            // first read the random number
+                            line = in.readLine();
+                            if (line.equals(String.valueOf(randomNumber)) ==
+                                    false) {
+                                // random number does not match
+                                // should not happen
+                                // shutdown server socket
+                                removeSingleInstanceFile(stringId, port);
+                                ss.close();
+                                serverStarted = false;
+                                SingleInstanceService.trace("Unexpected Error, "
+                                        + "SingleInstanceService disabled");
+                                return null;
+                            } else {
+                                line = in.readLine();
+                                // no need to continue reading if MAGICWORD
+                                // did not come first
+                                SingleInstanceService.trace("recv: " + line);
+                                if (line.equals(SI_MAGICWORD)) {
+                                    SingleInstanceService.trace(
+                                            "got magic word.");
+                                    while (true) {
+                                        // Get input string
+                                        try {
+                                            line = in.readLine();
+                                            if (line != null
+                                                    && line.equals(SI_EOF)) {
+                                                // end of file reached
+                                                break;
+                                            } else {
+                                                recvArgs.add(line);
+                                            }
+                                        } catch (IOException ioe) {
+                                            SingleInstanceService.trace(ioe);
+                                        }
+                                    }
+                                    arguments = recvArgs.toArray(
+                                            new String[recvArgs.size()]);
+                                    sendAck = true;
+                                } else if (line.equals(SI_STOP)) {
+                                    // remove the SingleInstance file
+                                    removeSingleInstanceFile(stringId, port);
+                                    break;
+                                }
+                            }
+                        } catch (IOException ioe) {
+                            SingleInstanceService.trace(ioe);
+                        } finally {
+                            try {
+                                if (sendAck) {
+                                    // let the action listener handle the rest
+                                    for (String arg : arguments) {
+                                        SingleInstanceService.trace(
+                                                "Starting new instance with "
+                                                + "arguments: arg:" + arg);
+                                    }
+
+                                    performNewActivation(arguments);
+
+                                    // now the event is handled, we can send
+                                    // out the ACK
+                                    SingleInstanceService.trace(
+                                            "sending out ACK");
+                                    if (s != null) {
+                                        try (OutputStream os =
+                                                s.getOutputStream();
+                                            PrintStream ps = new PrintStream(os,
+                                                    true, charset)) {
+                                            // send OK (ACK)
+                                            ps.println(SI_ACK);
+                                            ps.flush();
+                                        }
+                                    }
+                                }
+
+                                if (in != null) {
+                                    in.close();
+                                }
+
+                                if (isr != null) {
+                                    isr.close();
+                                }
+
+                                if (is != null) {
+                                    is.close();
+                                }
+
+                                if (s != null) {
+                                    s.close();
+                                }
+                            } catch (IOException ioe) {
+                                SingleInstanceService.trace(ioe);
+                            }
+                        }
+                    }
+                    return null;
+                }
+            });
+        }
+    }
+
+    private void performNewActivation(final String[] args) {
+        // enumerate the sil list and call
+        // each sil with arguments
+        @SuppressWarnings("unchecked")
+        ArrayList<SingleInstanceListener> silal =
+                (ArrayList<SingleInstanceListener>)siListeners.clone();
+        silal.forEach(sil -> sil.newActivation(args));
+    }
+
+    void setOpenFileHandler() {
+        String os = System.getProperty("os.name").toLowerCase();
+        if (!os.contains("mac") && !os.contains("os x")) {
+            return;
+        }
+
+        Desktop.getDesktop().setOpenFileHandler(new OpenFilesHandler() {
+            @Override
+            public void openFiles(OpenFilesEvent e) {
+                List<String> arguments = new ArrayList<>();
+                e.getFiles().forEach(file -> arguments.add(file.toString()));
+                performNewActivation(arguments.toArray(
+                                    new String[arguments.size()]));
+            }
+        });
+    }
+
+    void removeSingleInstanceListener(SingleInstanceListener sil) {
+        if (sil == null) {
+            return;
+        }
+
+        synchronized (siListeners) {
+
+            if (!siListeners.remove(sil)) {
+                return;
+            }
+
+            if (siListeners.isEmpty()) {
+                 AccessController.doPrivileged(new PrivilegedAction<Void>() {
+                    @Override
+                    public Void run() {
+                        // stop server
+                        Socket socket = null;
+                        PrintStream out = null;
+                        OutputStream os = null;
+                        try {
+                            socket = new Socket("127.0.0.1",
+                                    siServer.getPort());
+                            os = socket.getOutputStream();
+                            byte[] encoding = new byte[1];
+                            encoding[0] =
+                                    SingleInstanceService.ENCODING_PLATFORM;
+                            os.write(encoding);
+                            String charset = Charset.defaultCharset().name();
+                            out = new PrintStream(os, true, charset);
+                            out.println(randomNumber);
+                            out.println(SingleInstanceImpl.SI_STOP);
+                            out.flush();
+                            serverStarted = false;
+                        } catch (IOException ioe) {
+                            SingleInstanceService.trace(ioe);
+                        } finally {
+                            try {
+                                if (out != null) {
+                                    out.close();
+                                }
+                                if (os != null) {
+                                    os.close();
+                                }
+                                if (socket != null) {
+                                    socket.close();
+                                }
+                            } catch (IOException ioe) {
+                                SingleInstanceService.trace(ioe);
+                            }
+                        }
+                        return null;
+                    }
+               });
+            }
+        }
+    }
+}