// 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 Omair Majid
*/
public class XSingleInstanceService implements ExtendedSingleInstanceService {
boolean initialized = false;
List listeners = new LinkedList();
/**
* 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 instances of
* applications
*
* @param lockFile
* the {@link SingleInstanceLock} that the server should use
*/
private void startListeningServer(SingleInstanceLock lockFile) {
SingleInstanceServer server = new SingleInstanceServer(lockFile);
Thread serverThread = new Thread(server);
/*
* mark as daemon so the JVM can shutdown if the server is the only
* thread running
*/
serverThread.setDaemon(true);
serverThread.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);
}
}