diff --git a/netx-dist-tests-whitelist b/netx-dist-tests-whitelist --- a/netx-dist-tests-whitelist +++ b/netx-dist-tests-whitelist @@ -1,1 +1,1 @@ -.* +SingleInst.* diff --git a/netx/net/sourceforge/jnlp/Launcher.java b/netx/net/sourceforge/jnlp/Launcher.java --- a/netx/net/sourceforge/jnlp/Launcher.java +++ b/netx/net/sourceforge/jnlp/Launcher.java @@ -536,6 +536,9 @@ public class Launcher { try { ServiceUtil.checkExistingSingleInstance(file); } catch (InstanceExistsException e) { + if (JNLPRuntime.isDebug()) { + System.out.println("Single instance application is already running."); + } return null; } @@ -653,11 +656,17 @@ public class Launcher { throw launchError(new LaunchException(file, null, R("LSFatal"), R("LCClient"), R("LNotApplet"), R("LNotAppletInfo"))); try { + ServiceUtil.checkExistingSingleInstance(file); AppletInstance applet = createApplet(file, enableCodeBase, cont); applet.initialize(); applet.getAppletEnvironment().startApplet(); // this should be a direct call to applet instance return applet; + } catch (InstanceExistsException ieex) { + if (JNLPRuntime.isDebug()) { + System.out.println("Single instance applet is already running."); + } + throw launchError(new LaunchException(file, ieex, R("LSFatal"), R("LCLaunching"), R("LCouldNotLaunch"), R("LSingleInstanceExists"))); } catch (LaunchException lex) { throw launchError(lex); } catch (Exception ex) { @@ -673,9 +682,17 @@ public class Launcher { throw launchError(new LaunchException(file, null, R("LSFatal"), R("LCClient"), R("LNotApplet"), R("LNotAppletInfo"))); try { + ServiceUtil.checkExistingSingleInstance(file); + AppletInstance applet = createApplet(file, enableCodeBase, cont); applet.initialize(); return applet; + + } catch (InstanceExistsException ieex) { + if (JNLPRuntime.isDebug()) { + System.out.println("Single instance applet is already running."); + } + throw launchError(new LaunchException(file, ieex, R("LSFatal"), R("LCLaunching"), R("LCouldNotLaunch"), R("LSingleInstanceExists"))); } catch (LaunchException lex) { throw launchError(lex); } catch (Exception ex) { @@ -688,6 +705,8 @@ public class Launcher { * a thread in the application's thread group. */ protected ApplicationInstance launchInstaller(JNLPFile file) throws LaunchException { + // TODO Check for an existing single instance once implemented. + // ServiceUtil.checkExistingSingleInstance(file); throw launchError(new LaunchException(file, null, R("LSFatal"), R("LCNotSupported"), R("LNoInstallers"), R("LNoInstallersInfo"))); } diff --git a/netx/net/sourceforge/jnlp/resources/Messages.properties b/netx/net/sourceforge/jnlp/resources/Messages.properties --- a/netx/net/sourceforge/jnlp/resources/Messages.properties +++ b/netx/net/sourceforge/jnlp/resources/Messages.properties @@ -81,6 +81,7 @@ LUnsignedJarWithSecurityInfo=Application LSignedAppJarUsingUnsignedJar=Signed application using unsigned jars. LSignedAppJarUsingUnsignedJarInfo=The main application jar is signed, but some of the jars it is using aren't. LSignedJNLPFileDidNotMatch=The signed JNLP file did not match the launching JNLP file. +LSingleInstanceExists=Another instance of this applet already exists and only one may be run at the same time. LNoSecInstance=Error: No security instance for {0}. The application may have trouble continuing LCertFoundIn={0} found in cacerts ({1}) diff --git a/netx/net/sourceforge/jnlp/services/XSingleInstanceService.java b/netx/net/sourceforge/jnlp/services/XSingleInstanceService.java --- a/netx/net/sourceforge/jnlp/services/XSingleInstanceService.java +++ b/netx/net/sourceforge/jnlp/services/XSingleInstanceService.java @@ -29,6 +29,9 @@ import javax.jnlp.SingleInstanceListener import javax.management.InstanceAlreadyExistsException; import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.PluginBridge; +import net.sourceforge.jnlp.runtime.AppletInstance; +import net.sourceforge.jnlp.runtime.ApplicationInstance; import net.sourceforge.jnlp.runtime.JNLPRuntime; /** @@ -104,13 +107,14 @@ public class XSingleInstanceService impl * @throws InstanceAlreadyExistsException if the instance already exists */ public void initializeSingleInstance() { - if (!initialized) { - // this is called after the application has started. so safe to use - // JNLPRuntime.getApplication() - checkSingleInstanceRunning(JNLPRuntime.getApplication().getJNLPFile()); + // this is called after the application has started. so safe to use + // JNLPRuntime.getApplication() + JNLPFile jnlpFile = JNLPRuntime.getApplication().getJNLPFile(); + if (!initialized || jnlpFile instanceof PluginBridge) { + // Either a new process or a new applet being handled by the plugin. + checkSingleInstanceRunning(jnlpFile); initialized = true; SingleInstanceLock lockFile; - JNLPFile jnlpFile = JNLPRuntime.getApplication().getJNLPFile(); lockFile = new SingleInstanceLock(jnlpFile); if (!lockFile.isValid()) { startListeningServer(lockFile); @@ -134,9 +138,21 @@ public class XSingleInstanceService impl if (JNLPRuntime.isDebug()) { System.out.println("Lock file is valid (port=" + port + "). Exiting."); } + + String[] args = null; + if (jnlpFile.isApplet()) { + // FIXME Proprietary plug-in is unclear about how to handle + // applets and their parameters. Potentially use + // jnlpFile.getApplet().getParameters(); + args = new String[0]; + } else if (jnlpFile.isInstaller()) { + // TODO Implement this once installer service is available. + } else { + args = jnlpFile.getApplication().getArguments(); + } + try { - sendProgramArgumentsToExistingApplication(port, jnlpFile.getApplication() - .getArguments()); + sendProgramArgumentsToExistingApplication(port, args); throw new InstanceExistsException(String.valueOf(port)); } catch (IOException e) { throw new RuntimeException(e); diff --git a/tests/jnlp_tests/simple/SingleInstanceServiceTest/resources/SingleInstanceTest.html b/tests/jnlp_tests/simple/SingleInstanceServiceTest/resources/SingleInstanceTest.html new file mode 100644 --- /dev/null +++ b/tests/jnlp_tests/simple/SingleInstanceServiceTest/resources/SingleInstanceTest.html @@ -0,0 +1,46 @@ + + + + + + + + + + diff --git a/tests/jnlp_tests/simple/SingleInstanceServiceTest/resources/SingleInstanceTest.jnlp b/tests/jnlp_tests/simple/SingleInstanceServiceTest/resources/SingleInstanceTest.jnlp new file mode 100644 --- /dev/null +++ b/tests/jnlp_tests/simple/SingleInstanceServiceTest/resources/SingleInstanceTest.jnlp @@ -0,0 +1,58 @@ + + + + + SingleInstance + IcedTea + + SingleInstance + + + + + + + + + diff --git a/tests/jnlp_tests/simple/SingleInstanceServiceTest/srcs/SingleInstanceChecker.java b/tests/jnlp_tests/simple/SingleInstanceServiceTest/srcs/SingleInstanceChecker.java new file mode 100644 --- /dev/null +++ b/tests/jnlp_tests/simple/SingleInstanceServiceTest/srcs/SingleInstanceChecker.java @@ -0,0 +1,92 @@ +/* SingleInstanceChecker.java +Copyright (C) 2012 Red Hat, Inc. + +This file is part of IcedTea. + +IcedTea is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License as published by +the Free Software Foundation, version 2. + +IcedTea 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 for more details. + +You should have received a copy of the GNU General Public License +along with IcedTea; see the file COPYING. If not, write to +the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. + */ + +import java.applet.Applet; + +import javax.jnlp.SingleInstanceListener; +import javax.jnlp.SingleInstanceService; +import javax.jnlp.ServiceManager; +import javax.jnlp.UnavailableServiceException; + +public class SingleInstanceChecker extends Applet { + private class Killer extends Thread { + + public int n = 5000; + + @Override + public void run() { + try { + Thread.sleep(n); + System.out.println("Applet killing itself after " + n + + " ms of life"); + System.exit(0); + } catch (Exception ex) { + } + } + } + + class SingInstTestListener implements SingleInstanceListener { + public void newActivation(String[] params) { + String paramsString = ""; + for (String param : params) { + paramsString += param + " "; + } + System.out.println("Paramaters received by SingleInstanceChecker: " + paramsString); + killer = new Killer(); + killer.start(); + } + } + + private Killer killer; + + @Override + public void init() { + try { + SingleInstanceService testService = (SingleInstanceService) ServiceManager + .lookup("javax.jnlp.SingleInstanceService"); + System.out.println("SingleInstanceChecker: Adding listener to service."); + SingInstTestListener testListener = new SingInstTestListener(); + testService.addSingleInstanceListener(testListener); + System.out.println("SingleInstanceChecker: Listener added."); + } catch (UnavailableServiceException use) { + System.err.println("SingleInstanceChecker: Service lookup failed."); + use.printStackTrace(); + return; + } + } +} + diff --git a/tests/jnlp_tests/simple/SingleInstanceServiceTest/testcases/SingleInstanceTest.java b/tests/jnlp_tests/simple/SingleInstanceServiceTest/testcases/SingleInstanceTest.java new file mode 100644 --- /dev/null +++ b/tests/jnlp_tests/simple/SingleInstanceServiceTest/testcases/SingleInstanceTest.java @@ -0,0 +1,149 @@ +/* SingleInstanceTest.java +Copyright (C) 2012 Red Hat, Inc. + +This file is part of IcedTea. + +IcedTea is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License as published by +the Free Software Foundation, version 2. + +IcedTea 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 for more details. + +You should have received a copy of the GNU General Public License +along with IcedTea; see the file COPYING. If not, write to +the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. + */ + +import java.util.List; + +import net.sourceforge.jnlp.ServerAccess; +import net.sourceforge.jnlp.ServerAccess.ProcessResult; +import net.sourceforge.jnlp.annotations.NeedsDisplay; +import net.sourceforge.jnlp.annotations.TestInBrowsers; +import net.sourceforge.jnlp.browsertesting.BrowserTest; +import net.sourceforge.jnlp.browsertesting.Browsers; + +import org.junit.Assert; +import org.junit.Test; + +public class SingleInstanceTest extends BrowserTest { + + private void evaluateApplet(ProcessResult prFirstInst, + ProcessResult prSecondInst, String testName) { + // First Instance's result should run without exceptions. + String s0 = "SingleInstanceChecker: Adding listener to service."; + Assert.assertTrue("SingleInstanceTest." + testName + + "'s first PR stdout should contain " + s0 + " but didn't", + prFirstInst.stdout.contains(s0)); + String s1 = "Paramaters received by SingleInstanceChecker"; + Assert.assertTrue("SingleInstanceTest." + testName + + "'s first PR stdout should contain " + s1 + " but didn't", + prFirstInst.stdout.contains(s1)); + String ss = "SingleInstanceChecker: Service lookup failed."; + Assert.assertFalse("SingleInstanceTest." + testName + + "'s first PR stderr should not contain " + ss + " but did", + prFirstInst.stderr.contains(ss)); + + // Second Instance's result should throw a LaunchException. + String s2 = "net.sourceforge.jnlp.LaunchException"; + Assert.assertTrue("SingleInstanceTest." + testName + + "'s second PR stderr should contain " + s2 + " but didn't", + prSecondInst.stderr.contains(s2)); + } + + @NeedsDisplay + @Test + public void WebstartAppletTest() throws Exception { + SingleInstanceProcess firstProc = new SingleInstanceProcess("/SingleInstanceTest.jnlp", true); + ServerAccess.logOutputReprint("\n\n\n\n\nStarting it"); + firstProc.start(); + ServerAccess.logOutputReprint("Waiting"); + waitForSingleInstanceProcess(firstProc); + + ProcessResult prSecondInst = server.executeJavawsHeadless(null, "/SingleInstanceTest.jnlp"); + + // Initialize after second process has been executed and finished (or timed out). + ProcessResult prFirstInst = firstProc.getPr(); + + ServerAccess.logOutputReprint("Evaluate\n\n\n\n\n\n\n"); + evaluateApplet(prFirstInst, prSecondInst, "WebstartAppletTest"); + Assert.assertFalse(prFirstInst.wasTerminated); + Assert.assertEquals((Integer) 0, prFirstInst.returnValue); + Assert.assertFalse(prSecondInst.wasTerminated); + Assert.assertEquals((Integer) 0, prSecondInst.returnValue); + } + + // @NeedsDisplay + // @Test + // @TestInBrowsers(testIn = { Browsers.one }) + // public void PluginJNLPHrefTest() throws Exception { + // ProcessResult prFirstInst = server.executeBrowser("/SingleInstanceTest.html"); + // ProcessResult prSecondInst = server.executeBrowser("/SingleInstanceTest.html"); + // prFirstInst.process.destroy(); + // evaluateApplet(prFirstInst, prSecondInst, "PluginJNLPHrefTest"); + // Assert.assertTrue(prFirstInst.wasTerminated); + // Assert.assertTrue(prSecondInst.wasTerminated); + // } + + public static void waitForSingleInstanceProcess(SingleInstanceProcess sip) + throws InterruptedException { + while (!sip.getPr().stdout.contains("SingleInstanceChecker: Listener added.")) { + Thread.sleep(500); + } + ServerAccess.logOutputReprint("Finished wait"); + } + + private static class SingleInstanceProcess extends Thread { + + private ProcessResult pr = null; + private String launchFile; + private List args; + private boolean isJavawsTest; + + SingleInstanceProcess(String launchFile, boolean isJavawsTest) { + this(launchFile, null, isJavawsTest); + } + + public SingleInstanceProcess(String launchFile, List args, + boolean isJavawsTest) { + this.launchFile = launchFile; + this.args = args; + this.isJavawsTest = isJavawsTest; + } + + @Override + public void run() { + try { + pr = isJavawsTest ? server.executeJavawsHeadless(args, launchFile) + : server.executeBrowser(launchFile); + } catch (Exception ex) { + ServerAccess.logException(ex); + } + } + + public ProcessResult getPr() { + return pr; + } + } +}