001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 * 
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 * 
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.launcher;
019
020import java.io.File;
021import java.io.InputStream;
022import java.io.IOException;
023
024/**
025 * A class for detecting if the parent JVM that launched this process has
026 * terminated.
027 *
028 * @author Patrick Luby
029 */
030public class ParentListener extends Thread {
031
032    //------------------------------------------------------------------ Fields
033
034    /**
035     * Cached heartbeat file.
036     */
037    private File heartbeatFile = null;
038
039    //------------------------------------------------------------ Constructors
040
041    /**
042     * Validates and caches a lock file created by the parent JVM.
043     *
044     * @param path the lock file that the parent JVM has an open
045     *  FileOutputStream
046     * @throws IOException if the heartbeat cannot be converted into a valid
047     *  File object
048     */
049    public ParentListener(String path) throws IOException {
050
051        if (path == null)
052            throw new IOException();
053
054        // Make sure we have a valid path
055        heartbeatFile = new File(path);
056        heartbeatFile.getCanonicalPath();
057
058    }
059
060    //----------------------------------------------------------------- Methods
061
062    /**
063     * Periodically check that the parent JVM has not terminated. On all
064     * platforms other than Windows, this method will check that System.in has
065     * not been closed. On Windows NT, 2000, and XP the lock file specified in
066     * the {@link #ParentListener(String)} constructor is monitored as reading
067     * System.in will block the entire process on Windows machines that use
068     * some versions of Unix shells such as MKS, etc. No monitoring is done
069     * on Window 95, 98, and ME.
070     */ 
071    public void run() {
072
073        String osname = System.getProperty("os.name").toLowerCase();
074
075        // We need to use file locking on Windows since reading System.in
076        // will block the entire process on some Windows machines.
077        if (osname.indexOf("windows") >= 0) {
078
079            // Do nothing if this is a Windows 9x platform since our file
080            // locking mechanism does not work on the early versions of
081            // Windows
082            if (osname.indexOf("nt") == -1 && osname.indexOf("2000") == -1 && osname.indexOf("xp") == -1)
083                return;
084
085            // If we can delete the heartbeatFile on Windows, it means that
086            // the parent JVM has closed its FileOutputStream on the file.
087            // Note that the parent JVM's stream should only be closed when
088            // it exits.
089            for ( ; ; ) {
090                if (heartbeatFile.delete())
091                    break;
092                // Wait awhile before we try again
093                yield();
094                try {
095                    sleep(5000);
096                } catch (Exception e) {}
097            }
098
099        } else {
100
101            // Cache System.in in case the application redirects
102            InputStream is = System.in;
103            int bytesAvailable = 0;
104            int bytesRead = 0;
105            byte[] buf = new byte[1024];
106            try {
107                while (true) {
108                    synchronized (is) {
109                        // Mark the stream position so that other threads can
110                        // reread the strea
111                        is.mark(buf.length);
112                        // Read one more byte than has already been read to
113                        // force the stream to wait for input
114                        bytesAvailable = is.available();
115                        if (bytesAvailable < buf.length) {
116                            bytesRead = is.read(buf, 0, bytesAvailable + 1);
117                            // Reset so that we "unread" the bytes that we read
118                            is.reset();
119                            if (bytesRead == -1)
120                                break;
121                        } else {
122                            // Make the buffer larger
123                            if (buf.length < Integer.MAX_VALUE / 2)
124                                buf = new byte[buf.length * 2];
125                        }
126                    }
127                    yield();
128                }
129            } catch (IOException ioe) {}
130
131        }
132
133        // Clean up before exiting
134        if (heartbeatFile != null)
135            heartbeatFile.delete();
136
137        // Exit this process since the parent JVM has exited
138        System.exit(0);
139
140    }
141
142}