/*
* Adito
*
* Copyright (C) 2003-2006 3SP LTD. All Rights Reserved
*
* This program 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; either version 2 of
* the License, or (at your option) any later version.
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package com.adito.server;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.StringTokenizer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.adito.boot.ContextHolder;
import com.adito.boot.Util;
/**
* Used to determine if Adito (or some other server) is already running
* under a different runtime. The check also determines if the already running
* the installation wizard.
* <p>
* This mechanism also provides a way for the server to be shutdown use a
* scrip. Once created, the lock file is watched and if it changes to contain
* the text "shutdown" then the server will be shutdown.
*/
public class ServerLock {
/**
* Name of the lock file
*/
public final static String LOCK_NAME = "server.run";
final static Log log = LogFactory.getLog(ServerLock.class);
private File lockFile;
private boolean locked;
private boolean setup;
private int port;
private String bindAddress;
private boolean started;
private long lastLockChange;
/**
* Constructor. The lock will be searched for, and if a service is known to
* be already running an exception is thrown.
*
* @param bindAddress address of interface server is bound to
* @throws IOException if the service is already running
*
*/
public ServerLock(String bindAddress) throws IOException {
this.bindAddress = bindAddress;
lockFile = new File(ContextHolder.getContext().getTempDirectory(), LOCK_NAME);
/*
* Check that Adito is not already running using file locks. If
* the file exists but is not locked, then this may be have been an
* unclean shutdown
*/
if (lockFile.exists()) {
FileInputStream lockIn = null;
try {
lockIn = new FileInputStream(lockFile);
BufferedReader br = new BufferedReader(new InputStreamReader(lockIn));
String r = br.readLine();
if (r != null && !r.equals("")) {
try {
if(!r.equals("shutdown") || r.equals("restart")) {
StringTokenizer t = new StringTokenizer(r, ":");
setup = "true".equals(t.nextToken());
port = Integer.parseInt(t.nextToken());
checkStatus();
}
} catch (Exception e) {
System.err.println("Could not parse lock file.");
e.printStackTrace();
}
}
} finally {
Util.closeStream(lockIn);
}
}
}
/**
* Get if a service is already running.
*
* @return server is locked
*/
public boolean isLocked() {
return locked;
}
/**
* Should be called when the service starts. This checks if a service is
* already running, and if not creates the lock so future instances know.
*
* @param port port the service is running on
* @throws IOException
*/
public void start(int port) throws IOException {
this.port = port;
this.setup = ContextHolder.getContext().isSetupMode();
/*
* Check whether there is already a listener on the port, this means we
* can throw a more meaningful exception than jetty does if a server
* other than Adito is already running on this port
*/
checkStatus();
if (locked) {
if (port == 443 || port == 8443) {
throw new IOException("Some other server is already running on port " + port + "."
+ "Most web servers will run on this port by default, so check if you have such "
+ "a service is installed (IIS, Apache or Tomcat for example). Either shutdown "
+ "and disable the conflicting server, or if you wish to run both services "
+ "concurrently, change the port number on which one listens.");
} else {
throw new IOException("Some other server is already running on port " + port + "."
+ "Check which other services you have enabled that may be causing "
+ "this conflict. Then, either disable the service, change the port on "
+ "which it is listening or change the port on which this server listens.");
}
}
//
PrintWriter pw = new PrintWriter(new FileOutputStream(lockFile));
pw.println(ContextHolder.getContext().isSetupMode() + ":" + port);
pw.flush();
pw.close();
started = true;
lastLockChange = lockFile.lastModified();
/* Start watching the lock file, if it disappears then shut down the
* server
*/
Thread t = new Thread("ServerLockMonitor") {
public void run() {
while(true) {
try {
Thread.sleep(5000);
if(lastLockChange != lockFile.lastModified()) {
lastLockChange = lockFile.lastModified();
if (log.isDebugEnabled())
log.debug("Lock file changed, examining");
InputStream in = null;
try {
in = new FileInputStream(lockFile);;
BufferedReader br = new BufferedReader(new InputStreamReader(in));
String s = br.readLine();
Util.closeStream(in); // close so we can delete
if("shutdown".equals(s)) {
ContextHolder.getContext().shutdown(false);
break;
}
else if("restart".equals(s)) {
ContextHolder.getContext().shutdown(true);
break;
}
}
catch(IOException ioe) {
Util.closeStream(in);
throw ioe;
}
}
}
catch(Exception e) {
}
}
}
};
t.setDaemon(true);
t.setPriority(Thread.MIN_PRIORITY);
t.start();
}
/**
* Get if this instance started and is running
*
* @return started
*
*/
public boolean isStarted() {
return started;
}
/**
* Remove the lock and clean up. Should be called when the server shuts
* down.
*/
public void stop() {
if (log.isInfoEnabled())
log.info("Removing lock");
lockFile.delete();
started = false;
}
/**
* Get if the currently running service is running the installation wizard.
*
* @return running installation wizard
*/
public boolean isSetup() {
return setup;
}
/**
* Get the port the currently running service is running on
*
* @return port
*/
public int getPort() {
return port;
}
private void checkStatus() {
Socket socket = null;
try {
int timeout = 5000; // 5 seconds
if (log.isInfoEnabled())
log.info("Connecting to " + bindAddress + ":" + port + " to see if a server is already running.");
SocketAddress socketAddress = new InetSocketAddress(bindAddress, port);
socket = new Socket();
socket.connect(socketAddress, timeout);
locked = true;
} catch (Exception e) {
locked = false;
} finally {
if (socket != null) {
try {
socket.close();
} catch (Exception e) {
}
}
}
}
}