src/jdk.jpackage.runtime/share/classes/jdk/jpackage/runtime/singleton/SingleInstanceImpl.java
branchJDK-8200758-branch
changeset 57099 9a85a7a076ad
parent 57098 fd4868c5fca1
child 57100 489b1afd0ec8
equal deleted inserted replaced
57098:fd4868c5fca1 57099:9a85a7a076ad
     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.jpackage.runtime.singleton;
       
    27 
       
    28 import java.net.ServerSocket;
       
    29 import java.net.InetAddress;
       
    30 import java.net.Socket;
       
    31 import java.io.File;
       
    32 import java.io.PrintStream;
       
    33 import java.io.FileOutputStream;
       
    34 import java.io.IOException;
       
    35 import java.io.InputStream;
       
    36 import java.io.BufferedReader;
       
    37 import java.io.InputStreamReader;
       
    38 import java.io.OutputStream;
       
    39 import java.nio.charset.Charset;
       
    40 import java.util.ArrayList;
       
    41 import java.util.List;
       
    42 import java.security.PrivilegedAction;
       
    43 import java.security.AccessController;
       
    44 import java.security.SecureRandom;
       
    45 
       
    46 
       
    47 class SingleInstanceImpl {
       
    48 
       
    49     static final String SI_FILEDIR = getTmpDir() + File.separator
       
    50            + "si" + File.separator;
       
    51     static final String SI_MAGICWORD = "jpackage.singleinstance.init";
       
    52     static final String SI_ACK = "jpackage.singleinstance.ack";
       
    53     static final String SI_STOP = "jpackage.singleinstance.stop";
       
    54     static final String SI_EOF = "jpackage.singleinstance.EOF";
       
    55 
       
    56     private final ArrayList<SingleInstanceListener> siListeners =
       
    57             new ArrayList<>();
       
    58     private SingleInstanceServer siServer;
       
    59 
       
    60     private static final SecureRandom random = new SecureRandom();
       
    61     private static volatile boolean serverStarted = false;
       
    62     private static int randomNumber;
       
    63 
       
    64     private final Object lock = new Object();
       
    65 
       
    66     static String getSingleInstanceFilePrefix(final String stringId) {
       
    67         String filePrefix = stringId.replace('/','_');
       
    68         filePrefix = filePrefix.replace(':','_');
       
    69         return filePrefix;
       
    70     }
       
    71 
       
    72     static String getTmpDir() {
       
    73         String os = System.getProperty("os.name").toLowerCase();
       
    74         if (os.contains("win")) {
       
    75             return System.getProperty("user.home")
       
    76                     + "\\AppData\\Local\\Java\\JPackage\\tmp";
       
    77         } else if (os.contains("mac")) {
       
    78             return System.getProperty("user.home")
       
    79                     + "/Library/Application Support/Java/JPackage/tmp";
       
    80         } else {
       
    81             return System.getProperty("user.home") + "/.java/jpackage/tmp";
       
    82         }
       
    83     }
       
    84 
       
    85     void addSingleInstanceListener(SingleInstanceListener sil, String id) {
       
    86 
       
    87         if (sil == null || id == null) {
       
    88             return;
       
    89         }
       
    90 
       
    91         // start a new server thread for this unique id
       
    92         // first time
       
    93         synchronized (lock) {
       
    94             if (!serverStarted) {
       
    95                 SingleInstanceService.trace("unique id: " + id);
       
    96                 try {
       
    97                     siServer = new SingleInstanceServer(id);
       
    98                     siServer.start();
       
    99                 } catch (Exception e) {
       
   100                     SingleInstanceService.trace(
       
   101                             "addSingleInstanceListener failed");
       
   102                     SingleInstanceService.trace(e);
       
   103                     return; // didn't start
       
   104                 }
       
   105                 serverStarted = true;
       
   106             }
       
   107         }
       
   108 
       
   109         synchronized (siListeners) {
       
   110             // add the sil to the arrayList
       
   111             if (!siListeners.contains(sil)) {
       
   112                 siListeners.add(sil);
       
   113             }
       
   114         }
       
   115     }
       
   116 
       
   117     class SingleInstanceServer {
       
   118 
       
   119         private final SingleInstanceServerRunnable runnable;
       
   120         private final Thread thread;
       
   121 
       
   122         SingleInstanceServer(SingleInstanceServerRunnable runnable)
       
   123                 throws IOException {
       
   124             thread = new Thread(null, runnable, "JPackageSIThread",
       
   125                                 0, false);
       
   126             thread.setDaemon(true);
       
   127             this.runnable = runnable;
       
   128         }
       
   129 
       
   130         SingleInstanceServer(String stringId) throws IOException {
       
   131             this(new SingleInstanceServerRunnable(stringId));
       
   132         }
       
   133 
       
   134         int getPort() {
       
   135             return runnable.getPort();
       
   136         }
       
   137 
       
   138         void start() {
       
   139             thread.start();
       
   140         }
       
   141     }
       
   142 
       
   143     private class SingleInstanceServerRunnable implements Runnable {
       
   144 
       
   145         ServerSocket ss;
       
   146         int port;
       
   147         String stringId;
       
   148         String[] arguments;
       
   149 
       
   150         int getPort() {
       
   151             return port;
       
   152         }
       
   153 
       
   154         SingleInstanceServerRunnable(String id) throws IOException {
       
   155             stringId = id;
       
   156 
       
   157             // open a free ServerSocket
       
   158             ss = null;
       
   159 
       
   160             // we should bind the server to the local InetAddress 127.0.0.1
       
   161             // port number is automatically allocated for current SI
       
   162             ss = new ServerSocket(0, 0, InetAddress.getByName("127.0.0.1"));
       
   163 
       
   164             // get the port number
       
   165             port = ss.getLocalPort();
       
   166             SingleInstanceService.trace("server port at: " + port);
       
   167 
       
   168             // create the single instance file with canonical home and port num
       
   169             createSingleInstanceFile(stringId, port);
       
   170         }
       
   171 
       
   172         private String getSingleInstanceFilename(final String id,
       
   173                 final int port) {
       
   174             String name = SI_FILEDIR + getSingleInstanceFilePrefix(id)
       
   175                     + "_" + port;
       
   176             SingleInstanceService.trace("getSingleInstanceFilename: " + name);
       
   177             return name;
       
   178         }
       
   179 
       
   180         private void removeSingleInstanceFile(final String id, final int port) {
       
   181             new File(getSingleInstanceFilename(id, port)).delete();
       
   182             SingleInstanceService.trace("removed SingleInstanceFile: "
       
   183                                         + getSingleInstanceFilename(id, port));
       
   184         }
       
   185 
       
   186         private void createSingleInstanceFile(final String id, final int port) {
       
   187             String filename = getSingleInstanceFilename(id, port);
       
   188             final File siFile = new File(filename);
       
   189             final File siDir = new File(SI_FILEDIR);
       
   190             AccessController.doPrivileged(new PrivilegedAction<Void>() {
       
   191                 @Override
       
   192                 public Void run() {
       
   193                     siDir.mkdirs();
       
   194                     String[] fList = siDir.list();
       
   195                     if (fList != null) {
       
   196                         String prefix = getSingleInstanceFilePrefix(id);
       
   197                         for (String file : fList) {
       
   198                             // if file with the same prefix exist, remove it
       
   199                             if (file.startsWith(prefix)) {
       
   200                                 SingleInstanceService.trace(
       
   201                                         "file should be removed: "
       
   202                                          + SI_FILEDIR + file);
       
   203                                 new File(SI_FILEDIR + file).delete();
       
   204                             }
       
   205                         }
       
   206                     }
       
   207 
       
   208                     PrintStream out = null;
       
   209                     try {
       
   210                         siFile.createNewFile();
       
   211                         siFile.deleteOnExit();
       
   212                         // write random number to single instance file
       
   213                         out = new PrintStream(new FileOutputStream(siFile));
       
   214                         randomNumber = random.nextInt();
       
   215                         out.print(randomNumber);
       
   216                     } catch (IOException ioe) {
       
   217                         SingleInstanceService.trace(ioe);
       
   218                     } finally {
       
   219                         if (out != null) {
       
   220                             out.close();
       
   221                         }
       
   222                     }
       
   223                     return null;
       
   224                 }
       
   225             });
       
   226         }
       
   227 
       
   228         @Override
       
   229         public void run() {
       
   230             // start sil to handle all the incoming request
       
   231             // from the server port of the current url
       
   232             AccessController.doPrivileged(new PrivilegedAction<Void>() {
       
   233                 @Override
       
   234                 public Void run() {
       
   235                     List<String> recvArgs = new ArrayList<>();
       
   236                     while (true) {
       
   237                         recvArgs.clear();
       
   238                         InputStream is = null;
       
   239                         BufferedReader in = null;
       
   240                         InputStreamReader isr = null;
       
   241                         Socket s = null;
       
   242                         String line = null;
       
   243                         boolean sendAck = false;
       
   244                         int port = -1;
       
   245                         String charset = null;
       
   246                         try {
       
   247                             SingleInstanceService.trace("waiting connection");
       
   248                             s = ss.accept();
       
   249                             is = s.getInputStream();
       
   250                             // read first byte for encoding type
       
   251                             int encoding = is.read();
       
   252                             if (encoding ==
       
   253                                 SingleInstanceService.ENCODING_PLATFORM) {
       
   254                                 charset = Charset.defaultCharset().name();
       
   255                             } else if (encoding ==
       
   256                                     SingleInstanceService.ENCODING_UNICODE) {
       
   257                                 charset =
       
   258                                     SingleInstanceService.ENCODING_UNICODE_NAME;
       
   259                             } else {
       
   260                                 SingleInstanceService.trace(
       
   261                                     "SingleInstanceImpl - unknown encoding");
       
   262                                 return null;
       
   263                             }
       
   264                             isr = new InputStreamReader(is, charset);
       
   265                             in = new BufferedReader(isr);
       
   266                             // first read the random number
       
   267                             line = in.readLine();
       
   268                             if (line.equals(String.valueOf(randomNumber)) ==
       
   269                                     false) {
       
   270                                 // random number does not match
       
   271                                 // should not happen
       
   272                                 // shutdown server socket
       
   273                                 removeSingleInstanceFile(stringId, port);
       
   274                                 ss.close();
       
   275                                 serverStarted = false;
       
   276                                 SingleInstanceService.trace("Unexpected Error, "
       
   277                                         + "SingleInstanceService disabled");
       
   278                                 return null;
       
   279                             } else {
       
   280                                 line = in.readLine();
       
   281                                 // no need to continue reading if MAGICWORD
       
   282                                 // did not come first
       
   283                                 SingleInstanceService.trace("recv: " + line);
       
   284                                 if (line.equals(SI_MAGICWORD)) {
       
   285                                     SingleInstanceService.trace(
       
   286                                             "got magic word.");
       
   287                                     while (true) {
       
   288                                         // Get input string
       
   289                                         try {
       
   290                                             line = in.readLine();
       
   291                                             if (line != null
       
   292                                                     && line.equals(SI_EOF)) {
       
   293                                                 // end of file reached
       
   294                                                 break;
       
   295                                             } else {
       
   296                                                 recvArgs.add(line);
       
   297                                             }
       
   298                                         } catch (IOException ioe) {
       
   299                                             SingleInstanceService.trace(ioe);
       
   300                                         }
       
   301                                     }
       
   302                                     arguments = recvArgs.toArray(
       
   303                                             new String[recvArgs.size()]);
       
   304                                     sendAck = true;
       
   305                                 } else if (line.equals(SI_STOP)) {
       
   306                                     // remove the SingleInstance file
       
   307                                     removeSingleInstanceFile(stringId, port);
       
   308                                     break;
       
   309                                 }
       
   310                             }
       
   311                         } catch (IOException ioe) {
       
   312                             SingleInstanceService.trace(ioe);
       
   313                         } finally {
       
   314                             try {
       
   315                                 if (sendAck) {
       
   316                                     // let the action listener handle the rest
       
   317                                     for (String arg : arguments) {
       
   318                                         SingleInstanceService.trace(
       
   319                                                 "Starting new instance with "
       
   320                                                 + "arguments: arg:" + arg);
       
   321                                     }
       
   322 
       
   323                                     performNewActivation(arguments);
       
   324 
       
   325                                     // now the event is handled, we can send
       
   326                                     // out the ACK
       
   327                                     SingleInstanceService.trace(
       
   328                                             "sending out ACK");
       
   329                                     if (s != null) {
       
   330                                         try (OutputStream os =
       
   331                                                 s.getOutputStream();
       
   332                                             PrintStream ps = new PrintStream(os,
       
   333                                                     true, charset)) {
       
   334                                             // send OK (ACK)
       
   335                                             ps.println(SI_ACK);
       
   336                                             ps.flush();
       
   337                                         }
       
   338                                     }
       
   339                                 }
       
   340 
       
   341                                 if (in != null) {
       
   342                                     in.close();
       
   343                                 }
       
   344 
       
   345                                 if (isr != null) {
       
   346                                     isr.close();
       
   347                                 }
       
   348 
       
   349                                 if (is != null) {
       
   350                                     is.close();
       
   351                                 }
       
   352 
       
   353                                 if (s != null) {
       
   354                                     s.close();
       
   355                                 }
       
   356                             } catch (IOException ioe) {
       
   357                                 SingleInstanceService.trace(ioe);
       
   358                             }
       
   359                         }
       
   360                     }
       
   361                     return null;
       
   362                 }
       
   363             });
       
   364         }
       
   365     }
       
   366 
       
   367     private void performNewActivation(final String[] args) {
       
   368         // enumerate the sil list and call
       
   369         // each sil with arguments
       
   370         @SuppressWarnings("unchecked")
       
   371         ArrayList<SingleInstanceListener> silal =
       
   372                 (ArrayList<SingleInstanceListener>)siListeners.clone();
       
   373         silal.forEach(sil -> sil.newActivation(args));
       
   374     }
       
   375 
       
   376     void removeSingleInstanceListener(SingleInstanceListener sil) {
       
   377         if (sil == null) {
       
   378             return;
       
   379         }
       
   380 
       
   381         synchronized (siListeners) {
       
   382 
       
   383             if (!siListeners.remove(sil)) {
       
   384                 return;
       
   385             }
       
   386 
       
   387             if (siListeners.isEmpty()) {
       
   388                  AccessController.doPrivileged(new PrivilegedAction<Void>() {
       
   389                     @Override
       
   390                     public Void run() {
       
   391                         // stop server
       
   392                         Socket socket = null;
       
   393                         PrintStream out = null;
       
   394                         OutputStream os = null;
       
   395                         try {
       
   396                             socket = new Socket("127.0.0.1",
       
   397                                     siServer.getPort());
       
   398                             os = socket.getOutputStream();
       
   399                             byte[] encoding = new byte[1];
       
   400                             encoding[0] =
       
   401                                     SingleInstanceService.ENCODING_PLATFORM;
       
   402                             os.write(encoding);
       
   403                             String charset = Charset.defaultCharset().name();
       
   404                             out = new PrintStream(os, true, charset);
       
   405                             out.println(randomNumber);
       
   406                             out.println(SingleInstanceImpl.SI_STOP);
       
   407                             out.flush();
       
   408                             serverStarted = false;
       
   409                         } catch (IOException ioe) {
       
   410                             SingleInstanceService.trace(ioe);
       
   411                         } finally {
       
   412                             try {
       
   413                                 if (out != null) {
       
   414                                     out.close();
       
   415                                 }
       
   416                                 if (os != null) {
       
   417                                     os.close();
       
   418                                 }
       
   419                                 if (socket != null) {
       
   420                                     socket.close();
       
   421                                 }
       
   422                             } catch (IOException ioe) {
       
   423                                 SingleInstanceService.trace(ioe);
       
   424                             }
       
   425                         }
       
   426                         return null;
       
   427                     }
       
   428                });
       
   429             }
       
   430         }
       
   431     }
       
   432 }