src/jdk.jpackager.runtime/share/classes/jdk/jpackager/runtime/singleton/SingleInstanceImpl.java
branchJDK-8200758-branch
changeset 57017 1b08af362a30
parent 56982 e094d5483bd6
child 57028 51cc1a1f91f3
equal deleted inserted replaced
57016:f63f13da91c0 57017:1b08af362a30
       
     1 /*
       
     2  * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Oracle designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Oracle in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    22  * or visit www.oracle.com if you need additional information or have any
       
    23  * questions.
       
    24  */
       
    25 
       
    26 package jdk.jpackager.runtime.singleton;
       
    27 
       
    28 import java.awt.Desktop;
       
    29 import java.awt.desktop.OpenFilesHandler;
       
    30 import java.awt.desktop.OpenFilesEvent;
       
    31 import java.net.ServerSocket;
       
    32 import java.net.InetAddress;
       
    33 import java.net.Socket;
       
    34 import java.io.File;
       
    35 import java.io.PrintStream;
       
    36 import java.io.FileOutputStream;
       
    37 import java.io.IOException;
       
    38 import java.io.InputStream;
       
    39 import java.io.BufferedReader;
       
    40 import java.io.InputStreamReader;
       
    41 import java.io.OutputStream;
       
    42 import java.nio.charset.Charset;
       
    43 import java.util.ArrayList;
       
    44 import java.util.List;
       
    45 import java.security.PrivilegedAction;
       
    46 import java.security.AccessController;
       
    47 import java.security.SecureRandom;
       
    48 
       
    49 
       
    50 class SingleInstanceImpl {
       
    51 
       
    52     static final String SI_FILEDIR = getTmpDir() + File.separator
       
    53            + "si" + File.separator;
       
    54     static final String SI_MAGICWORD = "jpackager.singleinstance.init";
       
    55     static final String SI_ACK = "jpackager.singleinstance.ack";
       
    56     static final String SI_STOP = "jpackager.singleinstance.stop";
       
    57     static final String SI_EOF = "jpackager.singleinstance.EOF";
       
    58 
       
    59     private final ArrayList<SingleInstanceListener> siListeners =
       
    60             new ArrayList<>();
       
    61     private SingleInstanceServer siServer;
       
    62 
       
    63     private static final SecureRandom random = new SecureRandom();
       
    64     private static volatile boolean serverStarted = false;
       
    65     private static int randomNumber;
       
    66 
       
    67     private final Object lock = new Object();
       
    68 
       
    69     static String getSingleInstanceFilePrefix(final String stringId) {
       
    70         String filePrefix = stringId.replace('/','_');
       
    71         filePrefix = filePrefix.replace(':','_');
       
    72         return filePrefix;
       
    73     }
       
    74 
       
    75     static String getTmpDir() {
       
    76         String os = System.getProperty("os.name").toLowerCase();
       
    77         if (os.contains("win")) {
       
    78             return System.getProperty("user.home")
       
    79                     + "\\AppData\\LocalLow\\Sun\\Java\\JPackager\\tmp";
       
    80         } else if (os.contains("mac") || os.contains("os x")) {
       
    81             return System.getProperty("user.home")
       
    82                     + "/Library/Application Support/Oracle/Java/JPackager/tmp";
       
    83         } else if (os.contains("nix") || os.contains("nux")
       
    84                 || os.contains("aix")) {
       
    85             return System.getProperty("user.home") + "/.java/jpackager/tmp";
       
    86         }
       
    87 
       
    88         return System.getProperty("java.io.tmpdir");
       
    89     }
       
    90 
       
    91     void addSingleInstanceListener(SingleInstanceListener sil, String id) {
       
    92 
       
    93         if (sil == null || id == null) {
       
    94             return;
       
    95         }
       
    96 
       
    97         // start a new server thread for this unique id
       
    98         // first time
       
    99         synchronized (lock) {
       
   100             if (!serverStarted) {
       
   101                 SingleInstanceService.trace("unique id: " + id);
       
   102                 try {
       
   103                     siServer = new SingleInstanceServer(id);
       
   104                     siServer.start();
       
   105                 } catch (Exception e) {
       
   106                     SingleInstanceService.trace(
       
   107                             "addSingleInstanceListener failed");
       
   108                     SingleInstanceService.trace(e);
       
   109                     return; // didn't start
       
   110                 }
       
   111                 serverStarted = true;
       
   112             }
       
   113         }
       
   114 
       
   115         synchronized (siListeners) {
       
   116             // add the sil to the arrayList
       
   117             if (!siListeners.contains(sil)) {
       
   118                 siListeners.add(sil);
       
   119             }
       
   120         }
       
   121     }
       
   122 
       
   123     class SingleInstanceServer {
       
   124 
       
   125         private final SingleInstanceServerRunnable runnable;
       
   126         private final Thread thread;
       
   127 
       
   128         SingleInstanceServer(SingleInstanceServerRunnable runnable)
       
   129                 throws IOException {
       
   130             thread = new Thread(null, runnable, "JPackagerSIThread",
       
   131                                 0, false);
       
   132             thread.setDaemon(true);
       
   133             this.runnable = runnable;
       
   134         }
       
   135 
       
   136         SingleInstanceServer(String stringId) throws IOException {
       
   137             this(new SingleInstanceServerRunnable(stringId));
       
   138         }
       
   139 
       
   140         int getPort() {
       
   141             return runnable.getPort();
       
   142         }
       
   143 
       
   144         void start() {
       
   145             thread.start();
       
   146         }
       
   147     }
       
   148 
       
   149     private class SingleInstanceServerRunnable implements Runnable {
       
   150 
       
   151         ServerSocket ss;
       
   152         int port;
       
   153         String stringId;
       
   154         String[] arguments;
       
   155 
       
   156         int getPort() {
       
   157             return port;
       
   158         }
       
   159 
       
   160         SingleInstanceServerRunnable(String id) throws IOException {
       
   161             stringId = id;
       
   162 
       
   163             // open a free ServerSocket
       
   164             ss = null;
       
   165 
       
   166             // we should bind the server to the local InetAddress 127.0.0.1
       
   167             // port number is automatically allocated for current SI
       
   168             ss = new ServerSocket(0, 0, InetAddress.getByName("127.0.0.1"));
       
   169 
       
   170             // get the port number
       
   171             port = ss.getLocalPort();
       
   172             SingleInstanceService.trace("server port at: " + port);
       
   173 
       
   174             // create the single instance file with canonical home and port num
       
   175             createSingleInstanceFile(stringId, port);
       
   176         }
       
   177 
       
   178         private String getSingleInstanceFilename(final String id,
       
   179                 final int port) {
       
   180             String name = SI_FILEDIR + getSingleInstanceFilePrefix(id)
       
   181                     + "_" + port;
       
   182             SingleInstanceService.trace("getSingleInstanceFilename: " + name);
       
   183             return name;
       
   184         }
       
   185 
       
   186         private void removeSingleInstanceFile(final String id, final int port) {
       
   187             new File(getSingleInstanceFilename(id, port)).delete();
       
   188             SingleInstanceService.trace("removed SingleInstanceFile: "
       
   189                                         + getSingleInstanceFilename(id, port));
       
   190         }
       
   191 
       
   192         private void createSingleInstanceFile(final String id, final int port) {
       
   193             String filename = getSingleInstanceFilename(id, port);
       
   194             final File siFile = new File(filename);
       
   195             final File siDir = new File(SI_FILEDIR);
       
   196             AccessController.doPrivileged(new PrivilegedAction<Void>() {
       
   197                 @Override
       
   198                 public Void run() {
       
   199                     siDir.mkdirs();
       
   200                     String[] fList = siDir.list();
       
   201                     if (fList != null) {
       
   202                         String prefix = getSingleInstanceFilePrefix(id);
       
   203                         for (String file : fList) {
       
   204                             // if file with the same prefix exist, remove it
       
   205                             if (file.startsWith(prefix)) {
       
   206                                 SingleInstanceService.trace(
       
   207                                         "file should be removed: "
       
   208                                          + SI_FILEDIR + file);
       
   209                                 new File(SI_FILEDIR + file).delete();
       
   210                             }
       
   211                         }
       
   212                     }
       
   213 
       
   214                     PrintStream out = null;
       
   215                     try {
       
   216                         siFile.createNewFile();
       
   217                         siFile.deleteOnExit();
       
   218                         // write random number to single instance file
       
   219                         out = new PrintStream(new FileOutputStream(siFile));
       
   220                         randomNumber = random.nextInt();
       
   221                         out.print(randomNumber);
       
   222                     } catch (IOException ioe) {
       
   223                         SingleInstanceService.trace(ioe);
       
   224                     } finally {
       
   225                         if (out != null) {
       
   226                             out.close();
       
   227                         }
       
   228                     }
       
   229                     return null;
       
   230                 }
       
   231             });
       
   232         }
       
   233 
       
   234         @Override
       
   235         public void run() {
       
   236             // start sil to handle all the incoming request
       
   237             // from the server port of the current url
       
   238             AccessController.doPrivileged(new PrivilegedAction<Void>() {
       
   239                 @Override
       
   240                 public Void run() {
       
   241                     List<String> recvArgs = new ArrayList<>();
       
   242                     while (true) {
       
   243                         recvArgs.clear();
       
   244                         InputStream is = null;
       
   245                         BufferedReader in = null;
       
   246                         InputStreamReader isr = null;
       
   247                         Socket s = null;
       
   248                         String line = null;
       
   249                         boolean sendAck = false;
       
   250                         int port = -1;
       
   251                         String charset = null;
       
   252                         try {
       
   253                             SingleInstanceService.trace("waiting connection");
       
   254                             s = ss.accept();
       
   255                             is = s.getInputStream();
       
   256                             // read first byte for encoding type
       
   257                             int encoding = is.read();
       
   258                             if (encoding ==
       
   259                                 SingleInstanceService.ENCODING_PLATFORM) {
       
   260                                 charset = Charset.defaultCharset().name();
       
   261                             } else if (encoding ==
       
   262                                     SingleInstanceService.ENCODING_UNICODE) {
       
   263                                 charset =
       
   264                                     SingleInstanceService.ENCODING_UNICODE_NAME;
       
   265                             } else {
       
   266                                 SingleInstanceService.trace(
       
   267                                     "SingleInstanceImpl - unknown encoding");
       
   268                                 return null;
       
   269                             }
       
   270                             isr = new InputStreamReader(is, charset);
       
   271                             in = new BufferedReader(isr);
       
   272                             // first read the random number
       
   273                             line = in.readLine();
       
   274                             if (line.equals(String.valueOf(randomNumber)) ==
       
   275                                     false) {
       
   276                                 // random number does not match
       
   277                                 // should not happen
       
   278                                 // shutdown server socket
       
   279                                 removeSingleInstanceFile(stringId, port);
       
   280                                 ss.close();
       
   281                                 serverStarted = false;
       
   282                                 SingleInstanceService.trace("Unexpected Error, "
       
   283                                         + "SingleInstanceService disabled");
       
   284                                 return null;
       
   285                             } else {
       
   286                                 line = in.readLine();
       
   287                                 // no need to continue reading if MAGICWORD
       
   288                                 // did not come first
       
   289                                 SingleInstanceService.trace("recv: " + line);
       
   290                                 if (line.equals(SI_MAGICWORD)) {
       
   291                                     SingleInstanceService.trace(
       
   292                                             "got magic word.");
       
   293                                     while (true) {
       
   294                                         // Get input string
       
   295                                         try {
       
   296                                             line = in.readLine();
       
   297                                             if (line != null
       
   298                                                     && line.equals(SI_EOF)) {
       
   299                                                 // end of file reached
       
   300                                                 break;
       
   301                                             } else {
       
   302                                                 recvArgs.add(line);
       
   303                                             }
       
   304                                         } catch (IOException ioe) {
       
   305                                             SingleInstanceService.trace(ioe);
       
   306                                         }
       
   307                                     }
       
   308                                     arguments = recvArgs.toArray(
       
   309                                             new String[recvArgs.size()]);
       
   310                                     sendAck = true;
       
   311                                 } else if (line.equals(SI_STOP)) {
       
   312                                     // remove the SingleInstance file
       
   313                                     removeSingleInstanceFile(stringId, port);
       
   314                                     break;
       
   315                                 }
       
   316                             }
       
   317                         } catch (IOException ioe) {
       
   318                             SingleInstanceService.trace(ioe);
       
   319                         } finally {
       
   320                             try {
       
   321                                 if (sendAck) {
       
   322                                     // let the action listener handle the rest
       
   323                                     for (String arg : arguments) {
       
   324                                         SingleInstanceService.trace(
       
   325                                                 "Starting new instance with "
       
   326                                                 + "arguments: arg:" + arg);
       
   327                                     }
       
   328 
       
   329                                     performNewActivation(arguments);
       
   330 
       
   331                                     // now the event is handled, we can send
       
   332                                     // out the ACK
       
   333                                     SingleInstanceService.trace(
       
   334                                             "sending out ACK");
       
   335                                     if (s != null) {
       
   336                                         try (OutputStream os =
       
   337                                                 s.getOutputStream();
       
   338                                             PrintStream ps = new PrintStream(os,
       
   339                                                     true, charset)) {
       
   340                                             // send OK (ACK)
       
   341                                             ps.println(SI_ACK);
       
   342                                             ps.flush();
       
   343                                         }
       
   344                                     }
       
   345                                 }
       
   346 
       
   347                                 if (in != null) {
       
   348                                     in.close();
       
   349                                 }
       
   350 
       
   351                                 if (isr != null) {
       
   352                                     isr.close();
       
   353                                 }
       
   354 
       
   355                                 if (is != null) {
       
   356                                     is.close();
       
   357                                 }
       
   358 
       
   359                                 if (s != null) {
       
   360                                     s.close();
       
   361                                 }
       
   362                             } catch (IOException ioe) {
       
   363                                 SingleInstanceService.trace(ioe);
       
   364                             }
       
   365                         }
       
   366                     }
       
   367                     return null;
       
   368                 }
       
   369             });
       
   370         }
       
   371     }
       
   372 
       
   373     private void performNewActivation(final String[] args) {
       
   374         // enumerate the sil list and call
       
   375         // each sil with arguments
       
   376         @SuppressWarnings("unchecked")
       
   377         ArrayList<SingleInstanceListener> silal =
       
   378                 (ArrayList<SingleInstanceListener>)siListeners.clone();
       
   379         silal.forEach(sil -> sil.newActivation(args));
       
   380     }
       
   381 
       
   382     void setOpenFileHandler() {
       
   383         String os = System.getProperty("os.name").toLowerCase();
       
   384         if (!os.contains("mac") && !os.contains("os x")) {
       
   385             return;
       
   386         }
       
   387 
       
   388         Desktop.getDesktop().setOpenFileHandler(new OpenFilesHandler() {
       
   389             @Override
       
   390             public void openFiles(OpenFilesEvent e) {
       
   391                 List<String> arguments = new ArrayList<>();
       
   392                 e.getFiles().forEach(file -> arguments.add(file.toString()));
       
   393                 performNewActivation(arguments.toArray(
       
   394                                     new String[arguments.size()]));
       
   395             }
       
   396         });
       
   397     }
       
   398 
       
   399     void removeSingleInstanceListener(SingleInstanceListener sil) {
       
   400         if (sil == null) {
       
   401             return;
       
   402         }
       
   403 
       
   404         synchronized (siListeners) {
       
   405 
       
   406             if (!siListeners.remove(sil)) {
       
   407                 return;
       
   408             }
       
   409 
       
   410             if (siListeners.isEmpty()) {
       
   411                  AccessController.doPrivileged(new PrivilegedAction<Void>() {
       
   412                     @Override
       
   413                     public Void run() {
       
   414                         // stop server
       
   415                         Socket socket = null;
       
   416                         PrintStream out = null;
       
   417                         OutputStream os = null;
       
   418                         try {
       
   419                             socket = new Socket("127.0.0.1",
       
   420                                     siServer.getPort());
       
   421                             os = socket.getOutputStream();
       
   422                             byte[] encoding = new byte[1];
       
   423                             encoding[0] =
       
   424                                     SingleInstanceService.ENCODING_PLATFORM;
       
   425                             os.write(encoding);
       
   426                             String charset = Charset.defaultCharset().name();
       
   427                             out = new PrintStream(os, true, charset);
       
   428                             out.println(randomNumber);
       
   429                             out.println(SingleInstanceImpl.SI_STOP);
       
   430                             out.flush();
       
   431                             serverStarted = false;
       
   432                         } catch (IOException ioe) {
       
   433                             SingleInstanceService.trace(ioe);
       
   434                         } finally {
       
   435                             try {
       
   436                                 if (out != null) {
       
   437                                     out.close();
       
   438                                 }
       
   439                                 if (os != null) {
       
   440                                     os.close();
       
   441                                 }
       
   442                                 if (socket != null) {
       
   443                                     socket.close();
       
   444                                 }
       
   445                             } catch (IOException ioe) {
       
   446                                 SingleInstanceService.trace(ioe);
       
   447                             }
       
   448                         }
       
   449                         return null;
       
   450                     }
       
   451                });
       
   452             }
       
   453         }
       
   454     }
       
   455 }