/** * Copyright (c) 2009 - 2010 AppWork UG(haftungsbeschränkt) <e-mail@appwork.org> * * This file is part of org.appwork.utils.singleapp * * This software is licensed under the Artistic License 2.0, * see the LICENSE file or http://www.opensource.org/licenses/artistic-license-2.0.php * for details */ package org.appwork.utils.singleapp; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.RandomAccessFile; import java.io.UnsupportedEncodingException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketAddress; import java.net.UnknownHostException; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.nio.channels.OverlappingFileLockException; import org.appwork.shutdown.ShutdownController; import org.appwork.shutdown.ShutdownRunableEvent; import org.appwork.utils.Application; import org.appwork.utils.IO; /** * @author daniel * */ public class SingleAppInstance { private static class ShutdownHook implements Runnable { private SingleAppInstance instance = null; public ShutdownHook(final SingleAppInstance instance) { this.instance = instance; } public void run() { if (instance != null) { instance.exit(); } } } private final String appID; private InstanceMessageListener listener = null; private File lockFile = null; private FileLock fileLock = null; private FileChannel lockChannel = null; private boolean daemonRunning = false; private boolean alreadyUsed = false; private ServerSocket serverSocket = null; private final String SINGLEAPP = "SingleAppInstance"; private Thread daemon = null; private File portFile = null; public SingleAppInstance(final String appID) { this(appID, new File(Application.getRoot())); } public SingleAppInstance(final String appID, final File directory) { this.appID = appID; lockFile = new File(directory, appID + ".lock"); portFile = new File(directory, appID + ".port"); ShutdownController.getInstance().addShutdownEvent(new ShutdownRunableEvent(new ShutdownHook(this))); } private synchronized void cannotStart(final String cause) throws UncheckableInstanceException { alreadyUsed = true; lockChannel = null; fileLock = null; throw new UncheckableInstanceException(cause); } public synchronized void exit() { if (fileLock == null) { return; } daemonRunning = false; if (daemon != null) { daemon.interrupt(); } try { try { fileLock.release(); } catch (final IOException e) { } try { lockChannel.close(); } catch (final IOException e) { } } finally { lockChannel = null; fileLock = null; lockFile.delete(); portFile.delete(); } } private synchronized void foundRunningInstance() throws AnotherInstanceRunningException { alreadyUsed = true; lockChannel = null; fileLock = null; throw new AnotherInstanceRunningException(appID); } private InetAddress getLocalHost() { InetAddress localhost = null; try { localhost = InetAddress.getByName("127.0.0.1"); } catch (final UnknownHostException e1) { } if (localhost != null) { return localhost; } try { localhost = InetAddress.getByName(null); } catch (final UnknownHostException e1) { } return localhost; } private String readLine(final BufferedInputStream in) { final ByteArrayOutputStream inbuffer = new ByteArrayOutputStream(); if (in == null) { return ""; } int c; try { in.mark(1); if (in.read() == -1) { return null; } else { in.reset(); } while ((c = in.read()) >= 0) { if (c == 0 || c == 10 || c == 13) { break; } else { inbuffer.write(c); } } if (c == 13) { in.mark(1); if (in.read() != 10) { in.reset(); } } } catch (final Exception e) { } try { return inbuffer.toString("UTF-8"); } catch (final UnsupportedEncodingException e) { return ""; } } private int readPortFromPortFile() { if (!portFile.exists()) { return 0; } try { final String port = IO.readFileToString(portFile); return Integer.parseInt(String.valueOf(port).trim()); } catch (final Exception e) { return 0; } } public synchronized boolean sendToRunningInstance(final String[] message) { if (portFile.exists()) { final int port = readPortFromPortFile(); Socket runninginstance = null; if (port != 0) { try { runninginstance = new Socket(getLocalHost(), port); runninginstance.setSoTimeout(10000);/* set Timeout */ final BufferedInputStream in = new BufferedInputStream(runninginstance.getInputStream()); final OutputStream out = runninginstance.getOutputStream(); final String response = readLine(in); if (response == null || !response.equalsIgnoreCase(SINGLEAPP)) { /* invalid server response */ return false; } if (message == null || message.length == 0) { writeLine(out, "0"); } else { writeLine(out, message.length + ""); for (final String msg : message) { writeLine(out, msg); } } } catch (final UnknownHostException e) { return false; } catch (final IOException e) { return false; } finally { if (runninginstance != null) { try { runninginstance.shutdownInput(); } catch (final Throwable e) { } try { runninginstance.shutdownOutput(); } catch (final Throwable e) { } try { runninginstance.close(); } catch (final Throwable e) { } runninginstance = null; } } return true; } } return false; } public synchronized void setInstanceMessageListener(final InstanceMessageListener listener) { this.listener = listener; } public synchronized void start() throws AnotherInstanceRunningException, UncheckableInstanceException { if (fileLock != null) { return; } if (alreadyUsed) { cannotStart("create new instance!"); } try { if (sendToRunningInstance(null)) { foundRunningInstance(); } lockChannel = new RandomAccessFile(lockFile, "rw").getChannel(); try { fileLock = lockChannel.tryLock(); if (fileLock == null) { foundRunningInstance(); } } catch (final OverlappingFileLockException e) { foundRunningInstance(); } catch (final IOException e) { foundRunningInstance(); } portFile.delete(); serverSocket = new ServerSocket(); final SocketAddress socketAddress = new InetSocketAddress(getLocalHost(), 0); serverSocket.bind(socketAddress); FileOutputStream portWriter = null; try { portWriter = new FileOutputStream(portFile); portWriter.write((serverSocket.getLocalPort() + "").getBytes()); portWriter.flush(); startDaemon(); return; } catch (final Throwable t) { /* network communication not possible */ } finally { try { portWriter.close(); } catch (final Throwable t) { } } cannotStart("could not create instance!"); } catch (final FileNotFoundException e) { cannotStart(e.getMessage()); } catch (final IOException e) { try { serverSocket.close(); } catch (final Throwable t) { } cannotStart(e.getMessage()); } } private synchronized void startDaemon() { if (daemon != null) { return; } daemon = new Thread(new Runnable() { public void run() { daemonRunning = true; while (daemonRunning) { if (daemon.isInterrupted()) { break; } Socket client = null; try { /* accept new request */ client = serverSocket.accept(); client.setSoTimeout(10000);/* set Timeout */ final BufferedInputStream in = new BufferedInputStream(client.getInputStream()); final OutputStream out = client.getOutputStream(); SingleAppInstance.this.writeLine(out, SINGLEAPP); final String line = SingleAppInstance.this.readLine(in); if (line != null && line.length() > 0) { final int lines = Integer.parseInt(line); if (lines != 0) { final String[] message = new String[lines]; for (int index = 0; index < lines; index++) { message[index] = SingleAppInstance.this.readLine(in); } if (listener != null) { try { listener.parseMessage(message); } catch (final Throwable e) { } } } } } catch (final IOException e) { org.appwork.utils.logging.Log.exception(e); } finally { if (client != null) { try { client.shutdownInput(); } catch (final Throwable e) { } try { client.shutdownOutput(); } catch (final Throwable e) { } try { client.close(); } catch (final Throwable e) { } client = null; } } } try { serverSocket.close(); } catch (final Throwable e) { org.appwork.utils.logging.Log.exception(e); } } }); daemon.setName("SingleAppInstance: " + appID); /* set daemonmode so java does not wait for this thread */ daemon.setDaemon(true); daemon.start(); } private void writeLine(final OutputStream outputStream, final String line) { if (outputStream == null || line == null) { return; } try { outputStream.write(line.getBytes("UTF-8")); outputStream.write("\r\n".getBytes()); outputStream.flush(); } catch (final Exception e) { } } }