001 package org.tynamo.watchdog.services;
002
003 import java.io.File;
004 import java.io.FileOutputStream;
005 import java.io.IOException;
006 import java.io.InputStream;
007 import java.io.OutputStream;
008 import java.net.JarURLConnection;
009 import java.net.URISyntaxException;
010 import java.net.URL;
011 import java.util.jar.JarFile;
012
013 import org.apache.tapestry5.SymbolConstants;
014 import org.apache.tapestry5.internal.InternalConstants;
015 import org.apache.tapestry5.ioc.annotations.EagerLoad;
016 import org.apache.tapestry5.ioc.annotations.Inject;
017 import org.apache.tapestry5.ioc.annotations.Symbol;
018 import org.slf4j.Logger;
019 import org.tynamo.watchdog.StreamGobbler;
020
021 import tynamo_watchdog.Watchdog;
022
023 @EagerLoad
024 public class WatchdogServiceImpl implements WatchdogService {
025
026 private Process watchdog;
027 private WatchdogLeash watchdogLeash;
028 private volatile boolean watchdogAlarmed;
029
030 OutputStream watchdogOutputStream;
031 private final String appPackageName;
032 private final String smtpHost;
033 private final Integer smtpPort;
034 private final String sendEmail;
035 private final Logger logger;
036
037 public WatchdogServiceImpl(Logger logger, @Symbol(SymbolConstants.PRODUCTION_MODE) boolean productionMode,
038 @Inject @Symbol(InternalConstants.TAPESTRY_APP_PACKAGE_PARAM) final String appPackageName,
039 @Inject @Symbol(Watchdog.SMTP_HOST) final String smtpHost, @Symbol(Watchdog.SMTP_PORT) final Integer smtpPort,
040 @Inject @Symbol(Watchdog.SEND_EMAIL) String sendEmail) throws IOException, URISyntaxException, InterruptedException {
041 this.logger = logger;
042 this.appPackageName = appPackageName;
043 this.smtpHost = smtpHost;
044 this.smtpPort = smtpPort;
045 this.sendEmail = sendEmail;
046 if (productionMode) startWatchdog();
047 }
048
049 /**
050 * Extract a resource from jar, mark it for deletion upon exit, and return its location.
051 */
052 File extractFromJar(URL resource, File watchdogFolder) throws IOException {
053 // put this jar in a file system so that we can load jars from there
054 String fileName = resource.getPath().substring(resource.getPath().lastIndexOf("/"));
055
056 File file = new File(watchdogFolder, fileName);
057 try {
058 file.createNewFile();
059 } catch (IOException e) {
060 String tmpdir = System.getProperty("java.io.tmpdir");
061 IOException x = new IOException("Watchdog failed to create a temporary file in " + tmpdir);
062 x.initCause(e);
063 throw x;
064 }
065 InputStream is = resource.openStream();
066 try {
067 OutputStream os = new FileOutputStream(file);
068 try {
069 copyStream(is, os);
070 } finally {
071 os.close();
072 }
073 } finally {
074 is.close();
075 }
076
077 file.deleteOnExit();
078 return file;
079 }
080
081 private static void copyStream(InputStream in, OutputStream out) throws IOException {
082 byte[] buf = new byte[8192];
083 int len;
084 while ((len = in.read(buf)) > 0)
085 out.write(buf, 0, len);
086 }
087
088 File prepareWatchdog() throws IOException {
089 File tempFile = File.createTempFile("forwatchdog", "test");
090 tempFile.deleteOnExit();
091 File watchdogFolder = new File(tempFile.getParentFile(), Watchdog.class.getPackage().getName());
092 watchdogFolder.mkdir();
093
094 extractFromJar(Watchdog.class.getResource(Watchdog.class.getSimpleName() + ".class"), watchdogFolder);
095 extractFromJar(Watchdog.class.getResource(WatchdogModule.javamailSpec + ".jar"), watchdogFolder);
096 extractFromJar(Watchdog.class.getResource(WatchdogModule.javamailProvider + ".jar"), watchdogFolder);
097 return watchdogFolder.getParentFile();
098 }
099
100 /*
101 * (non-Javadoc)
102 *
103 * @see org.tynamo.watchdog.services.WatchdogService#startWatchdog()
104 */
105 public synchronized void startWatchdog() throws IOException, URISyntaxException, InterruptedException {
106 File watchdogFolder = prepareWatchdog();
107 // whoamI gives us the watchdog jar when libs are loaded separately which is less than ideal
108 // So don't use this at all, use appPackageName instead
109 // String appName = whoAmI();
110 // if (appName.isEmpty()) appName = "dev/exploded";
111
112 Process testJavaProcess = Runtime.getRuntime().exec("java -version");
113 // TODO You could also read the output and make sure java is at least 1.5
114
115 try {
116 if (testJavaProcess.waitFor() != 0) {
117 logger.error("Couldn't execute java in given environment - is java on PATH? Cannot start the watchdog");
118 return;
119 }
120 } catch (IllegalThreadStateException e) {
121 logger.error("Testing java execution didn't return immediately. Report this issue to Tynamo.org");
122 }
123
124 final String packageName = Watchdog.class.getPackage().getName();
125 String[] args = new String[11];
126 args[0] = "java";
127 args[1] = "-D" + Watchdog.SEND_EMAIL + "=" + sendEmail;
128 args[2] = "-D" + Watchdog.SMTP_HOST + "=" + smtpHost;
129 args[3] = "-D" + Watchdog.SMTP_PORT + "=" + smtpPort;
130 // With -Xms4m, at least 64-bit 1.6 jvm you get:
131 // Error occurred during initialization of VM
132 // Too small initial heap for new size specified
133 args[4] = "-Xms8m";
134 args[5] = "-Xmx16m";
135 args[6] = "-XX:MaxPermSize=16m";
136 args[7] = "-cp";
137 args[8] = "." + File.pathSeparator + packageName + File.separator + WatchdogModule.javamailSpec + ".jar" + File.pathSeparator
138 + packageName + File.separator + WatchdogModule.javamailProvider + ".jar";
139 args[9] = Watchdog.class.getName();
140 args[10] = appPackageName;
141
142 StringBuilder command = new StringBuilder();
143 for (String value : args) {
144 command.append(value);
145 command.append(" ");
146 }
147
148 logger.info("Starting watchdog with command: " + command.toString());
149
150 // You *have* to start with inherited environment or set at least some of the most critical
151 // environment variables manually (such as SystemRoot), otherwise I got
152 // Unrecognized Windows Sockets error: 10106: errors
153 watchdog = Runtime.getRuntime().exec(args, null, watchdogFolder);
154
155 (new StreamGobbler(watchdog.getErrorStream(), "WATCHDOG ERROR")).start();
156 (new StreamGobbler(watchdog.getInputStream(), "WATCHDOG OUTPUT")).start();
157
158 watchdogOutputStream = watchdog.getOutputStream();
159
160 // Intentionally try to cause an exception to see if the process is still alive and kicking
161 try {
162 int exitCode = watchdog.exitValue();
163 logger.error("Watchdog failed to start: the process exited immediately with exit code " + exitCode);
164 return;
165 } catch (IllegalThreadStateException e) {
166 // Ignore, process hasn't exited
167 }
168
169 watchdogLeash = new WatchdogLeash();
170 watchdogLeash.start();
171
172 // TODO Make adding shutdownhook configurable
173 Runtime.getRuntime().addShutdownHook(new Thread() {
174 @Override
175 public void run() {
176 try {
177 logger.warn("Dismissing watchdog before controlled JVM shutdown");
178 dismissWatchdog();
179 } catch (IOException e) {
180 logger.warn("Couldn't controllably dismiss the watchdog. Is watchdog still alive?");
181 }
182 try {
183 int exitCode = watchdog.exitValue();
184 logger.error("Watchdog has already exited with exit code " + exitCode);
185 } catch (IllegalThreadStateException e) {
186 // Ignore, can't do anything about the watchdog process
187 }
188 }
189 });
190
191 }
192
193 /*
194 * (non-Javadoc)
195 *
196 * @see org.tynamo.watchdog.services.WatchdogService#dismissWatchdog()
197 */
198 public void dismissWatchdog() throws IOException {
199 watchdogOutputStream.write(Watchdog.STOP_MESSAGE.getBytes());
200 watchdogOutputStream.flush();
201 }
202
203 /*
204 * (non-Javadoc)
205 *
206 * @see org.tynamo.watchdog.services.WatchdogService#alarmWatchdog()
207 */
208 public void alarmWatchdog() throws IOException {
209 watchdogOutputStream.close();
210 }
211
212 class WatchdogLeash extends Thread {
213
214 public WatchdogLeash() {
215 setDaemon(true);
216 }
217
218 @Override
219 public void run() {
220 try {
221 while (true) {
222 watchdogOutputStream.write(0);
223 watchdogOutputStream.flush();
224 sleep(5000);
225 }
226 } catch (IOException e) {
227 // Alarming the watchdog manually triggers the IOException
228 if (watchdogAlarmed) return;
229 logger.warn("IO exception occurred while communicating with the watchdog process. Was the watchdog killed? Releasing the leash");
230 try {
231 logger.info("Watchdog process exited with exit code " + watchdog.exitValue());
232 } catch (IllegalThreadStateException e1) {
233 // Ignore, process hasn't exited
234 }
235 } catch (InterruptedException e) {
236 }
237 }
238 }
239
240 /**
241 * Figures out the URL of <tt>war</tt>.
242 */
243 public String whoAmI() throws IOException, URISyntaxException {
244 // There is no portable way to find where the locally cached copy
245 // of war/jar is; JDK 6 is too smart. (See HUDSON-2326 - this code was adapted from Hudson.)
246 try {
247 URL classFile = Watchdog.class.getResource(Watchdog.class.getSimpleName() + ".class");
248 JarFile jf = ((JarURLConnection) classFile.openConnection()).getJarFile();
249 String fileName = jf.getName();
250 fileName = fileName.substring(fileName.lastIndexOf("/") + 1);
251 } catch (Exception x) {
252 System.err.println("ZipFile.name trick did not work, using fallback: " + x);
253 }
254 URL classFile = Watchdog.class.getProtectionDomain().getCodeSource().getLocation();
255 String fileName = classFile.toString();
256 return fileName.substring(fileName.lastIndexOf("/") + 1);
257 }
258 }