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.awt.Frame;
021import java.awt.Image;
022import java.awt.Rectangle;
023import java.awt.Toolkit;
024import java.awt.event.WindowAdapter;
025import java.awt.event.WindowEvent;
026import java.io.FileOutputStream;
027import java.io.PrintStream;
028import java.lang.reflect.Method;
029
030/**
031 * A wrapper class that invokes another class'
032 * <code>main(String[])</code>. This particular class uses several system
033 * properties to control features:
034 * <ul>
035 * <li>Redirecting System.out and System.err.
036 * <li>Displaying a minimized window in the Windows taskbar.
037 * </ul>
038 * This class is normally not invoked directly. Instead, it is invoked by the
039 * {@link LaunchTask} class.
040 *
041 * @author Patrick Luby
042 */
043public class ChildMain extends Thread {
044
045    //----------------------------------------------------------- Static Fields
046
047    /**
048     * The appendOutput system property name.
049     */
050    public final static String APPEND_OUTPUT_PROP_NAME =
051        "org.apache.commons.launcher.appendOutput";
052
053    /**
054     * The displayMiminizedWindow system property name.
055     */
056    public final static String DISPLAY_MINIMIZED_WINDOW_PROP_NAME =
057        "org.apache.commons.launcher.displayMinimizedWindow";
058
059    /**
060     * The disposeMiminizedWindow system property name.
061     */
062    public final static String DISPOSE_MINIMIZED_WINDOW_PROP_NAME =
063        "org.apache.commons.launcher.disposeMinimizedWindow";
064
065    /**
066     * The executableName system property name.
067     */
068    public final static String EXECUTABLE_PROP_NAME =
069        "org.apache.commons.launcher.executableName";
070
071    /**
072     * The heartbeatFile system property name.
073     */
074    public final static String HEARTBEAT_FILE_PROP_NAME =
075        "org.apache.commons.launcher.heartbeatFile";
076
077    /**
078     * The miminizedWindowTitle system property name.
079     */
080    public final static String MINIMIZED_WINDOW_TITLE_PROP_NAME =
081        "org.apache.commons.launcher.minimizedWindowTitle";
082
083    /**
084     * The miminizedWindowIcon system property name.
085     */
086    public final static String MINIMIZED_WINDOW_ICON_PROP_NAME=
087        "org.apache.commons.launcher.minimizedWindowIcon";
088
089    /**
090     * The outputFile system property name.
091     */
092    public final static String OUTPUT_FILE_PROP_NAME =
093        "org.apache.commons.launcher.outputFile";
094
095    /**
096     * The waitForChild system property name.
097     */
098    public final static String WAIT_FOR_CHILD_PROP_NAME =
099        "org.apache.commons.launcher.waitForChild";
100
101    //------------------------------------------------------------------ Fields
102
103    /**
104     * Cached command line arguments
105     */
106    private String[] args = null;
107
108    //------------------------------------------------------------ Constructors
109
110    /**
111     * Construct an instance of this {@link Thread} subclass and cache the
112     * args parameter for use by the {@link #run()} method.
113     *
114     * @param group the ThreadGroup to use for this thread
115     * @param args the command line arguments
116     */
117    private ChildMain(ThreadGroup group, String[] args) {
118
119        super(group, ChildMain.class.getName());
120        this.args = args;
121
122    }
123
124    //---------------------------------------------------------- Static Methods
125
126    /**
127     * Main entry point for the child process. This method should only be
128     * invoked by the {@link LaunchTask} class.
129     *
130     * @param args command line arguments
131     */
132    public static void main(String[] args) {
133
134        // Invoke the target application in a separate thread so that we
135        // caught any uncaught errors thrown by the target application
136        Thread mainThread = new ChildMain(new ExitOnErrorThreadGroup(ChildMain.class.getName()), args);
137        mainThread.start();
138
139    }
140
141    //----------------------------------------------------------------- Methods
142
143    /**
144     * Invoke the target application.
145     */
146    public void run() {
147
148        // If there are no command line arguments, do nothing
149        if (args == null || args.length == 0)
150            return;
151
152        // Invoke the target application
153        try {
154
155            // Start the thread to check if the parent JVM exits.
156            boolean waitForChild = false;
157            if (System.getProperty(ChildMain.WAIT_FOR_CHILD_PROP_NAME) != null) {
158                waitForChild = true;
159                String heartbeatFile = System.getProperty(ChildMain.HEARTBEAT_FILE_PROP_NAME);
160                ParentListener heartbeat = new ParentListener(heartbeatFile);
161                // Make the thread a daemon thread so that it does not
162                // prevent this process from exiting when all of the
163                // appliation's threads finish.
164                heartbeat.setDaemon(true);
165                heartbeat.start();
166            }
167
168            // If applicable, redirect output and error streams
169            String outputPath = System.getProperty(ChildMain.OUTPUT_FILE_PROP_NAME);
170            if (outputPath != null) {
171                boolean appendOutput = false;
172                if (System.getProperty(ChildMain.APPEND_OUTPUT_PROP_NAME) != null)
173                    appendOutput = true;
174                PrintStream ps = new PrintStream(new FileOutputStream(outputPath, appendOutput), true);
175                System.setOut(ps);
176                System.setErr(ps);
177            }
178
179            // The first argument should be the class that we really want to
180            // invoke. Try to load the class and invoke its main(String[])
181            // method with the first argument shifted out.
182            Class mainClass = Class.forName(args[0]);
183            Class[] paramTypes = new Class[1];
184            Object[] paramValues = new Object[1];
185            String[] params = new String[args.length - 1];
186            // Shift args[0] out of the arguments
187            for (int i = 0; i < params.length; i++)
188                params[i] = args[i + 1];
189            paramTypes[0] = params.getClass();
190            paramValues[0] = params;
191
192            // Create the icon window if this is a waitForChild task
193            Frame frame = null;
194            boolean displayMinimizedWindow = false;
195            if (System.getProperty(ChildMain.DISPLAY_MINIMIZED_WINDOW_PROP_NAME) != null)
196                displayMinimizedWindow = true;
197            String osname = System.getProperty("os.name").toLowerCase();
198            if (displayMinimizedWindow && osname.indexOf("windows") >= 0) {
199                try {
200                    frame = new Frame();
201                    String title = System.getProperty(ChildMain.MINIMIZED_WINDOW_TITLE_PROP_NAME);
202                    if (title != null)
203                        frame.setTitle(title);
204                    frame.setState(Frame.ICONIFIED);
205                    String icon = System.getProperty(ChildMain.MINIMIZED_WINDOW_ICON_PROP_NAME);
206                    if (icon != null) {
207                        Image iconImage = Toolkit.getDefaultToolkit().createImage(icon);
208                        if (iconImage != null)
209                            frame.setIconImage(iconImage);
210                    }
211
212                    // Ensure that window always remains minimized
213                    frame.addWindowListener(new ChildWindowAdapter());
214                    Rectangle bounds = frame.getGraphicsConfiguration().getBounds();
215                    int width = (int)frame.getBounds().getWidth();
216                    int height = frame.getInsets().top + frame.getInsets().bottom;
217                    int x = (int)bounds.getWidth() - width;
218                    int y = (int)bounds.getHeight() - height;
219                    frame.setBounds(x, y, width, height);
220                    frame.setResizable(false);
221                    frame.setVisible(true);
222                } catch(Exception fe) {}
223            }
224
225            // Invoke the main() method
226            Method mainMethod = mainClass.getDeclaredMethod("main", paramTypes);
227            mainMethod.invoke(null, paramValues);
228
229            // Close the frame if it exists
230            if (frame != null && System.getProperty(ChildMain.DISPOSE_MINIMIZED_WINDOW_PROP_NAME) != null) {
231                // Exit this process. Closing or disposing of the window is not
232                // enough to allow the process to exit.
233                System.exit(0);
234            }
235
236        } catch (Throwable t) {
237            String message = t.getMessage();
238            t.printStackTrace();
239            System.exit(1);
240        }
241
242    }
243
244    /**
245     * A WindowAdapter subclass that causes the application to exit when its
246     * {@link #windowClosing(WindowEvent)} method is invoked.
247     */
248    private static class ChildWindowAdapter extends WindowAdapter {
249
250        /**
251         * Invoked when a window is in the process of being closed.
252         *
253         * @param e the event
254         */
255        public void windowClosing(WindowEvent e) {
256
257            System.exit(0);
258
259        }
260
261    }
262
263}