--- old/buildSrc/linux.gradle 2017-07-05 12:50:40.000000000 -0700 +++ new/buildSrc/linux.gradle 2017-07-05 12:50:40.000000000 -0700 @@ -242,7 +242,7 @@ LINUX.launcher.compiler = compiler LINUX.launcher.ccFlags = ["-Wextra", "-Wformat", "-Wformat-security", "-DJAVAARCH=\"$OS_ARCH\"", "-I$JDK_HOME/include", "-I$JDK_HOME/include/linux", "-c"] LINUX.launcher.linker = linker -LINUX.launcher.linkFlags = ["-ldl"] +LINUX.launcher.linkFlags = ["-ldl", "-lX11"] if (!IS_64) { LINUX.launcher.ccFlags += "-m32" LINUX.launcher.linkFlags += "-m32" @@ -252,7 +252,7 @@ LINUX.launcherlibrary.compiler = compiler LINUX.launcherlibrary.ccFlags = ["-Wextra", "-Wformat", "-Wformat-security", "-DJAVAARCH=\"$OS_ARCH\"", "-I$JDK_HOME/include", "-I$JDK_HOME/include/linux", "-c", "-fPIC"] LINUX.launcherlibrary.linker = linker -LINUX.launcherlibrary.linkFlags = ["-ldl", "-lpthread", "-shared"] +LINUX.launcherlibrary.linkFlags = ["-ldl", "-lpthread", "-shared", "-lX11"] if (!IS_64) { LINUX.launcherlibrary.ccFlags += "-m32" LINUX.launcherlibrary.linkFlags += "-m32" --- old/modules/jdk.packager.services/src/main/java/module-info.java 2017-07-05 12:50:41.000000000 -0700 +++ new/modules/jdk.packager.services/src/main/java/module-info.java 2017-07-05 12:50:41.000000000 -0700 @@ -31,8 +31,10 @@ */ module jdk.packager.services { exports jdk.packager.services; + exports jdk.packager.services.singleton; requires java.prefs; + requires java.desktop; uses jdk.packager.services.UserJvmOptionsService; --- old/modules/jdk.packager/src/antplugin/java/com/sun/javafx/tools/ant/DeployFXTask.java 2017-07-05 12:50:41.000000000 -0700 +++ new/modules/jdk.packager/src/antplugin/java/com/sun/javafx/tools/ant/DeployFXTask.java 2017-07-05 12:50:41.000000000 -0700 @@ -628,6 +628,7 @@ deployParams.setNeedMenu(prefs.getMenu()); deployParams.setSystemWide(prefs.getSystemInstall()); deployParams.setInstalldirChooser(prefs.getInstalldirChooser()); + deployParams.setSingleton(prefs.getSingleton()); } } --- old/modules/jdk.packager/src/antplugin/java/com/sun/javafx/tools/ant/Preferences.java 2017-07-05 12:50:42.000000000 -0700 +++ new/modules/jdk.packager/src/antplugin/java/com/sun/javafx/tools/ant/Preferences.java 2017-07-05 12:50:42.000000000 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 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 @@ -55,6 +55,7 @@ private Boolean menuRequested = null; private Boolean systemWide = null; private Boolean installdirChooserRequested = null; + private Boolean singletonRequested = null; Boolean getSystemInstall() { return systemWide; @@ -106,6 +107,10 @@ installdirChooserRequested = b; } + public void setSingleton(Boolean b) { + singletonRequested = b; + } + private Preferences get() { if (isReference()) { return (Preferences) getRefid().getReferencedObject(); @@ -128,4 +133,8 @@ Boolean getInstalldirChooser() { return get().installdirChooserRequested; } + + Boolean getSingleton() { + return get().singletonRequested; + } } --- old/modules/jdk.packager/src/main/java/com/oracle/tools/packager/StandardBundlerParam.java 2017-07-05 12:50:43.000000000 -0700 +++ new/modules/jdk.packager/src/main/java/com/oracle/tools/packager/StandardBundlerParam.java 2017-07-05 12:50:42.000000000 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 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 @@ -722,6 +722,15 @@ (s, p) -> Boolean.valueOf(s) ); + public static final BundlerParamInfo SINGLETON = new StandardBundlerParam<> ( + I18N.getString("param.singleton.name"), + I18N.getString("param.singleton.description"), + BundleParams.PARAM_SINGLETON, + Boolean.class, + params -> Boolean.FALSE, + (s, p) -> Boolean.valueOf(s) + ); + public static void extractMainClassInfoFromAppResources(Map params) { boolean hasMainClass = params.containsKey(MAIN_CLASS.getID()); boolean hasMainJar = params.containsKey(MAIN_JAR.getID()); --- old/modules/jdk.packager/src/main/java/com/sun/javafx/tools/packager/DeployParams.java 2017-07-05 12:50:43.000000000 -0700 +++ new/modules/jdk.packager/src/main/java/com/sun/javafx/tools/packager/DeployParams.java 2017-07-05 12:50:43.000000000 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 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 @@ -68,6 +68,7 @@ Boolean serviceHint; Boolean signBundle; Boolean installdirChooser; + Boolean singleton; String applicationClass; String preloader; @@ -180,6 +181,10 @@ this.installdirChooser = installdirChooser; } + public void setSingleton(Boolean singleton) { + this.singleton = singleton; + } + public void setSignBundle(Boolean signBundle) { this.signBundle = signBundle; } @@ -686,6 +691,7 @@ bundleParams.setSystemWide(systemWide); bundleParams.setServiceHint(serviceHint); bundleParams.setInstalldirChooser(installdirChooser); + bundleParams.setSingleton(singleton); bundleParams.setSignBundle(signBundle); bundleParams.setCopyright(copyright); bundleParams.setApplicationCategory(category); --- old/modules/jdk.packager/src/main/java/com/sun/javafx/tools/packager/Main.java 2017-07-05 12:50:44.000000000 -0700 +++ new/modules/jdk.packager/src/main/java/com/sun/javafx/tools/packager/Main.java 2017-07-05 12:50:44.000000000 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 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 @@ -296,6 +296,8 @@ deployParams.setApplicationClass(nextArg(args, i++)); } else if(arg.equalsIgnoreCase("-daemon")) { deployParams.setServiceHint(true); + } else if(arg.equalsIgnoreCase("-singleton")) { + deployParams.setSingleton(true); } else if(arg.equalsIgnoreCase("-installdirChooser")) { deployParams.setInstalldirChooser(true); } else if (arg.equalsIgnoreCase("-preloader")) { --- old/modules/jdk.packager/src/main/java/com/sun/javafx/tools/packager/bundlers/BundleParams.java 2017-07-05 12:50:44.000000000 -0700 +++ new/modules/jdk.packager/src/main/java/com/sun/javafx/tools/packager/bundlers/BundleParams.java 2017-07-05 12:50:44.000000000 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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 @@ -102,6 +102,9 @@ /* Adds a dialog to let the user choose a directory where the product will be installed. */ public static final String PARAM_INSTALLDIR_CHOOSER = "installdirChooser"; // Boolean + + /* Prevents from launching multiple instances of application. */ + public static final String PARAM_SINGLETON = "singleton"; // Boolean /** * create a new bundle with all default values @@ -282,6 +285,10 @@ putUnlessNull(PARAM_INSTALLDIR_CHOOSER, b); } + public void setSingleton(Boolean b) { + putUnlessNull(PARAM_SINGLETON, b); + } + public void setSignBundle(Boolean b) { putUnlessNull(SIGN_BUNDLE.getID(), b); } public boolean isShortcutHint() { --- old/modules/jdk.packager/src/main/java/jdk/packager/builders/AbstractAppImageBuilder.java 2017-07-05 12:50:45.000000000 -0700 +++ new/modules/jdk.packager/src/main/java/jdk/packager/builders/AbstractAppImageBuilder.java 2017-07-05 12:50:45.000000000 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 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 @@ -191,6 +191,7 @@ out.println("app.runtime=" + runtimeLocation); out.println("app.identifier=" + IDENTIFIER.fetchFrom(params)); out.println("app.classpath=" + String.join(File.pathSeparator, CLASSPATH.fetchFrom(params).split("[ :;]"))); + out.println("app.application.instance=" + (SINGLETON.fetchFrom(params) ? "single" : "multiple")); // The main app is required to be a jar, modular or unnamed. if (mainJarType == Module.ModuleType.Unknown || mainJarType == Module.ModuleType.ModularJar) { --- old/modules/jdk.packager/src/main/native/launcher/linux/launcher.cpp 2017-07-05 12:50:46.000000000 -0700 +++ new/modules/jdk.packager/src/main/native/launcher/linux/launcher.cpp 2017-07-05 12:50:45.000000000 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2016, Oracle and/or its affiliates. + * Copyright (c) 2014, 2017, Oracle and/or its affiliates. * All rights reserved. Use is subject to license terms. * * This file is available and licensed under the following license: @@ -30,7 +30,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - +#include #include #include #include @@ -59,6 +59,7 @@ int main(int argc, char *argv[]) { int result = 1; + XInitThreads(); setlocale(LC_ALL, "en_US.utf8"); void* library = NULL; --- old/modules/jdk.packager/src/main/native/library/common/GenericPlatform.cpp 2017-07-05 12:50:46.000000000 -0700 +++ new/modules/jdk.packager/src/main/native/library/common/GenericPlatform.cpp 2017-07-05 12:50:46.000000000 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2016, Oracle and/or its affiliates. + * Copyright (c) 2014, 2017, Oracle and/or its affiliates. * All rights reserved. Use is subject to license terms. * * This file is available and licensed under the following license: @@ -176,6 +176,7 @@ keys.insert(std::map::value_type(CONFIG_SPLASH_KEY, _T("app.splash"))); keys.insert(std::map::value_type(CONFIG_APP_MEMORY, _T("app.memory"))); keys.insert(std::map::value_type(CONFIG_APP_DEBUG, _T("app.debug"))); + keys.insert(std::map::value_type(CONFIG_APPLICATION_INSTANCE, _T("app.application.instance"))); keys.insert(std::map::value_type(CONFIG_SECTION_APPLICATION, _T("Application"))); keys.insert(std::map::value_type(CONFIG_SECTION_JVMOPTIONS, _T("JVMOptions"))); --- old/modules/jdk.packager/src/main/native/library/common/JavaVirtualMachine.cpp 2017-07-05 12:50:47.000000000 -0700 +++ new/modules/jdk.packager/src/main/native/library/common/JavaVirtualMachine.cpp 2017-07-05 12:50:47.000000000 -0700 @@ -46,16 +46,23 @@ #include #include +#include -bool RunVM() { +bool RunVM(JvmLaunchType type) { bool result = false; JavaVirtualMachine javavm; - if (javavm.StartJVM() == true) { - result = true; - } - else { + switch (type){ + case USER_APP_LAUNCH: + result = javavm.StartJVM(); + break; + case SINGLE_INSTANCE_NOTIFICATION_LAUNCH: + result = javavm.NotifySingleInstance(); + break; + } + + if (!result) { Platform& platform = Platform::GetInstance(); platform.ShowMessage(_T("Failed to launch JVM\n")); } @@ -63,44 +70,22 @@ return result; } +JavaLibrary::JavaLibrary() : Library(), FCreateProc(NULL) { +} -// Private typedef for function pointer casting -#define LAUNCH_FUNC "JLI_Launch" -typedef int (JNICALL *JVM_CREATE)(int argc, char ** argv, - int jargc, const char** jargv, - int appclassc, const char** appclassv, - const char* fullversion, - const char* dotversion, - const char* pname, - const char* lname, - jboolean javaargs, - jboolean cpwildcard, - jboolean javaw, - jint ergo); - -class JavaLibrary : public Library { - JVM_CREATE FCreateProc; - - JavaLibrary(const TString &FileName); - -public: - JavaLibrary() : Library() { - FCreateProc = NULL; - } - - bool JavaVMCreate(size_t argc, char *argv[]) { - if (FCreateProc == NULL) { - FCreateProc = (JVM_CREATE)GetProcAddress(LAUNCH_FUNC); - } +bool JavaLibrary::JavaVMCreate(size_t argc, char *argv[]) { + if (FCreateProc == NULL) { + FCreateProc = (JVM_CREATE)GetProcAddress(LAUNCH_FUNC); + } - if (FCreateProc == NULL) { - Platform& platform = Platform::GetInstance(); - Messages& messages = Messages::GetInstance(); - platform.ShowMessage(messages.GetMessage(FAILED_LOCATING_JVM_ENTRY_POINT)); - return false; - } + if (FCreateProc == NULL) { + Platform& platform = Platform::GetInstance(); + Messages& messages = Messages::GetInstance(); + platform.ShowMessage(messages.GetMessage(FAILED_LOCATING_JVM_ENTRY_POINT)); + return false; + } - return FCreateProc((int)argc, argv, + return FCreateProc((int)argc, argv, 0, NULL, 0, NULL, "", @@ -111,103 +96,88 @@ false, false, 0) == 0; - } -}; +} //-------------------------------------------------------------------------------------------------- -struct JavaOptionItem { - TString name; - TString value; - void* extraInfo; -}; - - -class JavaOptions { -private: - std::list FItems; - JavaVMOption* FOptions; - -public: - JavaOptions() { - FOptions = NULL; - } - - ~JavaOptions() { - if (FOptions != NULL) { - for (unsigned int index = 0; index < GetCount(); index++) { - delete[] FOptions[index].optionString; - } +JavaOptions::JavaOptions(): FOptions(NULL) { +} - delete[] FOptions; +JavaOptions::~JavaOptions() { + if (FOptions != NULL) { + for (unsigned int index = 0; index < GetCount(); index++) { + delete[] FOptions[index].optionString; } - } - void AppendValue(const TString Key, TString Value, void* Extra) { - JavaOptionItem item; - item.name = Key; - item.value = Value; - item.extraInfo = Extra; - FItems.push_back(item); + delete[] FOptions; } +} - void AppendValue(const TString Key, TString Value) { - AppendValue(Key, Value, NULL); - } +void JavaOptions::AppendValue(const TString Key, TString Value, void* Extra) { + JavaOptionItem item; + item.name = Key; + item.value = Value; + item.extraInfo = Extra; + FItems.push_back(item); +} - void AppendValue(const TString Key) { - AppendValue(Key, _T(""), NULL); - } +void JavaOptions::AppendValue(const TString Key, TString Value) { + AppendValue(Key, Value, NULL); +} - void AppendValues(OrderedMap Values) { - std::vector orderedKeys = Values.GetKeys(); +void JavaOptions::AppendValue(const TString Key) { + AppendValue(Key, _T(""), NULL); +} - for (std::vector::const_iterator iterator = orderedKeys.begin(); iterator != orderedKeys.end(); iterator++) { - TString name = *iterator; - TString value; +void JavaOptions::AppendValues(OrderedMap Values) { + std::vector orderedKeys = Values.GetKeys(); - if (Values.GetValue(name, value) == true) { - AppendValue(name, value); - } - } - } + for (std::vector::const_iterator iterator = orderedKeys.begin(); + iterator != orderedKeys.end(); iterator++) { + TString name = *iterator; + TString value; - void ReplaceValue(const TString Key, TString Value) { - for (std::list::iterator iterator = FItems.begin(); - iterator != FItems.end(); iterator++) { - - TString lkey = iterator->name; - - if (lkey == Key) { - JavaOptionItem item = *iterator; - item.value = Value; - iterator = FItems.erase(iterator); - FItems.insert(iterator, item); - break; - } + if (Values.GetValue(name, value) == true) { + AppendValue(name, value); } } +} - std::list ToList() { - std::list result; - Macros& macros = Macros::GetInstance(); - - for (std::list::const_iterator iterator = FItems.begin(); - iterator != FItems.end(); iterator++) { - TString key = iterator->name; - TString value = iterator->value; - TString option = Helpers::NameValueToString(key, value); - option = macros.ExpandMacros(option); - result.push_back(option); +void JavaOptions::ReplaceValue(const TString Key, TString Value) { + for (std::list::iterator iterator = FItems.begin(); + iterator != FItems.end(); iterator++) { + + TString lkey = iterator->name; + + if (lkey == Key) { + JavaOptionItem item = *iterator; + item.value = Value; + iterator = FItems.erase(iterator); + FItems.insert(iterator, item); + break; } - - return result; } +} - size_t GetCount() { - return FItems.size(); +std::list JavaOptions::ToList() { + std::list result; + Macros& macros = Macros::GetInstance(); + + for (std::list::const_iterator iterator = FItems.begin(); + iterator != FItems.end(); iterator++) { + TString key = iterator->name; + TString value = iterator->value; + TString option = Helpers::NameValueToString(key, value); + option = macros.ExpandMacros(option); + result.push_back(option); } -}; + + return result; +} + +size_t JavaOptions::GetCount() { + return FItems.size(); +} // jvmuserargs can have a trailing equals in the key. This needs to be removed to use // other parts of the launcher. @@ -309,19 +279,7 @@ return false; } - JavaLibrary javaLibrary; - - // TODO: Clean this up. Because of bug JDK-8131321 the opening of the PE file fails in WindowsPlatform.cpp on the check to - // if (pNTHeader->Signature == IMAGE_NT_SIGNATURE) -#ifdef _WIN64 - if (FilePath::FileExists(_T("msvcr100.dll")) == true) { - javaLibrary.AddDependency(_T("msvcr100.dll")); - } -#else - javaLibrary.AddDependencies(platform.FilterOutRuntimeDependenciesForPlatform(platform.GetLibraryImports(package.GetJVMLibraryFileName()))); -#endif - - javaLibrary.Load(package.GetJVMLibraryFileName()); + configureLibrary(); // Initialize the arguments to JLI_Launch() // @@ -349,6 +307,50 @@ options.AppendValue(mainModule); } + return launchVM(options, vmargs, false); +} + +bool JavaVirtualMachine::NotifySingleInstance() { + Package& package = Package::GetInstance(); + + std::list vmargs; + vmargs.push_back(package.GetCommandName()); + + JavaOptions options; + options.AppendValue(_T("-Djava.library.path"), package.GetPackageAppDirectory() + + FilePath::PathSeparator() + package.GetPackageLauncherDirectory()); + options.AppendValue(_T("-Djava.launcher.path"), package.GetPackageLauncherDirectory()); + // launch SingleInstanceNewActivation.main() to pass arguments to another instance + options.AppendValue(_T("-m")); + options.AppendValue(_T("jdk.packager.services/jdk.packager.services.singleton.SingleInstanceNewActivation")); + + configureLibrary(); + + return launchVM(options, vmargs, true); +} + +void JavaVirtualMachine::configureLibrary() { + Platform& platform = Platform::GetInstance(); + Package& package = Package::GetInstance(); + // TODO: Clean this up. Because of bug JDK-8131321 the opening of the PE file + // fails in WindowsPlatform.cpp on the check to + // if (pNTHeader->Signature == IMAGE_NT_SIGNATURE) + TString libName = package.GetJVMLibraryFileName(); +#ifdef _WIN64 + if (FilePath::FileExists(_T("msvcr100.dll")) == true) { + javaLibrary.AddDependency(_T("msvcr100.dll")); + } +#else + javaLibrary.AddDependencies( + platform.FilterOutRuntimeDependenciesForPlatform(platform.GetLibraryImports(libName))); +#endif + javaLibrary.Load(libName); +} + +bool JavaVirtualMachine::launchVM(JavaOptions& options, std::list& vmargs, bool addSiProcessId) { + Platform& platform = Platform::GetInstance(); + Package& package = Package::GetInstance(); + #ifdef MAC // Mac adds a ProcessSerialNumber to args when launched from .app // filter out the psn since they it's not expected in the app @@ -361,14 +363,23 @@ vmargs.splice(vmargs.end(), loptions, loptions.begin(), loptions.end()); #endif + if (addSiProcessId) { + // add single instance process ID as a first argument + TProcessID pid = platform.GetSingleInstanceProcessId(); + std::ostringstream s; + s << pid; + std::string procIdStr(s.str()); + vmargs.push_back(TString(procIdStr.begin(), procIdStr.end())); + } + std::list largs = package.GetArgs(); vmargs.splice(vmargs.end(), largs, largs.begin(), largs.end()); + size_t argc = vmargs.size(); DynamicBuffer argv(argc + 1); unsigned int index = 0; - for (std::list::const_iterator iterator = vmargs.begin(); - iterator != vmargs.end(); iterator++) { + iterator != vmargs.end(); iterator++) { TString item = *iterator; std::string arg = PlatformString(item).toStdString(); #ifdef DEBUG @@ -380,14 +391,14 @@ argv[argc] = NULL; - // On Mac we can only free the boot fields if the calling thread is not the main thread. - #ifdef MAC +// On Mac we can only free the boot fields if the calling thread is not the main thread. +#ifdef MAC if (platform.IsMainThread() == false) { package.FreeBootFields(); } - #else +#else package.FreeBootFields(); - #endif //MAC +#endif //MAC if (javaLibrary.JavaVMCreate(argc, argv.GetData()) == true) { return true; @@ -400,4 +411,4 @@ } return false; -} \ No newline at end of file +} --- old/modules/jdk.packager/src/main/native/library/common/JavaVirtualMachine.h 2017-07-05 12:50:47.000000000 -0700 +++ new/modules/jdk.packager/src/main/native/library/common/JavaVirtualMachine.h 2017-07-05 12:50:47.000000000 -0700 @@ -39,14 +39,73 @@ #include "Platform.h" +enum JvmLaunchType { + USER_APP_LAUNCH, + SINGLE_INSTANCE_NOTIFICATION_LAUNCH, + JVM_LAUNCH_TYPES_NUM +}; + +struct JavaOptionItem { + TString name; + TString value; + void* extraInfo; +}; + +class JavaOptions { +private: + std::list FItems; + JavaVMOption* FOptions; + +public: + JavaOptions(); + ~JavaOptions(); + + void AppendValue(const TString Key, TString Value, void* Extra); + void AppendValue(const TString Key, TString Value); + void AppendValue(const TString Key); + void AppendValues(OrderedMap Values); + void ReplaceValue(const TString Key, TString Value); + std::list ToList(); + size_t GetCount(); +}; + +// Private typedef for function pointer casting +#define LAUNCH_FUNC "JLI_Launch" + +typedef int (JNICALL *JVM_CREATE)(int argc, char ** argv, + int jargc, const char** jargv, + int appclassc, const char** appclassv, + const char* fullversion, + const char* dotversion, + const char* pname, + const char* lname, + jboolean javaargs, + jboolean cpwildcard, + jboolean javaw, + jint ergo); + +class JavaLibrary : public Library { + JVM_CREATE FCreateProc; + JavaLibrary(const TString &FileName); +public: + JavaLibrary(); + bool JavaVMCreate(size_t argc, char *argv[]); +}; + class JavaVirtualMachine { +private: + JavaLibrary javaLibrary; + + void configureLibrary(); + bool launchVM(JavaOptions& options, std::list& vmargs, bool addSiProcessId); public: JavaVirtualMachine(); ~JavaVirtualMachine(void); bool StartJVM(); + bool NotifySingleInstance(); }; -bool RunVM(); +bool RunVM(JvmLaunchType type); #endif //JAVAVIRTUALMACHINE_H --- old/modules/jdk.packager/src/main/native/library/common/LinuxPlatform.cpp 2017-07-05 12:50:48.000000000 -0700 +++ new/modules/jdk.packager/src/main/native/library/common/LinuxPlatform.cpp 2017-07-05 12:50:48.000000000 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2016, Oracle and/or its affiliates. + * Copyright (c) 2014, 2017, Oracle and/or its affiliates. * All rights reserved. Use is subject to license terms. * * This file is available and licensed under the following license: @@ -42,6 +42,14 @@ #include #include +#include +#include +#include +#include +#include +#include + +#define LINUX_PACKAGER_TMP_DIR "/.java/packager/tmp" TString GetEnv(const TString &name) { @@ -195,6 +203,21 @@ return result; } +const char* LinuxPlatform::getTmpDirString() { + return LINUX_PACKAGER_TMP_DIR; +} + +void LinuxPlatform::reactivateAnotherInstance() { + if (singleInstanceProcessId == 0) { + printf("Unable to reactivate another instance, PID is undefined"); + return; + } + Display* dsp = XOpenDisplay(NULL); + ProcessReactivator processReactivator(dsp, singleInstanceProcessId); + processReactivator.reactivateProcess(); + XCloseDisplay(dsp); +} + TPlatformNumber LinuxPlatform::GetMemorySize() { long pages = sysconf(_SC_PHYS_PAGES); long page_size = sysconf(_SC_PAGE_SIZE); @@ -1103,6 +1126,68 @@ return result; } +ProcessReactivator::ProcessReactivator(Display* display, pid_t pid): + _pid(pid), _display(display) { + + _atomPid = XInternAtom(display, "_NET_WM_PID", True); + + if (_atomPid == None) { + return; + } + searchWindowHelper(XDefaultRootWindow(display)); +} + +void ProcessReactivator::searchWindowHelper(Window w) { + Atom type; + int format; + unsigned long num, bytesAfter; + unsigned char* propPid = 0; + if (Success == XGetWindowProperty(_display, w, _atomPid, 0, 1, False, XA_CARDINAL, + &type, &format, &num, &bytesAfter, &propPid)) { + if (propPid != 0) { + if (_pid == *((pid_t *)propPid)) { + _result.push_back(w); + } + XFree(propPid); + } + } + + Window root, parent; + Window* child; + unsigned int numChildren; + if (0 != XQueryTree(_display, w, &root, &parent, &child, &numChildren)) { + for (unsigned int i = 0; i < numChildren; i++) { + searchWindowHelper(child[i]); + } + } +} + +void ProcessReactivator::reactivateProcess() { + for (std::list::const_iterator it = _result.begin(); it != _result.end(); it++) { + // try sending an event to activate window, + // after that we can try to raise it. + XEvent xev; + Atom atom = XInternAtom (_display, "_NET_ACTIVE_WINDOW", False); + xev.xclient.type = ClientMessage; + xev.xclient.serial = 0; + xev.xclient.send_event = True; + xev.xclient.display = _display; + xev.xclient.window = *it; + xev.xclient.message_type = atom; + xev.xclient.format = 32; + xev.xclient.data.l[0] = 2; + xev.xclient.data.l[1] = 0; + xev.xclient.data.l[2] = 0; + xev.xclient.data.l[3] = 0; + xev.xclient.data.l[4] = 0; + XWindowAttributes attr; + XGetWindowAttributes(_display, *it, &attr); + XSendEvent(_display, attr.root, False, + SubstructureRedirectMask | SubstructureNotifyMask, &xev); + XRaiseWindow(_display, *it); + } +} + //-------------------------------------------------------------------------------------------------- #endif // LINUX --- old/modules/jdk.packager/src/main/native/library/common/LinuxPlatform.h 2017-07-05 12:50:48.000000000 -0700 +++ new/modules/jdk.packager/src/main/native/library/common/LinuxPlatform.h 2017-07-05 12:50:48.000000000 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2015, Oracle and/or its affiliates. + * Copyright (c) 2014, 2017, Oracle and/or its affiliates. * All rights reserved. Use is subject to license terms. * * This file is available and licensed under the following license: @@ -41,8 +41,10 @@ #include "PosixPlatform.h" #include "GenericPlatform.h" #include "JavaUserPreferences.h" - +#include +#include #include +#include #pragma warning( push ) @@ -52,6 +54,9 @@ private: pthread_t FMainThread; +protected: + virtual const char* getTmpDirString(); + public: LinuxPlatform(void); virtual ~LinuxPlatform(void); @@ -74,6 +79,7 @@ virtual ISectionalPropertyContainer* GetConfigFile(TString FileName); + virtual void reactivateAnotherInstance(); virtual bool IsMainThread(); virtual TPlatformNumber GetMemorySize(); @@ -96,6 +102,20 @@ virtual bool Load(TString Appid); }; +class ProcessReactivator { +private: + void searchWindowHelper(Window w); + + pid_t _pid; + Atom _atomPid; + Display* _display; + std::list _result; +public: + ProcessReactivator(Display *display, pid_t pid); + + void reactivateProcess(); +}; + #endif //LINUXPLATFORM_H #endif //LINUX --- old/modules/jdk.packager/src/main/native/library/common/MacPlatform.h 2017-07-05 12:50:49.000000000 -0700 +++ new/modules/jdk.packager/src/main/native/library/common/MacPlatform.h 2017-07-05 12:50:49.000000000 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2015, Oracle and/or its affiliates. + * Copyright (c) 2014, 2017, Oracle and/or its affiliates. * All rights reserved. Use is subject to license terms. * * This file is available and licensed under the following license: @@ -47,6 +47,9 @@ private: bool UsePListForConfigFile(); +protected: + virtual const char* getTmpDirString(); + public: MacPlatform(void); virtual ~MacPlatform(void); @@ -68,7 +71,8 @@ virtual ISectionalPropertyContainer* GetConfigFile(TString FileName); virtual TString GetModuleFileName(); - + + virtual void reactivateAnotherInstance(); virtual bool IsMainThread(); virtual TPlatformNumber GetMemorySize(); --- old/modules/jdk.packager/src/main/native/library/common/MacPlatform.mm 2017-07-05 12:50:50.000000000 -0700 +++ new/modules/jdk.packager/src/main/native/library/common/MacPlatform.mm 2017-07-05 12:50:49.000000000 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2016, Oracle and/or its affiliates. + * Copyright (c) 2014, 2017, Oracle and/or its affiliates. * All rights reserved. Use is subject to license terms. * * This file is available and licensed under the following license: @@ -46,6 +46,7 @@ #include #import +#import #include #include @@ -54,6 +55,8 @@ #import #endif //__OBJC__ +#define MAC_PACKAGER_TMP_DIR "/Library/Application Support/Oracle/Java/Packager/tmp" + //-------------------------------------------------------------------------------------------------- NSString* StringToNSString(TString Value) { @@ -87,6 +90,22 @@ ShowMessage(appname, Description); } +const char* MacPlatform::getTmpDirString() { + return MAC_PACKAGER_TMP_DIR; +} + +void MacPlatform::reactivateAnotherInstance() { + if (singleInstanceProcessId == 0) { + printf("Unable to reactivate another instance, PID is undefined"); + return; + } + NSRunningApplication* app = [NSRunningApplication runningApplicationWithProcessIdentifier: singleInstanceProcessId]; + if (app != nil) { + [app activateWithOptions: NSApplicationActivateIgnoringOtherApps]; + } else { + printf("Unable to reactivate another instance PID: %d", singleInstanceProcessId); + } +} TCHAR* MacPlatform::ConvertStringToFileSystemString(TCHAR* Source, bool &release) { TCHAR* result = NULL; @@ -339,6 +358,7 @@ keys.insert(std::map::value_type(CONFIG_SPLASH_KEY, _T("app.splash"))); keys.insert(std::map::value_type(CONFIG_APP_MEMORY, _T("app.memory"))); keys.insert(std::map::value_type(CONFIG_APP_DEBUG, _T("app.debug"))); + keys.insert(std::map::value_type(CONFIG_APPLICATION_INSTANCE, _T("app.application.instance"))); keys.insert(std::map::value_type(CONFIG_SECTION_APPLICATION, _T("Application"))); keys.insert(std::map::value_type(CONFIG_SECTION_JVMOPTIONS, _T("JVMOptions"))); --- old/modules/jdk.packager/src/main/native/library/common/Package.cpp 2017-07-05 12:50:50.000000000 -0700 +++ new/modules/jdk.packager/src/main/native/library/common/Package.cpp 2017-07-05 12:50:50.000000000 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2016, Oracle and/or its affiliates. + * Copyright (c) 2014, 2017, Oracle and/or its affiliates. * All rights reserved. Use is subject to license terms. * * This file is available and licensed under the following license: @@ -57,6 +57,33 @@ return result; } +bool Package::CheckForSingleInstance() { + Platform& platform = Platform::GetInstance(); +#ifdef MAC + if (platform.IsMainThread()) { + return false; + } +#endif + if (FInitialized == true) { + // everything must be initialised at this point + return false; + } + TString appName; + TString appVersion; + AutoFreePtr config = platform.GetConfigFile(platform.GetConfigFileName()); + std::map keys = platform.GetKeys(); + config->GetValue(keys[CONFIG_SECTION_APPLICATION], keys[APP_NAME_KEY], appName); + config->GetValue(keys[CONFIG_SECTION_APPLICATION], keys[CONFIG_VERSION], appVersion); + TString singleInstance; + config->GetValue(keys[CONFIG_SECTION_APPLICATION], keys[CONFIG_APPLICATION_INSTANCE], singleInstance); + if (singleInstance == _T("single")) { + TString uniqueID = appName + FBootFields->FAppID + appVersion; + // if another instance is running, later we can try to reactivate it + return platform.CheckForSingleInstance(uniqueID); + } + return false; +} + void Package::Initialize() { if (FInitialized == true) { return; --- old/modules/jdk.packager/src/main/native/library/common/Package.h 2017-07-05 12:50:51.000000000 -0700 +++ new/modules/jdk.packager/src/main/native/library/common/Package.h 2017-07-05 12:50:51.000000000 -0700 @@ -110,6 +110,7 @@ void Initialize(); void Clear(); void FreeBootFields(); + bool CheckForSingleInstance(); void SetCommandLineArguments(int argc, TCHAR* argv[]); --- old/modules/jdk.packager/src/main/native/library/common/Platform.h 2017-07-05 12:50:51.000000000 -0700 +++ new/modules/jdk.packager/src/main/native/library/common/Platform.h 2017-07-05 12:50:51.000000000 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2016, Oracle and/or its affiliates. + * Copyright (c) 2014, 2017, Oracle and/or its affiliates. * All rights reserved. Use is subject to license terms. * * This file is available and licensed under the following license: @@ -144,6 +144,7 @@ #define CONFIG_APP_ID_KEY _T("CONFIG_APP_ID_KEY") #define CONFIG_APP_MEMORY _T("CONFIG_APP_MEMORY") #define CONFIG_APP_DEBUG _T("CONFIG_APP_DEBUG") +#define CONFIG_APPLICATION_INSTANCE _T("CONFIG_APPLICATION_INSTANCE") #define JVM_RUNTIME_KEY _T("JVM_RUNTIME_KEY") #define PACKAGER_APP_DATA_DIR _T("CONFIG_APP_IDENTIFIER") @@ -386,13 +387,15 @@ AppCDSState FAppCDSState; protected: - Platform(void) { - FAppCDSState = cdsUninitialized; + TProcessID singleInstanceProcessId; + + Platform(void): FAppCDSState(cdsUninitialized), singleInstanceProcessId(0) { } public: AppCDSState GetAppCDSState() { return FAppCDSState; } void SetAppCDSState(AppCDSState Value) { FAppCDSState = Value; } + TProcessID GetSingleInstanceProcessId() { return singleInstanceProcessId; } static Platform& GetInstance(); @@ -444,6 +447,8 @@ virtual Process* CreateProcess() = 0; virtual bool IsMainThread() = 0; + virtual bool CheckForSingleInstance(TString Name) = 0; + virtual void reactivateAnotherInstance() = 0; // Returns megabytes. virtual TPlatformNumber GetMemorySize() = 0; --- old/modules/jdk.packager/src/main/native/library/common/PosixPlatform.cpp 2017-07-05 12:50:52.000000000 -0700 +++ new/modules/jdk.packager/src/main/native/library/common/PosixPlatform.cpp 2017-07-05 12:50:52.000000000 -0700 @@ -44,6 +44,11 @@ #include #include #include +#include +#include +#include +#include +#include #include #include #include @@ -53,6 +58,95 @@ } PosixPlatform::~PosixPlatform(void) { + if (!SingleInstanceFile.empty()) { + unlink(SingleInstanceFile.c_str()); + } +} + +bool PosixPlatform::mkdirs(const char* path) { + char* p = NULL; + char tmp_path[PATH_MAX] = {0}; + + if (strlen(path) > (sizeof(tmp_path) - 1)) { + // too long name + return false; + } + + strcpy(tmp_path, path); + + errno = 0; + for (p = tmp_path + 1; *p; ++p) { + if (*p == '/') { + *p = '\0'; + if (mkdir(tmp_path, S_IRWXU) != 0) { + if (errno != EEXIST) { + return false; + } + } + *p = '/'; + } + } + + if (mkdir(tmp_path, S_IRWXU) != 0) { + if (errno != EEXIST) { + return false; + } + } + + return true; +} + +bool PosixPlatform::getTmpDir(char* path, int len) { + struct passwd* pw = getpwuid(getuid()); + const char* homedir = pw->pw_dir; + snprintf(path, len-1 , "%s%s", homedir, getTmpDirString()); + struct stat sb; + if (stat(path, &sb) != 0 || !S_ISDIR(sb.st_mode)) { + // the dir doesn't exist + if (!mkdirs(path)) { + return false; + } + } + + return true; +} + +// returns true if another instance is already running. +// if false, we need to continue regular launch. +bool PosixPlatform::CheckForSingleInstance(TString appName) { + char tmpDir[PATH_MAX] = {0}; + if (!getTmpDir(tmpDir, PATH_MAX)) { + printf("Unable to check for single instance.\n"); + return false; + } + + char lock_file[PATH_MAX] = {0}; + snprintf(lock_file, PATH_MAX-1, "%s/%s", tmpDir, appName.c_str()); + SingleInstanceFile = lock_file; + int pid_file = open(lock_file, O_CREAT | O_RDWR, 0666); + int rc = flock(pid_file, LOCK_EX | LOCK_NB); + + if (rc) { + if (EWOULDBLOCK == errno) { + // another instance is running + pid_t pid = 0; + read(pid_file, (void*)&pid, sizeof(pid_t)); + printf("Another instance is running PID: %d\n", pid); + if (pid != 0) { + singleInstanceProcessId = pid; + SingleInstanceFile.clear(); + return true; + } + } else { + printf("Unable to check for single instance.\n"); + } + } else { + // It is the first instance. + pid_t pid = getpid(); + write(pid_file, (void*)&pid, sizeof(pid_t)); + } + + return false; } MessageResponse PosixPlatform::ShowResponseMessage(TString title, TString description) { --- old/modules/jdk.packager/src/main/native/library/common/PosixPlatform.h 2017-07-05 12:50:53.000000000 -0700 +++ new/modules/jdk.packager/src/main/native/library/common/PosixPlatform.h 2017-07-05 12:50:52.000000000 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2016, Oracle and/or its affiliates. + * Copyright (c) 2014, 2017, Oracle and/or its affiliates. * All rights reserved. Use is subject to license terms. * * This file is available and licensed under the following license: @@ -40,6 +40,14 @@ class PosixPlatform : virtual public Platform{ +protected: + TString SingleInstanceFile; + + virtual bool mkdirs(const char* path); + virtual bool getTmpDir(char* path, int len); + + virtual const char* getTmpDirString() = 0; + public: PosixPlatform(void); virtual ~PosixPlatform(void); @@ -50,6 +58,7 @@ virtual void SetCurrentDirectory(TString Value); + virtual bool CheckForSingleInstance(TString Name); virtual Module LoadLibrary(TString FileName); virtual void FreeLibrary(Module AModule); virtual Procedure GetProcAddress(Module AModule, std::string MethodName); --- old/modules/jdk.packager/src/main/native/library/common/WindowsPlatform.cpp 2017-07-05 12:50:53.000000000 -0700 +++ new/modules/jdk.packager/src/main/native/library/common/WindowsPlatform.cpp 2017-07-05 12:50:53.000000000 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2016, Oracle and/or its affiliates. + * Copyright (c) 2014, 2017, Oracle and/or its affiliates. * All rights reserved. Use is subject to license terms. * * This file is available and licensed under the following license: @@ -318,6 +318,123 @@ return result; } +static BOOL CALLBACK enumWindows(HWND winHandle, LPARAM lParam) { + DWORD pid = (DWORD)lParam, wPid = 0; + GetWindowThreadProcessId(winHandle, &wPid); + if (pid == wPid) { + SetForegroundWindow(winHandle); + return FALSE; + } + return TRUE; +} + +void WindowsPlatform::reactivateAnotherInstance() { + if (singleInstanceProcessId == 0) { + printf("Unable to reactivate another instance, PID is undefined"); + return; + } + EnumWindows(&enumWindows, (LPARAM)singleInstanceProcessId); +} + +// returns true if another instance is already running. +// if false, we need to continue regular launch. +bool WindowsPlatform::CheckForSingleInstance(TString name) { + if (SingleInstance::getInstance(name)->IsAnotherInstanceRunning()) { + // read PID + DWORD pid = SingleInstance::getInstance(name)->readPid(); + if (pid != 0) { + singleInstanceProcessId = pid; + return true; + } + } else { + // it is the first intance + // write pid and continue regular launch + SingleInstance::getInstance(name)->writePid(GetCurrentProcessId()); + } + return false; +} + +SingleInstance::SingleInstance(TString& name_): BUF_SIZE(256), _name(name_), _hMapFile(NULL), _pBuf(NULL) { + _mutex = CreateMutex(NULL, TRUE, name_.data()); + _lastError = GetLastError(); + _sharedMemoryName = _T("Local\\javapackager-") + _name; +} + +SingleInstance::~SingleInstance() { + if (_pBuf != NULL) { + UnmapViewOfFile(_pBuf); + _pBuf = NULL; + } + + if (_hMapFile != NULL) { + CloseHandle(_hMapFile); + _hMapFile = NULL; + } + + if (_mutex != NULL) { + CloseHandle(_mutex); + _mutex = NULL; + } +} + +bool SingleInstance::writePid(DWORD pid) { + _hMapFile = CreateFileMapping( + INVALID_HANDLE_VALUE, + NULL, + PAGE_READWRITE, + 0, + BUF_SIZE, + _sharedMemoryName.data()); + + if (_hMapFile == NULL) { + return false; + } + + _pBuf = (LPTSTR) MapViewOfFile(_hMapFile, + FILE_MAP_ALL_ACCESS, + 0, + 0, + BUF_SIZE); + + if (_pBuf == NULL) { + CloseHandle(_hMapFile); + _hMapFile = NULL; + return false; + } + + CopyMemory((PVOID)_pBuf, &pid, sizeof(DWORD)); + + return true; +} + +DWORD SingleInstance::readPid() { + _hMapFile = OpenFileMapping( + FILE_MAP_ALL_ACCESS, + FALSE, + _sharedMemoryName.data()); + + if (_hMapFile == NULL) { + return 0; + } + + _pBuf = (LPTSTR) MapViewOfFile(_hMapFile, + FILE_MAP_ALL_ACCESS, + 0, + 0, + BUF_SIZE); + + if (_pBuf == NULL) { + CloseHandle(_hMapFile); + _hMapFile = NULL; + return 0; + } + + DWORD pid = 0; + CopyMemory(&pid, (PVOID)_pBuf, sizeof(DWORD)); + + return pid; +} + TPlatformNumber WindowsPlatform::GetMemorySize() { SYSTEM_INFO si; GetSystemInfo(&si); --- old/modules/jdk.packager/src/main/native/library/common/WindowsPlatform.h 2017-07-05 12:50:54.000000000 -0700 +++ new/modules/jdk.packager/src/main/native/library/common/WindowsPlatform.h 2017-07-05 12:50:54.000000000 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2016, Oracle and/or its affiliates. + * Copyright (c) 2014, 2017, Oracle and/or its affiliates. * All rights reserved. Use is subject to license terms. * * This file is available and licensed under the following license: @@ -44,6 +44,43 @@ #include +// the class is used to create and detect single instance of user application +class SingleInstance { +private: + const int BUF_SIZE; + + DWORD _lastError; + HANDLE _mutex; + TString _name; + TString _sharedMemoryName; + HANDLE _hMapFile; + LPCTSTR _pBuf; + + SingleInstance(): BUF_SIZE(0) {} + + SingleInstance(TString& name_); + +public: + static SingleInstance* getInstance(TString& name) { + static SingleInstance* result = NULL; + + if (result == NULL) { + result = new SingleInstance(name); + } + + return result; + } + + ~SingleInstance(); + + bool IsAnotherInstanceRunning() { + return (ERROR_ALREADY_EXISTS == _lastError); + } + + bool writePid(DWORD pid); + DWORD readPid(); +}; + #pragma warning( push ) #pragma warning( disable : 4250 ) // C4250 - 'class1' : inherits 'class2::member' class WindowsPlatform : virtual public Platform, GenericPlatform { @@ -79,8 +116,10 @@ virtual std::vector FilterOutRuntimeDependenciesForPlatform(std::vector Imports); virtual Process* CreateProcess(); - + + virtual void reactivateAnotherInstance(); virtual bool IsMainThread(); + virtual bool CheckForSingleInstance(TString Name); virtual TPlatformNumber GetMemorySize(); #ifdef DEBUG --- old/modules/jdk.packager/src/main/native/library/common/main.cpp 2017-07-05 12:50:54.000000000 -0700 +++ new/modules/jdk.packager/src/main/native/library/common/main.cpp 2017-07-05 12:50:54.000000000 -0700 @@ -119,6 +119,16 @@ package.SetCommandLineArguments(argc, argv); platform.SetCurrentDirectory(package.GetPackageAppDirectory()); + if (package.CheckForSingleInstance()) { + // reactivate the first instance if the process Id is valid + platform.reactivateAnotherInstance(); + if (package.GetArgs().size() > 0 && platform.GetSingleInstanceProcessId() != 0) { + // if user specified args, try to pass them to the first instance + return RunVM(SINGLE_INSTANCE_NOTIFICATION_LAUNCH); + } + return true; + } + switch (platform.GetAppCDSState()) { case cdsDisabled: case cdsUninitialized: @@ -192,9 +202,8 @@ } } } - // Run App - result = RunVM(); + result = RunVM(USER_APP_LAUNCH); } catch (FileNotFoundException &e) { platform.ShowMessage(e.GetMessage()); --- old/modules/jdk.packager/src/main/resources/com/oracle/tools/packager/StandardBundlerParam.properties 2017-07-05 12:50:55.000000000 -0700 +++ new/modules/jdk.packager/src/main/resources/com/oracle/tools/packager/StandardBundlerParam.properties 2017-07-05 12:50:55.000000000 -0700 @@ -166,6 +166,9 @@ param.main.module.name=Main Module param.main.module.description=The main module of the application. This module should have the main-class, and is on the module path. +param.singleton.name=Singleton +param.singleton.description=Prevents from launching multiple instances of application. + error.required-parameter={0} is a required parameter. error.no-main-class-with-main-jar=An application class was not specified nor was one found in the jar {0} error.no-main-class-with-main-jar.advice=Please specify a applicationClass or ensure that the jar {0} specifies one in the manifest. --- /dev/null 2017-07-05 12:50:56.000000000 -0700 +++ new/modules/jdk.packager.services/src/main/java/jdk/packager/services/singleton/SingleInstanceImpl.java 2017-07-05 12:50:55.000000000 -0700 @@ -0,0 +1,436 @@ +/* + * 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.awt.Desktop; +import java.awt.desktop.OpenFilesHandler; +import java.awt.desktop.OpenFilesEvent; +import java.net.ServerSocket; +import java.net.InetAddress; +import java.net.Socket; +import java.io.File; +import java.io.PrintStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; +import java.security.PrivilegedAction; +import java.security.AccessController; +import java.security.SecureRandom; + + +class SingleInstanceImpl { + + static final String SI_FILEDIR = getTmpDir() + File.separator + + "si" + File.separator; + static final String SI_MAGICWORD = "javapackager.singleinstance.init"; + static final String SI_ACK = "javapackager.singleinstance.ack"; + static final String SI_STOP = "javapackager.singleinstance.stop"; + static final String SI_EOF = "javapackager.singleinstance.EOF"; + + private final ArrayList siListeners = new ArrayList<>(); + private SingleInstanceServer siServer; + + private static final SecureRandom random = new SecureRandom(); + private static volatile boolean serverStarted = false; + private static int randomNumber; + + private final Object lock = new Object(); + + static String getSingleInstanceFilePrefix(final String stringId) { + String filePrefix = stringId.replace('/','_'); + filePrefix = filePrefix.replace(':','_'); + return filePrefix; + } + + static String getTmpDir() { + String os = System.getProperty("os.name").toLowerCase(); + if (os.contains("win")) { + return System.getProperty("user.home") + + "\\AppData\\LocalLow\\Sun\\Java\\Packager\\tmp"; + } else if (os.contains("mac") || os.contains("os x")) { + return System.getProperty("user.home") + + "/Library/Application Support/Oracle/Java/Packager/tmp"; + } else if (os.contains("nix") || os.contains("nux") || os.contains("aix")) { + return System.getProperty("user.home") + "/.java/packager/tmp"; + } + + return System.getProperty("java.io.tmpdir"); + } + + void addSingleInstanceListener(SingleInstanceListener sil, String id) { + + if (sil == null || id == null) { + return; + } + + // start a new server thread for this unique id + // first time + synchronized (lock) { + if (!serverStarted) { + SingleInstanceService.trace("unique id: " + id); + try { + String sessionID = id + + SingleInstanceService.getSessionSpecificString(); + siServer = new SingleInstanceServer(sessionID); + siServer.start(); + } catch (Exception e) { + SingleInstanceService.trace("addSingleInstanceListener failed"); + SingleInstanceService.trace(e); + return; // didn't start + } + serverStarted = true; + } + } + + synchronized (siListeners) { + // add the sil to the arrayList + if (!siListeners.contains(sil)) { + siListeners.add(sil); + } + } + + } + + class SingleInstanceServer { + + private final SingleInstanceServerRunnable runnable; + private final Thread thread; + + SingleInstanceServer(SingleInstanceServerRunnable runnable) throws IOException { + thread = new Thread(null, runnable, "JavaPackagerSIThread", 0, false); + thread.setDaemon(true); + this.runnable = runnable; + } + + SingleInstanceServer(String stringId) throws IOException { + this(new SingleInstanceServerRunnable(stringId)); + } + + int getPort() { + return runnable.getPort(); + } + + void start() { + thread.start(); + } + } + + private class SingleInstanceServerRunnable implements Runnable { + + ServerSocket ss; + int port; + String stringId; + String[] arguments; + + int getPort() { + return port; + } + + SingleInstanceServerRunnable(String id) throws IOException { + stringId = id; + + // open a free ServerSocket + ss = null; + + // we should bind the server to the local InetAddress 127.0.0.1 + // port number is automatically allocated for current SI + ss = new ServerSocket(0, 0, InetAddress.getByName("127.0.0.1")); + + // get the port number + port = ss.getLocalPort(); + SingleInstanceService.trace("server port at: " + port); + + // create the single instance file with canonical home and port number + createSingleInstanceFile(stringId, port); + } + + private String getSingleInstanceFilename(final String id, final int port) { + String name = SI_FILEDIR + getSingleInstanceFilePrefix(id) + "_" + port; + SingleInstanceService.trace("getSingleInstanceFilename: " + name); + return name; + } + + private void removeSingleInstanceFile(final String id, final int port) { + new File(getSingleInstanceFilename(id, port)).delete(); + SingleInstanceService.trace("removed SingleInstanceFile: " + + getSingleInstanceFilename(id, port)); + } + + private void createSingleInstanceFile(final String id, final int port) { + String filename = getSingleInstanceFilename(id, port); + final File siFile = new File(filename); + final File siDir = new File(SI_FILEDIR); + AccessController.doPrivileged(new PrivilegedAction() { + @Override + public Void run() { + siDir.mkdirs(); + String[] fList = siDir.list(); + if (fList != null) { + String prefix = getSingleInstanceFilePrefix(id); + for (String file : fList) { + // if file with the same prefix already exist, remove it + if (file.startsWith(prefix)) { + SingleInstanceService.trace( + "file should be removed: " + SI_FILEDIR + file); + new File(SI_FILEDIR + file).delete(); + } + } + } + + PrintStream out = null; + try { + siFile.createNewFile(); + siFile.deleteOnExit(); + // write random number to single instance file + out = new PrintStream(new FileOutputStream(siFile)); + randomNumber = random.nextInt(); + out.print(randomNumber); + } catch (IOException ioe) { + SingleInstanceService.trace(ioe); + } finally { + if (out != null) { + out.close(); + } + } + return null; + } + }); + } + + @Override + public void run() { + // start sil to handle all the incoming request + // from the server port of the current url + AccessController.doPrivileged(new PrivilegedAction() { + @Override + public Void run() { + List recvArgs = new ArrayList<>(); + while (true) { + recvArgs.clear(); + InputStream is = null; + BufferedReader in = null; + InputStreamReader isr = null; + Socket s = null; + String line = null; + boolean sendAck = false; + int port = -1; + String charset = null; + try { + SingleInstanceService.trace("waiting connection"); + s = ss.accept(); + is = s.getInputStream(); + // read first byte for encoding type + int encoding = is.read(); + if (encoding == SingleInstanceService.ENCODING_PLATFORM) { + charset = Charset.defaultCharset().name(); + } else if (encoding == SingleInstanceService.ENCODING_UNICODE) { + charset = SingleInstanceService.ENCODING_UNICODE_NAME; + } else { + SingleInstanceService.trace("SingleInstanceImpl - unknown encoding"); + return null; + } + isr = new InputStreamReader(is, charset); + in = new BufferedReader(isr); + // first read the random number + line = in.readLine(); + if (line.equals(String.valueOf(randomNumber)) == false) { + // random number does not match + // should not happen + // shutdown server socket + removeSingleInstanceFile(stringId, port); + ss.close(); + serverStarted = false; + SingleInstanceService.trace("Unexpected Error, " + + "SingleInstanceService disabled"); + return null; + } else { + line = in.readLine(); + // no need to continue reading if MAGICWORD + // did not come first + SingleInstanceService.trace("recv: " + line); + if (line.equals(SI_MAGICWORD)) { + SingleInstanceService.trace("got magic word."); + while (true) { + // Get input string + try { + line = in.readLine(); + if (line != null && line.equals(SI_EOF)) { + // end of file reached + break; + } else { + recvArgs.add(line); + } + } catch (IOException ioe) { + SingleInstanceService.trace(ioe); + } + } + arguments = recvArgs.toArray(new String[recvArgs.size()]); + sendAck = true; + } else if (line.equals(SI_STOP)) { + // remove the SingleInstance file + removeSingleInstanceFile(stringId, port); + break; + } + } + } catch (IOException ioe) { + SingleInstanceService.trace(ioe); + } finally { + try { + if (sendAck) { + // let the action listener handle the rest + for (String arg : arguments) { + SingleInstanceService.trace( + "Starting new instance with arguments: arg:" + arg); + } + + performNewActivation(arguments); + + // now the event is handled, we can send + // out the ACK + SingleInstanceService.trace("sending out ACK"); + if (s != null) { + try (OutputStream os = s.getOutputStream(); + PrintStream ps = new PrintStream(os, true, charset)) { + // send OK (ACK) + ps.println(SI_ACK); + ps.flush(); + } + } + } + + if (in != null) { + in.close(); + } + + if (isr != null) { + isr.close(); + } + + if (is != null) { + is.close(); + } + + if (s != null) { + s.close(); + } + } catch (IOException ioe) { + SingleInstanceService.trace(ioe); + } + } + } + return null; + } + }); + } + } + + private void performNewActivation(final String[] args) { + // enumerate the sil list and call + // each sil with arguments + @SuppressWarnings("unchecked") + ArrayList silal = + (ArrayList)siListeners.clone(); + silal.forEach(sil -> sil.newActivation(args)); + } + + void setOpenFileHandler() { + String os = System.getProperty("os.name").toLowerCase(); + if (!os.contains("mac") && !os.contains("os x")) { + return; + } + + Desktop.getDesktop().setOpenFileHandler(new OpenFilesHandler() { + @Override + public void openFiles(OpenFilesEvent e) { + List arguments = new ArrayList<>(); + e.getFiles().forEach(file -> arguments.add(file.toString())); + performNewActivation(arguments.toArray( + new String[arguments.size()])); + } + }); + } + + void removeSingleInstanceListener(SingleInstanceListener sil) { + if (sil == null) { + return; + } + + synchronized (siListeners) { + + if (!siListeners.remove(sil)) { + return; + } + + if (siListeners.isEmpty()) { + AccessController.doPrivileged(new PrivilegedAction() { + @Override + public Void run() { + // stop server + Socket socket = null; + PrintStream out = null; + OutputStream os = null; + try { + socket = new Socket("127.0.0.1", siServer.getPort()); + os = socket.getOutputStream(); + byte[] encoding = new byte[1]; + encoding[0] = SingleInstanceService.ENCODING_PLATFORM; + os.write(encoding); + String charset = Charset.defaultCharset().name(); + out = new PrintStream(os, true, charset); + out.println(randomNumber); + out.println(SingleInstanceImpl.SI_STOP); + out.flush(); + serverStarted = false; + } catch (IOException ioe) { + SingleInstanceService.trace(ioe); + } finally { + try { + if (out != null) { + out.close(); + } + if (os != null) { + os.close(); + } + if (socket != null) { + socket.close(); + } + } catch (IOException ioe) { + SingleInstanceService.trace(ioe); + } + } + return null; + } + }); + } + } + } +} --- /dev/null 2017-07-05 12:50:56.000000000 -0700 +++ new/modules/jdk.packager.services/src/main/java/jdk/packager/services/singleton/SingleInstanceListener.java 2017-07-05 12:50:56.000000000 -0700 @@ -0,0 +1,45 @@ +/* + * 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; + +/** + * The {@code SingleInstanceListener} interface is used for implementing + * Single Instance functionality for Java Packager. + * + * @since 10 + */ +public interface SingleInstanceListener { + + /** + * This method should be implemented by the application to + * handle the single instance behaviour - how should the application + * handle the arguments when another instance of the application is + * invoked with params. + * + * @param params parameters for the application main + */ + public void newActivation(String... params); +} --- /dev/null 2017-07-05 12:50:57.000000000 -0700 +++ new/modules/jdk.packager.services/src/main/java/jdk/packager/services/singleton/SingleInstanceNewActivation.java 2017-07-05 12:50:56.000000000 -0700 @@ -0,0 +1,49 @@ +/* + * 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.util.Arrays; + +// This class is used for notifying Single Instance for Java Packager. + +public class SingleInstanceNewActivation { + + public static void main(String[] args) { + + if (args.length < 2) { + // no user args specified + return; + } + + // the first arg is process id of the single instance + String appId = SingleInstanceService.APP_ID_PREFIX + args[0]; + + if (SingleInstanceService.isServerRunning(appId)) { + String[] newArgs = Arrays.copyOfRange(args, 1, args.length); + SingleInstanceService.connectToServer(newArgs); + } + } +} --- /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 ""; + } +}