/** * Licensed to JumpMind Inc under one or more contributor * license agreements. See the NOTICE file distributed * with this work for additional information regarding * copyright ownership. JumpMind Inc licenses this file * to you under the GNU General Public License, version 3.0 (GPLv3) * (the "License"); you may not use this file except in compliance * with the License. * * You should have received a copy of the GNU General Public License, * version 3.0 (GPLv3) along with this library; if not, see * <http://www.gnu.org/licenses/>. * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.jumpmind.symmetric.wrapper; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.logging.Level; import java.util.logging.LogManager; import java.util.logging.Logger; import org.jumpmind.symmetric.wrapper.Constants.Status; import com.sun.jna.Platform; public abstract class WrapperService { private static final Logger logger = Logger.getLogger(WrapperService.class.getName()); protected WrapperConfig config; protected boolean keepRunning = true; protected Process child; protected BufferedReader childReader; private static WrapperService instance; public static WrapperService getInstance() { if (Platform.isWindows()) { instance = new WindowsService(); } else { instance = new UnixService(); } return instance; } public void loadConfig(String configFile) throws IOException { config = new WrapperConfig(configFile); setWorkingDirectory(config.getWorkingDirectory().getAbsolutePath()); } public void start() { if (isRunning()) { throw new WrapperException(Constants.RC_SERVER_ALREADY_RUNNING, 0, "Server is already running"); } System.out.println("Waiting for server to start"); ArrayList<String> cmdLine = getWrapperCommand("exec"); Process process = null; boolean success = false; int rc = 0; try { ProcessBuilder pb = new ProcessBuilder(cmdLine); pb.redirectErrorStream(true); process = pb.start(); if (!(success = waitForPid(getProcessPid(process)))) { rc = process.exitValue(); } } catch (IOException e) { rc = -1; System.out.println(e.getMessage()); } if (success) { System.out.println("Started"); } else { System.err.println(commandToString(cmdLine)); try { BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); String line = null; while ((line = reader.readLine()) != null) { System.err.println(line); } reader.close(); } catch (Exception e) { } throw new WrapperException(Constants.RC_FAIL_EXECUTION, rc, "Failed second stage"); } } public void init() { execJava(false); } public void console() { if (isRunning()) { throw new WrapperException(Constants.RC_SERVER_ALREADY_RUNNING, 0, "Server is already running"); } execJava(true); } protected void execJava(boolean isConsole) { try { LogManager.getLogManager().reset(); WrapperLogHandler handler = new WrapperLogHandler(config.getLogFile(), config.getLogFileMaxSize(), config.getLogFileMaxFiles()); handler.setFormatter(new WrapperLogFormatter()); Logger rootLogger = Logger.getLogger(""); rootLogger.setLevel(Level.parse(config.getLogFileLogLevel())); rootLogger.addHandler(handler); } catch (IOException e) { throw new WrapperException(Constants.RC_FAIL_WRITE_LOG_FILE, 0, "Cannot open log file " + config.getLogFile(), e); } int pid = getCurrentPid(); writePidToFile(pid, config.getWrapperPidFile()); logger.log(Level.INFO, "Started wrapper as PID " + pid); ArrayList<String> cmd = config.getCommand(isConsole); String cmdString = commandToString(cmd); boolean usingHeapDump = cmdString.indexOf("-XX:+HeapDumpOnOutOfMemoryError") != -1; logger.log(Level.INFO, "Working directory is " + System.getProperty("user.dir")); long startTime = 0; int startCount = 0; boolean startProcess = true, restartDetected = false; int serverPid = 0; while (keepRunning) { if (startProcess) { logger.log(Level.INFO, "Executing " + cmdString); if (startCount == 0) { updateStatus(Status.START_PENDING); } startTime = System.currentTimeMillis(); ProcessBuilder pb = new ProcessBuilder(cmd); pb.redirectErrorStream(true); try { child = pb.start(); } catch (IOException e) { logger.log(Level.SEVERE, "Failed to execute: " + e.getMessage()); updateStatus(Status.STOPPED); throw new WrapperException(Constants.RC_FAIL_EXECUTION, -1, "Failed executing server", e); } serverPid = getProcessPid(child); logger.log(Level.INFO, "Started server as PID " + serverPid); writePidToFile(serverPid, config.getServerPidFile()); if (startCount == 0) { Runtime.getRuntime().addShutdownHook(new ShutdownHook()); updateStatus(Status.RUNNING); } startProcess = false; startCount++; } else { try { childReader = new BufferedReader(new InputStreamReader(child.getInputStream())); String line = null; while ((line = childReader.readLine()) != null) { System.out.println(line); logger.log(Level.INFO, line, "java"); if ((usingHeapDump && line.matches("Heap dump file created.*")) || (!usingHeapDump && line.matches("java.lang.OutOfMemoryError.*")) || line.matches(".*java.net.BindException.*")) { logger.log(Level.SEVERE, "Stopping server because its output matches a failure condition"); child.destroy(); childReader.close(); stopProcess(serverPid, "symmetricds"); break; } if (line.equalsIgnoreCase("Restarting")) { restartDetected = true; } } } catch (IOException e) { logger.log(Level.SEVERE, "Error while reading from process"); } if (restartDetected) { restartDetected = false; startProcess = true; } else if (keepRunning) { logger.log(Level.SEVERE, "Unexpected exit from server: " + child.exitValue()); long runTime = System.currentTimeMillis() - startTime; if (System.currentTimeMillis() - startTime < 7000) { logger.log(Level.SEVERE, "Stopping because server exited too quickly after only " + runTime + " milliseconds"); updateStatus(Status.STOPPED); throw new WrapperException(Constants.RC_SERVER_EXITED, child.exitValue(), "Unexpected exit from server"); } else { startProcess = true; } } } } } public void stop() { int symPid = readPidFromFile(config.getServerPidFile()); int wrapperPid = readPidFromFile(config.getWrapperPidFile()); if (!isPidRunning(symPid) && !isPidRunning(wrapperPid)) { throw new WrapperException(Constants.RC_SERVER_NOT_RUNNING, 0, "Server is not running"); } System.out.println("Waiting for server to stop"); if (!(stopProcess(wrapperPid, "wrapper") && stopProcess(symPid, "symmetricds"))) { throw new WrapperException(Constants.RC_FAIL_STOP_SERVER, 0, "Server did not stop"); } System.out.println("Stopped"); } protected boolean stopProcess(int pid, String name) { killProcess(pid, false); if (waitForPid(pid)) { killProcess(pid, true); if (waitForPid(pid)) { System.out.println("ERROR: '" + name + "' did not stop"); return false; } } return true; } protected void shutdown() { if (keepRunning) { keepRunning = false; new Thread() { @Override public void run() { logger.log(Level.INFO, "Stopping server"); child.destroy(); try { childReader.close(); } catch (IOException e) { } logger.log(Level.INFO, "Stopping wrapper"); deletePidFile(config.getWrapperPidFile()); deletePidFile(config.getServerPidFile()); updateStatus(Status.STOPPED); System.exit(0); } }.start(); } } public void restart() { if (isRunning()) { stop(); } start(); } public void relaunchAsPrivileged(String cmd, String args) { } public void status() { boolean isRunning = isRunning(); System.out.println("Installed: " + isInstalled()); System.out.println("Running: " + isRunning); if (isRunning) { System.out.println("Wrapper PID: " + readPidFromFile(config.getWrapperPidFile())); System.out.println("Server PID: " + readPidFromFile(config.getServerPidFile())); } } public boolean isRunning() { return isPidRunning(readPidFromFile(config.getWrapperPidFile())) || isPidRunning(readPidFromFile(config.getServerPidFile())); } public int getWrapperPid() { return readPidFromFile(config.getWrapperPidFile()); } public int getServerPid() { return readPidFromFile(config.getServerPidFile()); } protected String commandToString(ArrayList<String> cmd) { StringBuilder sb = new StringBuilder(); for (String c : cmd) { sb.append(c).append(" "); } return sb.toString(); } protected ArrayList<String> getWrapperCommand(String arg) { ArrayList<String> cmd = new ArrayList<String>(); String quote = getWrapperCommandQuote(); cmd.add(quote + config.getJavaCommand() + quote); cmd.addAll(config.getOptions()); cmd.add("-jar"); cmd.add(quote + config.getWrapperJarPath() + quote); cmd.add(arg); cmd.add(quote + config.getConfigFile() + quote); return cmd; } protected ArrayList<String> getPrivilegedCommand() { ArrayList<String> cmd = new ArrayList<String>(); String quote = getWrapperCommandQuote(); cmd.add(quote + config.getJavaCommand() + quote); return cmd; } protected String getWrapperCommandQuote() { return ""; } protected int readPidFromFile(String filename) { int pid = 0; try { BufferedReader reader = new BufferedReader(new FileReader(filename)); pid = Integer.parseInt(reader.readLine()); reader.close(); } catch (FileNotFoundException e) { } catch (IOException e) { e.printStackTrace(); } return pid; } protected void writePidToFile(int pid, String filename) { try { FileWriter writer = new FileWriter(filename, false); writer.write(String.valueOf(pid)); writer.close(); } catch (IOException e) { e.printStackTrace(); } } protected void deletePidFile(String filename) { new File(filename).delete(); } protected boolean waitForPid(int pid) { int seconds = 0; while (seconds <= 5) { System.out.print("."); if (!isPidRunning(pid)) { break; } try { Thread.sleep(1000); } catch (InterruptedException e) { } seconds++; } System.out.println(""); return isPidRunning(pid); } protected void updateStatus(Status status) { } class ShutdownHook extends Thread { public void run() { shutdown(); } } public abstract void install(); public abstract void uninstall(); public abstract boolean isInstalled(); public abstract boolean isPrivileged(); protected abstract boolean setWorkingDirectory(String dir); protected abstract int getProcessPid(Process process); protected abstract int getCurrentPid(); protected abstract boolean isPidRunning(int pid); protected abstract void killProcess(int pid, boolean isTerminate); }