package net.sf.tomcatmanager;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.net.URLConnection;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
import net.sf.tomcatmanager.MainView.YesNoCancel;
import net.sf.tomcatmanager.Model.ServerState;
/**
* This class represents the controller, which acts upon the model and views.
*
* @author Martin Edling Andersson, Leonard van Driel
*/
public class Controller {
private static final transient Logger logger = Logger.getLogger(Controller.class.toString());
private static final int POST_STOP_WAIT_SECONDS = 5;
// model-view-controller
private Model model;
private MainView mainView;
private PreferencesView preferencesView;
private LogView logView;
private ViewHelper viewHelper;
private Preferences preferences;
private ScriptRunner startRunner;
private ScriptRunner stopRunner;
/**
* This inner class runs the server status loop, which checks whether the
* server is running by opening a stream to the server's URL.
*/
private class ServerStatusLoop implements Runnable {
public void run() {
logger.fine("Starting server status loop");
while (true) {
boolean connected = false;
try {
URL url = new URL("http", model.getServerUrl(), model.getServerPort(), "");
URLConnection connection = url.openConnection();
connection.connect();
connected = true;
} catch (Exception e) {
}
model.setServerStarted(connected);
try {
Thread.sleep(model.getSleepTime());
} catch (InterruptedException e) {
logger.warning("Server status loop was interrupted: " + e);
break;
}
}
}
}
/**
* This inner class runs the execution of a bash script for starting and
* stopping the server.
*/
private class ScriptRunner {
private String scriptParam;
private ServerState failState;
private Process process;
public ScriptRunner(String scriptParam, ServerState failState) {
this.scriptParam = scriptParam;
this.failState = failState;
}
public void run() {
stopScriptProcess();
ProcessBuilder builder = new ProcessBuilder();
builder.command("/bin/sh", "-c", model.getCatalinaScriptFile() + " " + scriptParam);
builder.environment().put(model.getJavaHomeEnvironmentVariable(), model.getJavaHomePath());
builder.environment().put(model.getTomcatLocationEnvironmentVariable(), model.getTomcatLocationPath());
builder.directory(new File(model.getTomcatLocationPath()));
builder.redirectErrorStream(true);
try {
process = builder.start();
} catch (IOException e) {
logger.warning("Unable to run process: " + e);
if (mainView != null) {
mainView.showWarning("The server could not be started, please check the preferences.",
"Server could not be started");
}
setServerState(failState);
stopScriptProcess();
}
if (process != null) {
OutputStream stream;
if (logView != null) {
stream = logView.getLogOutputStream();
} else {
stream = System.out;
}
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stream));
try {
for (String s = reader.readLine(); s != null; s = reader.readLine()) {
writer.write(s + "\n");
writer.flush();
if (Thread.interrupted()) {
logger.warning("Read process stream loop was interupted");
break;
}
}
} catch (IOException e) {
}
stopScriptProcess();
}
}
public synchronized void stopScriptProcess() {
if (process != null) {
process.destroy();
process = null;
}
}
public synchronized boolean isScriptRunning() {
return process != null;
}
}
/**
* A rather empty constructor.
*/
public Controller() {
logger.setLevel(TomcatManagerGui.DEFAULT_LOG_LEVEL);
preferences = Preferences.userNodeForPackage(getClass());
}
/**
* Setup the java logging facility by lowering the console handler level and
* adding a file handler.
*/
public void postInit() {
// open up all handlers, to allow fine logs to come through
for (Handler handler : Logger.getLogger("").getHandlers()) {
handler.setLevel(Level.ALL);
}
model.updateLogPath();
logger.info(About.NAME + " loaded");
}
/**
* Resets all the application's preferences.
*/
public void resetAllPreferences() {
logger.fine("Resetting all preferences");
try {
if (preferences.nodeExists("")) {
preferences.removeNode();
}
} catch (BackingStoreException e) {
logger.warning("Unable to reset preferences: " + e);
if (mainView != null) {
mainView.showWarning("The preferences could not be reset, please investigate the application log.",
"Preferences could not be reset");
}
}
preferences = Preferences.userNodeForPackage(getClass());
model.loadFromPreferences();
if (mainView != null) {
mainView.loadFromPreferences();
}
if (logView != null) {
logView.loadFromPreferences();
}
}
private void saveAllPreferences() {
if (preferencesView != null) {
preferencesView.saveToModel();
}
if (mainView != null) {
mainView.saveToPreferences();
}
if (logView != null) {
logView.saveToPreferences();
}
model.saveToPreferences();
try {
preferences.flush();
} catch (BackingStoreException e) {
logger.warning("Unable to store main view state: " + e);
if (mainView != null) {
mainView.showWarning("The preferenes could not be saved, please investigate the application log.",
"The preferenes could not be saved");
}
}
}
private void hideAll() {
if (preferencesView != null) {
preferencesView.setVisible(false);
}
if (mainView != null) {
mainView.setVisible(false);
}
if (logView != null) {
logView.setVisible(false);
}
if (viewHelper != null) {
viewHelper.getRestartAction().setEnabled(false);
viewHelper.getShowLogAction().setEnabled(false);
viewHelper.getShowManagerAction().setEnabled(false);
viewHelper.getStatusAction().setEnabled(false);
}
}
/**
* Initiates periodical server status checking by running a server status
* loop class.
*/
public void startServerStatusLoop() {
ServerStatusLoop messageLoop = new ServerStatusLoop();
new Thread(messageLoop).start();
}
/**
* Starts the server by running a script runner on the startup script.
*/
public void startServer(boolean runInThread) {
logger.info("Starting up server");
setServerState(ServerState.STARTING);
Runnable runnable = new Runnable() {
public void run() {
if (startRunner != null) {
startRunner.stopScriptProcess();
}
if (stopRunner != null) {
stopRunner.stopScriptProcess();
}
startRunner = new ScriptRunner(model.getStartupScriptParam(), ServerState.STARTING_FAILED);
startRunner.run();
}
};
if (runInThread) {
new Thread(runnable).start();
} else {
runnable.run();
}
}
/**
* Stops the server by running a script runner on the shutdown script.
*/
public void stopServer(boolean runInThread) {
logger.info("Shutting down server");
setServerState(ServerState.STOPPING);
Runnable runnable = new Runnable() {
public void run() {
if (stopRunner != null) {
stopRunner.stopScriptProcess();
}
stopRunner = new ScriptRunner(model.getShutdownScriptParam(), ServerState.STOPPING_FAILED);
stopRunner.run();
if (startRunner != null) {
for (int i = 0; i < POST_STOP_WAIT_SECONDS && startRunner.isScriptRunning(); i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
logger.warning("Server stop was interrupted: " + e);
break;
}
}
startRunner.stopScriptProcess();
}
}
};
if (runInThread) {
new Thread(runnable).start();
} else {
runnable.run();
}
}
/**
* Stops the server by running a script runner on the shutdown script.
*/
public void restartServer(boolean runInThread) {
logger.info("Restarting server");
Runnable runnable = new Runnable() {
public void run() {
stopServer(false);
startServer(false);
}
};
if (runInThread) {
new Thread(runnable).start();
} else {
runnable.run();
}
}
private void forceAllStop() {
if (startRunner != null) {
startRunner.stopScriptProcess();
}
if (stopRunner != null) {
stopRunner.stopScriptProcess();
}
}
public void toggleServer() {
switch (model.getServerState()) {
case STARTED:
case STOPPING_FAILED:
stopServer(true);
break;
case STOPPED:
case STARTING_FAILED:
startServer(true);
break;
default:
break;
}
}
public void setServerState(ServerState serverState) {
if (model.getServerState() != serverState) {
model.setServerState(serverState);
if (mainView != null) {
mainView.updateServerState(serverState);
}
}
}
/**
* Asks the user for confirmation if needed, then quits.
*/
public void tryQuit() {
YesNoCancel shouldStop = YesNoCancel.NO;
if (model.isServerStartedOrAboutTo() && mainView != null) {
shouldStop = mainView.askToStop();
}
if (shouldStop != YesNoCancel.CANCEL) {
doQuit(shouldStop == YesNoCancel.YES, true);
}
}
/**
* Stores some program state and quits the application.
*
* Assumes running in Swing thread
*/
private void doQuit(boolean stopServer, boolean runInThread) {
hideAll();
Runnable runnable = new Runnable() {
private boolean stop;
public void run() {
if (stop) {
stopServer(false);
}
saveAllPreferences();
if (model.isServerStoppedOrAboutTo()) {
forceAllStop();
}
logger.info(About.NAME + " exit");
System.exit(0);
}
public Runnable set(boolean stop) {
this.stop = stop;
return this;
}
}.set(stopServer);
if (runInThread) {
new Thread(runnable).start();
} else {
runnable.run();
}
}
public Model getModel() {
return model;
}
public void setModel(Model model) {
this.model = model;
}
public Preferences getPreferences() {
return preferences;
}
public MainView getMainView() {
return mainView;
}
public void setMainView(MainView mainView) {
this.mainView = mainView;
}
public LogView getLogView() {
return logView;
}
public void setLogView(LogView logView) {
this.logView = logView;
}
public void setPreferencesView(PreferencesView preferencesView) {
this.preferencesView = preferencesView;
}
public ViewHelper getViewHelper() {
return viewHelper;
}
public void setViewHelper(ViewHelper viewHelper) {
this.viewHelper = viewHelper;
}
}