1 /* 2 * Copyright (c) 2017, 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.packager.services.singleton; 27 28 import java.io.BufferedReader; 29 import java.io.File; 30 import java.io.FileReader; 31 import java.io.IOException; 32 import java.io.InputStreamReader; 33 import java.io.OutputStream; 34 import java.io.PrintStream; 35 import java.net.Socket; 36 import java.nio.charset.Charset; 37 38 /** 39 * The {@code SingleInstanceService} class provides public methods for using 40 * Single Instance functionality for Java Packager. To use these methods, 41 * the option named "-singleton" must be specified on javapackager command line. 42 * 43 * @since 10 44 */ 45 public class SingleInstanceService { 46 47 static private boolean DEBUG = false; 48 static private PrintStream DEBUG_STREAM = null; 49 50 static private int currPort; 51 static private String stringId = null; 52 static private String randomNumberString = null; 53 54 static private SingleInstanceImpl instance = null; 55 56 static final int ENCODING_PLATFORM = 1; 57 static final int ENCODING_UNICODE = 2; 58 59 static final String ENCODING_PLATFORM_NAME = "UTF-8"; 60 static final String ENCODING_UNICODE_NAME = "UTF-16LE"; 61 62 static final String APP_ID_PREFIX = "javapackager.si."; 63 64 private SingleInstanceService() {} 65 66 static void enableDebug(boolean enable, PrintStream stream) { 67 DEBUG = enable; 68 DEBUG_STREAM = stream; 69 } 70 71 static void trace(String message) { 72 if (DEBUG && DEBUG_STREAM != null) { 73 DEBUG_STREAM.println(message); 74 } 75 } 76 77 static void trace(Throwable t) { 78 if (DEBUG && DEBUG_STREAM != null) { 79 t.printStackTrace(DEBUG_STREAM); 80 } 81 } 82 83 /** 84 * Registers {@code SingleInstanceListener} for current process. 85 * If the {@code SingleInstanceListener} object is already registered, or 86 * {@code slistener} is {@code null}, then the registration is skipped. 87 * 88 * @param slistener the listener to handle the single instance behaviour. 89 */ 90 public static void registerSingleInstance(SingleInstanceListener slistener) { 91 registerSingleInstance(slistener, false); 92 } 93 94 /** 95 * Registers {@code SingleInstanceListener} for current process. 96 * If the {@code SingleInstanceListener} object is already registered, or 97 * {@code slistener} is {@code null}, then the registration is skipped. 98 * 99 * @param slistener the listener to handle the single instance behaviour. 100 * @param setFileHandler if {@code true}, the listener is notified when the 101 * application is asked to open a list of files. If OS is not MacOS, 102 * the parameter is ignored. 103 */ 104 public static void registerSingleInstance(SingleInstanceListener slistener, 105 boolean setFileHandler) { 106 String appId = APP_ID_PREFIX + ProcessHandle.current().pid(); 107 registerSingleInstanceForId(slistener, appId, setFileHandler); 108 } 109 110 static void registerSingleInstanceForId(SingleInstanceListener slistener, 111 String stringId, boolean setFileHandler) { 112 // register SingleInstanceListener for given Id 113 instance = new SingleInstanceImpl(); 114 instance.addSingleInstanceListener(slistener, stringId); 115 if (setFileHandler) { 116 instance.setOpenFileHandler(); 117 } 118 } 119 120 /** 121 * Unregisters {@code SingleInstanceListener} for current process. 122 * If the {@code SingleInstanceListener} object is not registered, or 123 * {@code slistener} is {@code null}, then the unregistration is skipped. 124 * 125 * @param slistener the listener for unregistering. 126 */ 127 public static void unregisterSingleInstance(SingleInstanceListener slistener) { 128 instance.removeSingleInstanceListener(slistener); 129 } 130 131 /** 132 * Returns true if single instance server is running for the id 133 */ 134 static boolean isServerRunning(String id) { 135 trace("isServerRunning ? : "+ id); 136 File siDir = new File(SingleInstanceImpl.SI_FILEDIR); 137 String[] fList = siDir.list(); 138 if (fList != null) { 139 String prefix = SingleInstanceImpl.getSingleInstanceFilePrefix(id + 140 getSessionSpecificString()); 141 for (String file : fList) { 142 trace("isServerRunning: " + file); 143 trace("\t sessionString: " + getSessionSpecificString()); 144 trace("\t SingleInstanceFilePrefix: " + prefix); 145 // if file with the same prefix already exist, server is running 146 if (file.startsWith(prefix)) { 147 try { 148 currPort = Integer.parseInt( 149 file.substring(file.lastIndexOf('_') + 1)); 150 trace("isServerRunning: " + file + ": port: " + currPort); 151 } catch (NumberFormatException nfe) { 152 trace("isServerRunning: " + file + ": port parsing failed"); 153 trace(nfe); 154 return false; 155 } 156 157 trace("Server running at port: " + currPort); 158 File siFile = new File(SingleInstanceImpl.SI_FILEDIR, file); 159 160 // get random number from single instance file 161 try (BufferedReader br = new BufferedReader(new FileReader(siFile))) { 162 randomNumberString = br.readLine(); 163 trace("isServerRunning: " + file + ": magic: " + randomNumberString); 164 } catch (IOException ioe ) { 165 trace("isServerRunning: " + file + ": reading magic failed"); 166 trace(ioe); 167 } 168 trace("isServerRunning: " + file + ": setting id - OK"); 169 stringId = id; 170 return true; 171 } else { 172 trace("isServerRunning: " + file + ": prefix NOK"); 173 } 174 } 175 } else { 176 trace("isServerRunning: empty file list"); 177 } 178 trace("isServerRunning: false"); 179 return false; 180 } 181 182 /** 183 * Returns true if we connect successfully to the server for the stringId 184 */ 185 static boolean connectToServer(String[] args) { 186 trace("Connect to: " + stringId + " " + currPort); 187 188 if (randomNumberString == null) { 189 // should not happen 190 trace("MAGIC number is null, bail out."); 191 return false; 192 } 193 194 // Now we open the tcpSocket and the stream 195 Socket socket = null; 196 OutputStream os = null; 197 PrintStream out = null; 198 InputStreamReader isr = null; 199 BufferedReader br = null; 200 try { 201 socket = new Socket("127.0.0.1", currPort); 202 os = socket.getOutputStream(); 203 byte[] encoding = new byte[1]; 204 encoding[0] = ENCODING_PLATFORM; 205 os.write(encoding); 206 String encodingName = Charset.defaultCharset().name(); 207 208 out = new PrintStream(os, true, encodingName); 209 isr = new InputStreamReader(socket.getInputStream(), encodingName); 210 br = new BufferedReader(isr); 211 212 // send random number 213 out.println(randomNumberString); 214 // send MAGICWORD 215 out.println(SingleInstanceImpl.SI_MAGICWORD); 216 217 for (String arg : args) { 218 out.println(arg); 219 } 220 221 // indicate end of file transmission 222 out.println(SingleInstanceImpl.SI_EOF); 223 out.flush(); 224 225 // wait for ACK (OK) response 226 trace("Waiting for ack"); 227 final int tries = 5; 228 229 // try to listen for ACK 230 for (int i=0; i < tries; i++) { 231 String str = br.readLine(); 232 if (str != null && str.equals(SingleInstanceImpl.SI_ACK)) { 233 trace("Got ACK"); 234 return true; 235 } 236 } 237 } catch (java.net.SocketException se) { 238 // no server is running - continue launch 239 trace("No server is running - continue launch."); 240 trace(se); 241 } catch (Exception ioe) { 242 trace(ioe); 243 } 244 finally { 245 try { 246 if (br != null) { 247 br.close(); 248 } 249 if (isr != null) { 250 isr.close(); 251 } 252 if (out != null) { 253 out.close(); 254 } 255 if (os != null) { 256 os.close(); 257 } 258 if (socket != null) { 259 socket.close(); 260 } 261 } catch (IOException ioe) { 262 trace(ioe); 263 } 264 } 265 trace("No ACK from server, bail out."); 266 return false; 267 } 268 269 static String getSessionSpecificString() { 270 // TODO: consider providing session ids 271 return ""; 272 } 273 }