Mercurial > hg > release > icedtea6-1.6
changeset 1618:ed75376cfe3a
2009-06-24 Omair Majid <omajid@redhat.com>
* rt/net/sourceforge/jnlp/Launcher.java (launchApplication): Check for any
existing single instance. Dont start a second instance.
* rt/net/sourceforge/jnlp/resources/Messages.properties: Add RNoLockDir.
* rt/net/sourceforge/jnlp/runtime/JNLPRuntime.java:
Added USER, TMP_DIR, and changed LOCKS_DIR.
* rt/net/sourceforge/jnlp/services/ExtendedSingleInstanceService.java
New file.
* rt/net/sourceforge/jnlp/services/InstanceExistsException.java: New file.
* rt/net/sourceforge/jnlp/services/ServiceUtil.java
(getSingleInstanceService): New function.
(checkExistingSingleInstance): New function.
* rt/net/sourceforge/jnlp/services/SingleInstanceLock.java: New file.
* rt/net/sourceforge/jnlp/services/XServiceManagerStub.java: Add
SingleInstanceService to serviceNames. Create a new instance of
XSingleInstanceService as a privileged proxy.
* rt/net/sourceforge/jnlp/services/XSingleInstanceService.java: New file.
author | Omair Majid <omajid@redhat.com> |
---|---|
date | Wed, 24 Jun 2009 15:02:45 -0400 |
parents | 1ba29bfca5a0 |
children | acf8090028c9 |
files | ChangeLog rt/net/sourceforge/jnlp/Launcher.java rt/net/sourceforge/jnlp/resources/Messages.properties rt/net/sourceforge/jnlp/runtime/JNLPRuntime.java rt/net/sourceforge/jnlp/services/ExtendedSingleInstanceService.java rt/net/sourceforge/jnlp/services/InstanceExistsException.java rt/net/sourceforge/jnlp/services/ServiceUtil.java rt/net/sourceforge/jnlp/services/SingleInstanceLock.java rt/net/sourceforge/jnlp/services/XServiceManagerStub.java rt/net/sourceforge/jnlp/services/XSingleInstanceService.java |
diffstat | 10 files changed, 598 insertions(+), 8 deletions(-) [+] |
line wrap: on
line diff
--- a/ChangeLog Wed Jun 24 12:15:33 2009 -0400 +++ b/ChangeLog Wed Jun 24 15:02:45 2009 -0400 @@ -1,3 +1,22 @@ +2009-06-24 Omair Majid <omajid@redhat.com> + + * rt/net/sourceforge/jnlp/Launcher.java (launchApplication): Check for any + existing single instance. Dont start a second instance. + * rt/net/sourceforge/jnlp/resources/Messages.properties: Add RNoLockDir. + * rt/net/sourceforge/jnlp/runtime/JNLPRuntime.java: + Added USER, TMP_DIR, and changed LOCKS_DIR. + * rt/net/sourceforge/jnlp/services/ExtendedSingleInstanceService.java + New file. + * rt/net/sourceforge/jnlp/services/InstanceExistsException.java: New file. + * rt/net/sourceforge/jnlp/services/ServiceUtil.java + (getSingleInstanceService): New function. + (checkExistingSingleInstance): New function. + * rt/net/sourceforge/jnlp/services/SingleInstanceLock.java: New file. + * rt/net/sourceforge/jnlp/services/XServiceManagerStub.java: Add + SingleInstanceService to serviceNames. Create a new instance of + XSingleInstanceService as a privileged proxy. + * rt/net/sourceforge/jnlp/services/XSingleInstanceService.java: New file. + 2009-06-24 Omair Majid <omajid@redhat.com> * rt/net/sourceforge/jnlp/Parser.java: Add 1.5 and 6.0 to
--- a/rt/net/sourceforge/jnlp/Launcher.java Wed Jun 24 12:15:33 2009 -0400 +++ b/rt/net/sourceforge/jnlp/Launcher.java Wed Jun 24 15:02:45 2009 -0400 @@ -37,6 +37,8 @@ import net.sourceforge.jnlp.runtime.ApplicationInstance; import net.sourceforge.jnlp.runtime.JNLPClassLoader; import net.sourceforge.jnlp.runtime.JNLPRuntime; +import net.sourceforge.jnlp.services.InstanceExistsException; +import net.sourceforge.jnlp.services.ServiceUtil; import net.sourceforge.jnlp.util.Reflect; /** @@ -366,6 +368,12 @@ try { + try { + ServiceUtil.checkExistingSingleInstance(file); + } catch (InstanceExistsException e) { + return null; + } + if (JNLPRuntime.getForksAllowed() && file.needsNewVM()) { List<String> netxArguments = new LinkedList<String>(); netxArguments.add("-Xnofork");
--- a/rt/net/sourceforge/jnlp/resources/Messages.properties Wed Jun 24 12:15:33 2009 -0400 +++ b/rt/net/sourceforge/jnlp/resources/Messages.properties Wed Jun 24 15:02:45 2009 -0400 @@ -110,6 +110,7 @@ RCantReplaceSM=Changing the SecurityManager is not allowed. RDenyStopped=Stopped applications have no permissions. RExitNoApp=Can not exit the JVM because the current application cannot be determined. +RNoLockDir=Unable to create locks directory ({0}) RUnexpected=Unexpected {0} at {1} # Boot options, message should be shorter than this ---------------->
--- a/rt/net/sourceforge/jnlp/runtime/JNLPRuntime.java Wed Jun 24 12:15:33 2009 -0400 +++ b/rt/net/sourceforge/jnlp/runtime/JNLPRuntime.java Wed Jun 24 15:02:45 2009 -0400 @@ -104,6 +104,9 @@ /** contains the arguments passed to the jnlp runtime */ private static List<String> initialArguments; + /** Username */ + public static final String USER = System.getProperty("user.name"); + /** User's home directory */ public static final String HOME_DIR = System.getProperty("user.home"); @@ -119,12 +122,17 @@ /** the ~/.netx/security/trusted.certs file containing trusted certificates */ public static final String CERTIFICATES_FILE = SECURITY_DIR + File.separator + "trusted.certs"; + /** the /tmp/ directory used for temporary files */ + public static final String TMP_DIR = System.getProperty("java.io.tmpdir"); + /** - * the ~/.netx/locks/ directory containing locks for single instance + * the /tmp/$USER/netx/locks/ directory containing locks for single instance * applications */ - public static final String LOCKS_DIR = NETX_DIR + File.separator + "locks"; + public static final String LOCKS_DIR = TMP_DIR + File.separator + USER + File.separator + + "netx" + File.separator + "locks"; + /** the java.home directory */ public static final String JAVA_HOME_DIR = System.getProperty("java.home"); /** the JNLP file to open to display the network-based about window */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/net/sourceforge/jnlp/services/ExtendedSingleInstanceService.java Wed Jun 24 15:02:45 2009 -0400 @@ -0,0 +1,49 @@ +// Copyright (C) 2009 Red Hat, Inc. +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +package net.sourceforge.jnlp.services; + +import javax.jnlp.SingleInstanceService; + +import net.sourceforge.jnlp.JNLPFile; + +/** + * Extends SingleInstanceService to provide a few additional methods that are + * required to initialize SingleInstanceService and check things. These methods + * are not exposed publicly + * + * @author <a href="mailto:omajid@redhat.com">Omair Majid</a> + * + */ +interface ExtendedSingleInstanceService extends SingleInstanceService { + + /** + * Check if the instance identified by this jnlp file is already running + * + * @param jnlpFile The JNLPFile that specifies the application + * + * @throws InstanceExistsException if an instance of this application + * already exists + * + */ + void checkSingleInstanceRunning(JNLPFile jnlpFile); + + /** + * Start a single instance service based on the current application + */ + void initializeSingleInstance(); + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/net/sourceforge/jnlp/services/InstanceExistsException.java Wed Jun 24 15:02:45 2009 -0400 @@ -0,0 +1,35 @@ +// Copyright (C) 2009 Red Hat, Inc. +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +package net.sourceforge.jnlp.services; + +/** + * + * This class represents an exception indicating that an application instance + * already exists for this jnlp file + * + * @author <a href="mailto:omajid@redhat.com">Omair Majid</a> + * + */ +public class InstanceExistsException extends RuntimeException { + + private static final long serialVersionUID = 7950552292795498272L; + + public InstanceExistsException(String message) { + super(message); + } + +}
--- a/rt/net/sourceforge/jnlp/services/ServiceUtil.java Wed Jun 24 12:15:33 2009 -0400 +++ b/rt/net/sourceforge/jnlp/services/ServiceUtil.java Wed Jun 24 15:02:45 2009 -0400 @@ -17,12 +17,29 @@ package net.sourceforge.jnlp.services; -import java.lang.reflect.*; -import java.security.*; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; -import javax.jnlp.*; +import javax.jnlp.BasicService; +import javax.jnlp.ClipboardService; +import javax.jnlp.DownloadService; +import javax.jnlp.ExtensionInstallerService; +import javax.jnlp.FileOpenService; +import javax.jnlp.FileSaveService; +import javax.jnlp.PersistenceService; +import javax.jnlp.PrintService; +import javax.jnlp.ServiceManager; +import javax.jnlp.SingleInstanceService; +import javax.jnlp.UnavailableServiceException; -import net.sourceforge.jnlp.runtime.*; +import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.runtime.ApplicationInstance; +import net.sourceforge.jnlp.runtime.JNLPRuntime; import net.sourceforge.jnlp.security.SecurityWarningDialog; /** @@ -111,6 +128,25 @@ } /** + * Returns the SingleInstanceService reference, or null if the service is + * unavailable. + */ + public static SingleInstanceService getSingleInstanceService() { + return (SingleInstanceService) getService("javax.jnlp.SingleInstanceService"); + } + + /** + * Checks that this application (represented by the jnlp) isnt already running + * @param jnlpFile the {@link JNLPFile} that specifies the application + * + * @throws InstanceExistsException if an instance of this application already exists + */ + public static void checkExistingSingleInstance(JNLPFile jnlpFile) { + ExtendedSingleInstanceService esis = (ExtendedSingleInstanceService) getSingleInstanceService(); + esis.checkSingleInstanceRunning(jnlpFile); + } + + /** * Returns the service, or null instead of an UnavailableServiceException */ private static Object getService(String name) {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/net/sourceforge/jnlp/services/SingleInstanceLock.java Wed Jun 24 15:02:45 2009 -0400 @@ -0,0 +1,203 @@ +// Copyright (C) 2009 Red Hat, Inc. +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +package net.sourceforge.jnlp.services; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.net.BindException; +import java.net.ServerSocket; + +import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.runtime.JNLPRuntime; + +/** + * This class represents a Lock for single instance jnlp applications + * + * The lock is per-session, per user. + * + * @author <a href="mailto:omajid@redhat.com">Omair Majid</a> + */ +class SingleInstanceLock { + + JNLPFile jnlpFile; + File lockFile = null; + + public static final int INVALID_PORT = Integer.MIN_VALUE; + + int port = INVALID_PORT; + + /** + * Create an object to manage the instance lock for the specified JNLP file. + * + * @param jnlpFile the jnlpfile to create the lock for + */ + public SingleInstanceLock(JNLPFile jnlpFile) { + this.jnlpFile = jnlpFile; + lockFile = getLockFile(); + + } + + /** + * Create/overwrite the instance lock for the jnlp file. + * + * @param localPort the network port for the lock + * @throws IOException on any io problems + */ + public void createWithPort(int localPort) throws IOException { + + BufferedWriter lockFileWriter = new BufferedWriter(new FileWriter(lockFile, false)); + lockFileWriter.write(String.valueOf(localPort)); + lockFileWriter.newLine(); + lockFileWriter.flush(); + lockFileWriter.close(); + + } + + /** + * Returns true if the lock if valid. That is, the lock exists, and port it + * points to is listening for incoming messages. + */ + public boolean isValid() { + return (exists() && getPort() != INVALID_PORT && !isPortFree(getPort())); + } + + /** + * Returns the port in this lock file. + */ + public int getPort() { + if (!exists()) { + return INVALID_PORT; + } + + try { + parseFile(); + } catch (NumberFormatException e) { + port = INVALID_PORT; + } catch (IOException e) { + port = INVALID_PORT; + } + return port; + + } + + /** + * Returns true if the lock file already exists. + */ + private boolean exists() { + return lockFile.exists(); + } + + /** + * Returns true if the port is free. + */ + private boolean isPortFree(int port) { + try { + ServerSocket socket = new ServerSocket(port); + socket.close(); + return true; + } catch (BindException e) { + return false; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Return a file object that represents the lock file. The lock file itself + * may or may not exist. + */ + private File getLockFile() { + File baseDir = new File(JNLPRuntime.LOCKS_DIR); + + if (!baseDir.isDirectory() && !baseDir.mkdirs()) { + throw new RuntimeException(R("RNoLockDir", baseDir)); + } + String lockFileName = getLockFileName(); + File applicationLockFile = new File(baseDir, lockFileName); + return applicationLockFile; + } + + /** + * Returns the name of the lock file. + */ + private String getLockFileName() { + String initialName = ""; + + if (jnlpFile.getSourceLocation() != null) { + initialName = initialName + jnlpFile.getSourceLocation(); + } else { + initialName = initialName + jnlpFile.getFileLocation(); + } + + if (jnlpFile.getFileVersion() != null) { + initialName = initialName + jnlpFile.getFileVersion().toString(); + } + + initialName = initialName + getCurrentDisplay(); + String encodedName; + + /* + * FIXME + * + * Assuming safe characters are 'a-z','A-Z','0-9', '_', '.' + */ + + encodedName = initialName.replaceAll("[^a-zA-Z0-9.]", "_"); + + return encodedName; + + } + + /** + * Parse the lock file. + * + * @throws NumberFormatException + * @throws IOException + */ + private void parseFile() throws NumberFormatException, IOException { + BufferedReader lockFileReader = new BufferedReader(new FileReader(lockFile)); + int port = Integer.valueOf(lockFileReader.readLine()); + lockFileReader.close(); + this.port = port; + } + + /** + * Returns a string identifying this display. + * + * Implementation note: On systems with X support, this is the DISPLAY + * variable + * + * @return a string that is guaranteed to be not null. + */ + private String getCurrentDisplay() { + String display = System.getenv("DISPLAY"); + return (display == null) ? "" : display; + } + + private static String R(String key) { + return JNLPRuntime.getMessage(key); + } + + private static String R(String key, Object param) { + return JNLPRuntime.getMessage(key, new Object[] { param }); + } + +}
--- a/rt/net/sourceforge/jnlp/services/XServiceManagerStub.java Wed Jun 24 12:15:33 2009 -0400 +++ b/rt/net/sourceforge/jnlp/services/XServiceManagerStub.java Wed Jun 24 15:02:45 2009 -0400 @@ -51,7 +51,8 @@ "javax.jnlp.FileOpenService", "javax.jnlp.FileSaveService", "javax.jnlp.ClipboardService", - "javax.jnlp.PrintService" + "javax.jnlp.PrintService", + "javax.jnlp.SingleInstanceService" }; private static Object services[] = { @@ -63,7 +64,8 @@ ServiceUtil.createPrivilegedProxy(FileOpenService.class, new XFileOpenService()), ServiceUtil.createPrivilegedProxy(FileSaveService.class, new XFileSaveService()), ServiceUtil.createPrivilegedProxy(ClipboardService.class, new XClipboardService()), - ServiceUtil.createPrivilegedProxy(PrintService.class, new XPrintService()) + ServiceUtil.createPrivilegedProxy(PrintService.class, new XPrintService()), + ServiceUtil.createPrivilegedProxy(ExtendedSingleInstanceService.class, new XSingleInstanceService()) };
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/net/sourceforge/jnlp/services/XSingleInstanceService.java Wed Jun 24 15:02:45 2009 -0400 @@ -0,0 +1,229 @@ +// Copyright (C) 2009 Red Hat, Inc. +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +package net.sourceforge.jnlp.services; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.UnknownHostException; +import java.util.LinkedList; +import java.util.List; + +import javax.jnlp.SingleInstanceListener; +import javax.management.InstanceAlreadyExistsException; + +import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.runtime.JNLPRuntime; + +/** + * This class implements SingleInstanceService + * + * @author <a href="mailto:omajid@redhat.com">Omair Majid</a> + */ +public class XSingleInstanceService implements ExtendedSingleInstanceService { + + boolean initialized = false; + List<SingleInstanceListener> listeners = new LinkedList<SingleInstanceListener>(); + + /** + * Implements a server that listens for arguments from new instances of this + * application + * + */ + class SingleInstanceServer implements Runnable { + + SingleInstanceLock lockFile = null; + + public SingleInstanceServer(SingleInstanceLock lockFile) { + this.lockFile = lockFile; + } + + public void run() { + ServerSocket listeningSocket = null; + try { + listeningSocket = new ServerSocket(0); + lockFile.createWithPort(listeningSocket.getLocalPort()); + + if (JNLPRuntime.isDebug()) { + System.out.println("Starting SingleInstanceServer on port" + listeningSocket); + } + + while (true) { + try { + Socket communicationSocket = listeningSocket.accept(); + ObjectInputStream ois = new ObjectInputStream(communicationSocket + .getInputStream()); + String[] arguments = (String[]) ois.readObject(); + notifySingleInstanceListeners(arguments); + } catch (Exception exception) { + // not much to do here... + exception.printStackTrace(); + } + + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (listeningSocket != null) { + try { + listeningSocket.close(); + } catch (IOException e) { + // Give up. + e.printStackTrace(); + } + } + } + } + } + + /** + * Create a new XSingleInstanceService + */ + protected XSingleInstanceService() { + } + + /** + * Initialize the new SingleInstanceService + * + * @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()); + initialized = true; + SingleInstanceLock lockFile; + JNLPFile jnlpFile = JNLPRuntime.getApplication().getJNLPFile(); + lockFile = new SingleInstanceLock(jnlpFile); + if (!lockFile.isValid()) { + startListeningServer(lockFile); + } + } + + } + + /** + * Check if another instance of this application is already running + * + * @param jnlpFile The {@link JNLPFile} that specifies the application + * + * @throws InstanceExistsException if an instance of this application + * already exists + */ + public void checkSingleInstanceRunning(JNLPFile jnlpFile) { + SingleInstanceLock lockFile = new SingleInstanceLock(jnlpFile); + if (lockFile.isValid()) { + int port = lockFile.getPort(); + if (JNLPRuntime.isDebug()) { + System.out.println("Lock file is valid (port=" + port + "). Exiting."); + } + try { + sendProgramArgumentsToExistingApplication(port, jnlpFile.getApplication() + .getArguments()); + throw new InstanceExistsException(String.valueOf(port)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + /** + * Start the listening server to accept arguments from new isntances of + * applications + * + * @param lockFile + */ + private void startListeningServer(SingleInstanceLock lockFile) { + SingleInstanceServer server = new SingleInstanceServer(lockFile); + new Thread(server).start(); + } + + /** + * Send the arguments for this application to the main instance + * + * @param port the port at which the SingleInstanceServer is listening at + * @param arguments the new arguments + * @throws IOException on any io exception + */ + private void sendProgramArgumentsToExistingApplication(int port, String[] arguments) + throws IOException { + try { + Socket serverCommunicationSocket = new Socket((String) null, port); + ObjectOutputStream argumentStream = new ObjectOutputStream(serverCommunicationSocket + .getOutputStream()); + argumentStream.writeObject(arguments); + argumentStream.close(); + serverCommunicationSocket.close(); + + } catch (UnknownHostException unknownHost) { + if (JNLPRuntime.isDebug()) { + System.out.println("Unable to find localhost"); + } + throw new RuntimeException(unknownHost); + } + } + + /** + * Notify any SingleInstanceListener with new arguments + * + * @param arguments the new arguments to the application + */ + private void notifySingleInstanceListeners(String[] arguments) { + for (SingleInstanceListener listener : listeners) { + // TODO this proxy is privileged. should i worry about security in + // methods being called? + listener.newActivation(arguments); + } + } + + /** + * Add the specified SingleInstanceListener + * + * @throws InstanceExistsException, which is likely to terminate the + * application but not guaranteed to + */ + public void addSingleInstanceListener(SingleInstanceListener sil) { + initializeSingleInstance(); + + if (sil == null) { + return; + } + + listeners.add(sil); + } + + /** + * Remove the specified SingleInstanceListener + * + * @throws InstanceExistsException if an instance of this single instance + * application already exists + * + */ + public void removeSingleInstanceListener(SingleInstanceListener sil) { + initializeSingleInstance(); + + if (sil == null) { + return; + } + + listeners.remove(sil); + } + +}