org.ops4j.pax.runner.daemon.Daemon Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of pax-runner Show documentation
Show all versions of pax-runner Show documentation
OPS4J Pax Runner - Core engine.
See details at http://wiki.ops4j.org/confluence/x/A4A6.
package org.ops4j.pax.runner.daemon;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.AccessControlException;
import java.security.NoSuchAlgorithmException;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.ops4j.net.Base64Encoder;
import org.ops4j.pax.runner.CommandLine;
import org.ops4j.pax.runner.CommandLineImpl;
import org.ops4j.pax.runner.Run;
import org.ops4j.pax.runner.platform.DefaultJavaRunner;
import org.ops4j.pax.runner.platform.StoppableJavaRunner;
/**
* The class that runs as Daemon for the Pax Runner. The {@link DaemonLauncher}
* launches the instance of this class either as an attached or a detached process.
* In either case, the instance of this class launches the Pax-Runner and waits
* for appropriate shutdown command to be issued on its shutdown port.
*
* The Daemon process terminates either when the Pax-Runner completes execution,
* or when a shutdown command is issued via shutdown port.
*
* @author Thomas Joseph
* @since 0.20.0 (29 April 2009)
*
*/
public class Daemon {
// Constants -----------------------------------------------------
/**
* Configuration option to specify password file, if not specified, the name
* of the default password file.
*/
public static final String PASSWORD_FILE = "org.ops4j.pax.runner.daemon.password.file";
private static final String INFO_FILE = "org.ops4j.pax.runner.daemon.info";
private static final String LOCK_FILE = "org.ops4j.pax.runner.daemon.lock";
private static final String NEWLINE= "\r\n";
private static final String OPT_NETWORK_TIMEOUT="org.ops4j.pax.runner.daemon.network.timeout";
private static final String OPT_SHUTDOWN_TIMEOUT = "org.ops4j.pax.runner.daemon.shutdown.timeout";
private static final String OPT_SHUTDOWN_CMD = "org.ops4j.pax.runner.daemon.shutdown.cmd";
private static final String OPT_SHUTDOWN_PORT = "org.ops4j.pax.runner.daemon.shutdown.port";
// Attributes ----------------------------------------------------
private CommandLine commandLine = null;
private String[] cmdArgs = null;
private StoppableJavaRunner runner = null;
private Thread shutdownHook = null;
private int networkTimeout = 1000*60;
private long shutdownTimeout = 0;
private ServerSocket serverSocket = null;
private boolean continueAwait = true;
private static Daemon instance = null;
private static String shutdown = "shutdown";
private static int shutdownPort = 8008;
// Static --------------------------------------------------------
/** logger. */
private static final Log LOG = LogFactory.getLog(Daemon.class);
/**
* Launches a new Daemon instance, that will bring up the Pax Runner.
*/
public static void main(String[] args) {
getInstance();
instance.load(args);
instance.start();
}
/**
* @return The singleton instance of the Daemon.
*/
public static Daemon getInstance() {
if (instance == null)
instance = new Daemon();
return instance;
}
/**
* Determines if any instance of the Daemon is already started.
*
* @return True if any instance of this class is already started, false otherwise.
*/
public static boolean isDaemonStarted() {
File lock = new File(getRunnerHomeDir(false), LOCK_FILE);
if (lock.exists() && lock.isFile()) {
return true;
}
return false;
}
// Constructors --------------------------------------------------
// Public --------------------------------------------------------
/**
* Prepares the instance of this class with configurations, before launching
* the Pax-Runner.
*
* @param args The arguments passed to start this instance.
*/
public void load(String... args) {
cmdArgs = args;
commandLine = new CommandLineImpl(args);
setNetworkTimeout(commandLine.getOption(OPT_NETWORK_TIMEOUT));
setShutdown(commandLine.getOption(OPT_SHUTDOWN_CMD));
setShutdownPort(commandLine.getOption(OPT_SHUTDOWN_PORT));
setShutdownTimeout(commandLine.getOption(OPT_SHUTDOWN_TIMEOUT));
}
/**
* Starts the Daemon - Launches Pax Runner, Opens up shutdown port on which
* it will listen to shutdown command.
*
*/
public void start() {
new RunnerLauncher().start();
await();
}
/**
* Stops the running instance of the Daemon and Pax Runner if any.
*/
public void stop() {
if (shutdownHook != null) {
Runtime.getRuntime().removeShutdownHook(shutdownHook);
LOG.trace("Removed Shutdown Hook.");
shutdownHook.run();
shutdownHook = null;
} else {
shutdown();
}
}
// Package protected ---------------------------------------------
/**
* Retrieves the file that stores password information for the current
* configuration.
*
* @return The password information file.
*/
static File getPasswordFile(String passwordFilePath) {
if (passwordFilePath == null || passwordFilePath.length() == 0) {
return new File(getRunnerHomeDir(true),PASSWORD_FILE);
}
return new File(passwordFilePath);
}
/**
* Encrypts and returns the encrypted string for the given message.
*
* @param message the pass phrase that is to be encrypted.
* @return the encrypted string for the given message.
*/
static String encrypt(String message) {
java.security.MessageDigest d =null;
try {
d = java.security.MessageDigest.getInstance("SHA-1");
} catch (NoSuchAlgorithmException e) {
return null;
}
d.reset();
d.update(message.getBytes());
return new String(Base64Encoder.encode(d.digest()));
}
/**
* Creates a file on the file system with the given content for the file.
*
* @param file The file that should be written to the filesystem.
* @param content The content for the file.
*/
static void writeToFile(File file, String content) {
FileWriter fw = null;
try {
fw = new FileWriter(file);
fw.write(content);
fw.flush();
} catch (IOException e) {
throw new RuntimeException ("Error creating file.",e);
} finally {
try {
if(fw != null)
fw.close();
fw=null;
}
catch (IOException e) {/* ignore */}
}
}
/**
*
* @return The shutdown port that was configured (if any) to launch the
* Daemon (if it was launched), the default port otherwise
*
* @see #OPT_SHUTDOWN_PORT
*/
static int getShutdownPort() {
int port = shutdownPort;
String sPort = readDaemonProperty(OPT_SHUTDOWN_PORT);
if ((port = parseSafeInt( sPort)) != -1)
return port;
return shutdownPort;
}
/**
* Returns the command that must be issued to the shutdown port to stop the
* launched Daemon instance.
*
* @return The shutdown command that was configured (if any) when launching the
* Daemon instance (if it was launched), the default command otherwise
*
* @see #OPT_SHUTDOWN_CMD
*/
static String getShutdown() {
String read = readDaemonProperty(OPT_SHUTDOWN_CMD);
if (read!=null && read.length() > 0)
return read;
return shutdown;
}
// Protected -----------------------------------------------------
// Private -------------------------------------------------------
private void await() {
createInfoFile();
createLockFile();
shutdownHook = createShutdownHook();
Runtime.getRuntime().addShutdownHook(shutdownHook);
// Set up a server socket to wait on
try {
LOG.debug("Setting up shutdown port on " + getShutdownPort());
serverSocket =
new ServerSocket(getShutdownPort());
} catch (IOException e) {
throw new RuntimeException("Unable to set up shutdown port ["
+ getShutdownPort()
+ "].", e);
}
// Loop waiting for a connection and a valid command
while (continueAwait) {
// Wait for the next connection
try {
// handle each new connection in a separate thread
new ClientHandler().handle(serverSocket.accept());
} catch (IOException e) {
LOG.debug("Stopped accepting connections." + e.getMessage());
}
}
// Close the server socket and return
try {
if (serverSocket != null) {
serverSocket.close();
}
} catch (IOException e) {
;
}
LOG.trace("Finished awaiting...");
}
private void stopAwait() {
continueAwait = false;
try {
if (serverSocket != null) {
serverSocket.close();
serverSocket = null;
LOG.info("Stopped shutdown port.");
}
} catch (IOException e) {
;
}
}
private StoppableJavaRunner createJavaRunner()
{
return new DefaultJavaRunner();
}
/**
* Creates a simple shutdown hook.
*
* @return Unstarted thread that can be registered as a shutdown hook
*/
private Thread createShutdownHook() {
return new Thread(
new Runnable() {
public void run() {
LOG.trace("Executing shutdown hook...");
shutdown();
}
}, "Pax Runner Daemon Shutdown Hook"
);
}
/**
* Creates a "lock" file. An empty file that is created as the Daemon is
* started (attached/detached) and removed when the it stops. The file
* will be used to determine if the Daemon is already running.
*/
private void createLockFile() {
File lock = new File(getRunnerHomeDir(true), LOCK_FILE);
if (lock.exists()) {
throw new RuntimeException(LOCK_FILE + " exists. Please make sure" +
" that the Pax Runner daemon is not already running.");
}
try {
lock.createNewFile();
lock.deleteOnExit();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Creates a file that stores information about the running instance of the
* Daemon.
*/
private void createInfoFile() {
File info = new File(getRunnerHomeDir(true), INFO_FILE);
int count = 10;
while ( info.exists() && count > 0 ) {
info.delete();
count --;
}
try {
info.createNewFile();
String content = OPT_SHUTDOWN_CMD + "=" + shutdown +
NEWLINE + OPT_SHUTDOWN_PORT + "=" + shutdownPort;
Daemon.writeToFile(info, content);
} catch (IOException e) {
e.printStackTrace();
}
}
private String readEncryptedPassword() {
return readFile(getPasswordFile(commandLine.getOption(Daemon.PASSWORD_FILE)));
}
private String readFile(File file) {
try {
LOG.debug("Read file:" + file.getAbsolutePath());
BufferedReader br = new BufferedReader(new FileReader(file));
return br.readLine();
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void shutdown() {
stopAwait();
if (runner != null) {
LOG.debug("Bringing down Runner...");
runner.shutdown();
runner = null;
LOG.info("Pax Runner daemon stopped.");
}
}
private void setShutdownPort(String shutdownPort) {
if (parseSafeInt(shutdownPort) != -1) {
Daemon.shutdownPort = parseSafeInt(shutdownPort);
}
}
private void setNetworkTimeout(String networkTimeout) {
if (parseSafeInt(networkTimeout) != -1) {
this.networkTimeout = parseSafeInt(networkTimeout);
}
}
private void setShutdownTimeout(String shutdownTimeout) {
if (parseSafeInt(shutdownTimeout) != -1) {
this.shutdownTimeout = parseSafeInt(shutdownTimeout);
}
}
/**
* Returns the file reference of the Runner's home directory. Creates one if
* it doesn't exist and if the create flag is set to true
.
*
* @return
*/
private static File getRunnerHomeDir(boolean create) {
String homeDirPath = System.getProperty("user.home")
+ File.separator + ".pax"+ File.separator + "runner";
final File homeDir = new File(homeDirPath);
if ( !homeDir.exists() && create ) {
if( homeDir.mkdirs() ) {
LOG.debug("Created Pax Runner Home.");
}
}
return homeDir;
}
private static int parseSafeInt(String arg) {
if (arg != null && arg.length() > 0) {
try {
return Integer.parseInt(arg);
} catch(Exception e) {}
}
return -1;
}
private static String readDaemonProperty(String key) {
File info = new File(getRunnerHomeDir(false), INFO_FILE);
if (info.exists()) {
Properties props = new Properties();
try {
props.load(new FileInputStream(info));
return props.getProperty(key);
} catch (FileNotFoundException e) {;}
catch (IOException e) {;}
}
return null;
}
// Inner classes -------------------------------------------------
/**
* Launches the Pax Runner in a separate thread within the same process.
*/
class RunnerLauncher extends Thread {
RunnerLauncher () {
super("RunnerLauncher");
this.setDaemon(true);
}
public void run() {
if (runner == null) {
runner = createJavaRunner();
LOG.trace("Created Runner.");
}
try {
Run.main(runner, cmdArgs);
} finally {
instance.shutdown();
}
}
}
/**
* Handles a individual client on the given socket as a separate thread. If
* the client is connecting from a loopback adapter (localhost), no password is required.
* If the client connects via telnet, password is required to issue commands.
* The shutdown sequence begins if the client enters the correct shutdown command.
*
* @author Thomas Joseph.
*
*/
class ClientHandler {
public void handle(final Socket socket) {
new Thread(
new Runnable () {
public void run() {
boolean isLocalConnect = false;
InputStream stream = null;
PrintWriter out = null;
try {
isLocalConnect = socket.getInetAddress().isLoopbackAddress();
socket.setSoTimeout(networkTimeout);
LOG.trace("Connected.");
stream = socket.getInputStream();
out = new PrintWriter(socket.getOutputStream(), true);
} catch (AccessControlException ace) {
LOG.warn("StandardServer.accept security exception: "
+ ace.getMessage(), ace);
return;
} catch (IOException e) {
//LOG.error("StandardServer.await: accept: ", e);
return;
}
// ASK FOR PASSWORD for remote system
if (!isLocalConnect) {
// Write a welcome note
String welcome = "Welcome to Pax-Runner Remote Console";
String underline = "===============================================";
out.write(underline.substring(0, welcome.length()) + NEWLINE);
out.write(welcome + NEWLINE);
out.write(underline.substring(0, welcome.length()) + NEWLINE);
out.flush();
String passwordHash = new String(readEncryptedPassword());
if (passwordHash != null && passwordHash.trim().length() > 0) {
int count = 3;
boolean matched = false;
while (count > 0) {
--count;
out.write("Please enter password: ");
out.flush();
String clientPassword = readStream(stream);
readStream(stream); // skipping control character
clientPassword = new String(encrypt(clientPassword));
if (passwordHash.trim().equals(clientPassword.trim())) {
matched = true;
break;
}
}
if (!matched) {
out.write("Invalid password attempts exceeded limits."+NEWLINE);
out.write("Try connecting again."+NEWLINE);
out.flush();
try {
socket.close();
} catch (IOException e) {
;
}
return;
}
}
}
LOG.trace("Going to ask the client to supply command.");
out.write("Enter the command to shutdown the Pax Runner:"+NEWLINE);
out.write("> ");
out.flush();
// Read a set of characters from the socket
String command = readStream(stream);
LOG.trace("Recieved Command [ " + command + "] from ["
+socket.getRemoteSocketAddress() + "].");
try {
String[] commands = command.split("\\\\s+");
if (commands.length > 1) {
command = commands[0];
setShutdownTimeout(command);
}
} catch (Exception e) {
;// ignore
}
// Match against our command string
boolean match = command.toString().equals(shutdown);
if (match) {
LOG.trace("Shutdown command recieved via Telnet.");
LOG.trace("Stop after " + shutdownTimeout + "ms.");
try {
Thread.sleep(shutdownTimeout);
} catch (InterruptedException e) {
LOG.warn("Problems in shutdown timeout.");
}
stop();
return;
} else {
LOG.warn("Pax Runner: Invalid command.");
out.write("Invalid Command!"+NEWLINE);
out.flush();
}
// Close the socket now that we are done with it
try {
LOG.trace("Closing the socket now that we are done with it...");
socket.close();
} catch (IOException e) {
LOG.warn("Exception in closing socket..", e);
}
}
}, "ClientHandler-" + socket.getRemoteSocketAddress()
).start();
}
/**
* Reads the given stream for string commands.
*
* @param stream
* @return the command that was issued in the input stream.
*/
private String readStream(final InputStream stream) {
StringBuffer command = new StringBuffer();
int expected = 1024; // Cut off to avoid DoS attack
while (stream!=null && expected > 0) {
int ch = -1;
try {
ch = stream.read();
} catch (IOException e) {
ch = -1;
}
if (ch < 32) { // Control character or EOF terminates loop
break;
}
command.append((char) ch);
expected--;
}
return command.toString();
}
}
// Getters and Setters -----------------------------------------------------
private void setShutdown(String shutdown) {
if (shutdown != null && shutdown.length() > 0)
Daemon.shutdown = shutdown;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy