src/jdk.jpackager.runtime/share/classes/jdk/jpackager/runtime/singleton/SingleInstanceImpl.java
--- /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;
+ }
+ });
+ }
+ }
+ }
+}