--- /dev/null 2017-07-05 12:50:57.000000000 -0700 +++ new/modules/jdk.packager.services/src/main/java/jdk/packager/services/singleton/SingleInstanceService.java 2017-07-05 12:50:57.000000000 -0700 @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2017, 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.packager.services.singleton; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.PrintStream; +import java.net.Socket; +import java.nio.charset.Charset; + +/** + * The {@code SingleInstanceService} class provides public methods for using + * Single Instance functionality for Java Packager. To use these methods, + * the option named "-singleton" must be specified on javapackager command line. + * + * @since 10 + */ +public class SingleInstanceService { + + static private boolean DEBUG = false; + static private PrintStream DEBUG_STREAM = null; + + static private int currPort; + static private String stringId = null; + static private String randomNumberString = null; + + static private SingleInstanceImpl instance = null; + + static final int ENCODING_PLATFORM = 1; + static final int ENCODING_UNICODE = 2; + + static final String ENCODING_PLATFORM_NAME = "UTF-8"; + static final String ENCODING_UNICODE_NAME = "UTF-16LE"; + + static final String APP_ID_PREFIX = "javapackager.si."; + + private SingleInstanceService() {} + + static void enableDebug(boolean enable, PrintStream stream) { + DEBUG = enable; + DEBUG_STREAM = stream; + } + + static void trace(String message) { + if (DEBUG && DEBUG_STREAM != null) { + DEBUG_STREAM.println(message); + } + } + + static void trace(Throwable t) { + if (DEBUG && DEBUG_STREAM != null) { + t.printStackTrace(DEBUG_STREAM); + } + } + + /** + * Registers {@code SingleInstanceListener} for current process. + * If the {@code SingleInstanceListener} object is already registered, or + * {@code slistener} is {@code null}, then the registration is skipped. + * + * @param slistener the listener to handle the single instance behaviour. + */ + public static void registerSingleInstance(SingleInstanceListener slistener) { + registerSingleInstance(slistener, false); + } + + /** + * Registers {@code SingleInstanceListener} for current process. + * If the {@code SingleInstanceListener} object is already registered, or + * {@code slistener} is {@code null}, then the registration is skipped. + * + * @param slistener the listener to handle the single instance behaviour. + * @param setFileHandler if {@code true}, the listener is notified when the + * application is asked to open a list of files. If OS is not MacOS, + * the parameter is ignored. + */ + public static void registerSingleInstance(SingleInstanceListener slistener, + boolean setFileHandler) { + String appId = APP_ID_PREFIX + ProcessHandle.current().pid(); + registerSingleInstanceForId(slistener, appId, setFileHandler); + } + + static void registerSingleInstanceForId(SingleInstanceListener slistener, + String stringId, boolean setFileHandler) { + // register SingleInstanceListener for given Id + instance = new SingleInstanceImpl(); + instance.addSingleInstanceListener(slistener, stringId); + if (setFileHandler) { + instance.setOpenFileHandler(); + } + } + + /** + * Unregisters {@code SingleInstanceListener} for current process. + * If the {@code SingleInstanceListener} object is not registered, or + * {@code slistener} is {@code null}, then the unregistration is skipped. + * + * @param slistener the listener for unregistering. + */ + public static void unregisterSingleInstance(SingleInstanceListener slistener) { + instance.removeSingleInstanceListener(slistener); + } + + /** + * Returns true if single instance server is running for the id + */ + static boolean isServerRunning(String id) { + trace("isServerRunning ? : "+ id); + File siDir = new File(SingleInstanceImpl.SI_FILEDIR); + String[] fList = siDir.list(); + if (fList != null) { + String prefix = SingleInstanceImpl.getSingleInstanceFilePrefix(id + + getSessionSpecificString()); + for (String file : fList) { + trace("isServerRunning: " + file); + trace("\t sessionString: " + getSessionSpecificString()); + trace("\t SingleInstanceFilePrefix: " + prefix); + // if file with the same prefix already exist, server is running + if (file.startsWith(prefix)) { + try { + currPort = Integer.parseInt( + file.substring(file.lastIndexOf('_') + 1)); + trace("isServerRunning: " + file + ": port: " + currPort); + } catch (NumberFormatException nfe) { + trace("isServerRunning: " + file + ": port parsing failed"); + trace(nfe); + return false; + } + + trace("Server running at port: " + currPort); + File siFile = new File(SingleInstanceImpl.SI_FILEDIR, file); + + // get random number from single instance file + try (BufferedReader br = new BufferedReader(new FileReader(siFile))) { + randomNumberString = br.readLine(); + trace("isServerRunning: " + file + ": magic: " + randomNumberString); + } catch (IOException ioe ) { + trace("isServerRunning: " + file + ": reading magic failed"); + trace(ioe); + } + trace("isServerRunning: " + file + ": setting id - OK"); + stringId = id; + return true; + } else { + trace("isServerRunning: " + file + ": prefix NOK"); + } + } + } else { + trace("isServerRunning: empty file list"); + } + trace("isServerRunning: false"); + return false; + } + + /** + * Returns true if we connect successfully to the server for the stringId + */ + static boolean connectToServer(String[] args) { + trace("Connect to: " + stringId + " " + currPort); + + if (randomNumberString == null) { + // should not happen + trace("MAGIC number is null, bail out."); + return false; + } + + // Now we open the tcpSocket and the stream + Socket socket = null; + OutputStream os = null; + PrintStream out = null; + InputStreamReader isr = null; + BufferedReader br = null; + try { + socket = new Socket("127.0.0.1", currPort); + os = socket.getOutputStream(); + byte[] encoding = new byte[1]; + encoding[0] = ENCODING_PLATFORM; + os.write(encoding); + String encodingName = Charset.defaultCharset().name(); + + out = new PrintStream(os, true, encodingName); + isr = new InputStreamReader(socket.getInputStream(), encodingName); + br = new BufferedReader(isr); + + // send random number + out.println(randomNumberString); + // send MAGICWORD + out.println(SingleInstanceImpl.SI_MAGICWORD); + + for (String arg : args) { + out.println(arg); + } + + // indicate end of file transmission + out.println(SingleInstanceImpl.SI_EOF); + out.flush(); + + // wait for ACK (OK) response + trace("Waiting for ack"); + final int tries = 5; + + // try to listen for ACK + for (int i=0; i < tries; i++) { + String str = br.readLine(); + if (str != null && str.equals(SingleInstanceImpl.SI_ACK)) { + trace("Got ACK"); + return true; + } + } + } catch (java.net.SocketException se) { + // no server is running - continue launch + trace("No server is running - continue launch."); + trace(se); + } catch (Exception ioe) { + trace(ioe); + } + finally { + try { + if (br != null) { + br.close(); + } + if (isr != null) { + isr.close(); + } + if (out != null) { + out.close(); + } + if (os != null) { + os.close(); + } + if (socket != null) { + socket.close(); + } + } catch (IOException ioe) { + trace(ioe); + } + } + trace("No ACK from server, bail out."); + return false; + } + + static String getSessionSpecificString() { + // TODO: consider providing session ids + return ""; + } +}