src/jdk.packager.services/share/classes/jdk/packager/services/singleton/SingleInstanceService.java
8212048: Cleanup source code and fix issues discussed in preliminary review
Reviewed-by: almatvee
/*
* 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.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 jpackager command line.
*
* @since 12
*/
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 = "jpackager.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);
for (String file : fList) {
trace("isServerRunning: " + file);
trace("\t String id: " + id);
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;
}
}