001    /**
002     *  Licensed to the Apache Software Foundation (ASF) under one
003     *  or more contributor license agreements.  See the NOTICE file
004     *  distributed with this work for additional information
005     *  regarding copyright ownership.  The ASF licenses this file
006     *  to you under the Apache License, Version 2.0 (the
007     *  "License"); you may not use this file except in compliance
008     *  with the License.  You may obtain a copy of the License at
009     *
010     *    http://www.apache.org/licenses/LICENSE-2.0
011     *
012     *  Unless required by applicable law or agreed to in writing,
013     *  software distributed under the License is distributed on an
014     *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     *  KIND, either express or implied.  See the License for the
016     *  specific language governing permissions and limitations
017     *  under the License.
018     */
019    
020    package org.apache.geronimo.mavenplugins.geronimo.server;
021    
022    import java.io.File;
023    
024    import java.util.Timer;
025    import java.util.TimerTask;
026    import java.util.Map;
027    import java.util.HashMap;
028    import java.util.Properties;
029    import java.util.Iterator;
030    import java.util.StringTokenizer;
031    import java.util.List;
032    import java.util.ArrayList;
033    
034    import org.apache.maven.plugin.MojoExecutionException;
035    
036    import org.apache.tools.ant.taskdefs.Java;
037    
038    import org.codehaus.mojo.pluginsupport.util.ObjectHolder;
039    
040    import org.apache.geronimo.mavenplugins.geronimo.ServerProxy;
041    
042    import org.codehaus.plexus.util.FileUtils;
043    
044    import org.apache.commons.lang.time.StopWatch;
045    
046    /**
047     * Start the Geronimo server.
048     *
049     * @goal start-server
050     *
051     * @version $Rev: 552399 $ $Date: 2007-07-02 00:31:56 -0400 (Mon, 02 Jul 2007) $
052     */
053    public class StartServerMojo
054        extends InstallerMojoSupport
055    {
056        /**
057         * Set the false to skip the installation of the assembly, re-using anything
058         * that is already there.
059         *
060         * @parameter expression="${install}" default-value="true"
061         */
062        private boolean install = true;
063    
064        /**
065         * Flag to control if we background the server or block Maven execution.
066         *
067         * @parameter expression="${background}" default-value="false"
068         */
069        private boolean background = false;
070    
071        /**
072         * Set the maximum memory for the forked JVM.
073         *
074         * @parameter expression="${maximumMemory}"
075         */
076        private String maximumMemory = null;
077        
078        /**
079         * The location of the Java Virtual Machine executable to launch the server with.
080         *
081         * @paramter
082         */
083        private File javaVirtualMachine;
084        
085        /**
086         * Enable quiet mode.
087         *
088         * @parameter expression="${quiet}" default-value="false"
089         */
090        private boolean quiet = false;
091    
092        /**
093         * Enable verbose mode.
094         *
095         * @parameter expression="${verbose}" default-value="false"
096         */
097        private boolean verbose = false;
098    
099        /**
100         * Enable veryverbose mode.
101         *
102         * @parameter expression="${veryverbose}" default-value="false"
103         */
104        private boolean veryverbose = false;
105    
106        /**
107         * Time in seconds to wait before terminating the forked JVM.
108         *
109         * @parameter expression="${timeout}" default-value="-1"
110         */
111        private int timeout = -1;
112    
113        /**
114         * Time in seconds to wait while verifing that the server has started.
115         *
116         * @parameter expression="${verifyTimeout}" default-value="-1"
117         */
118        private int verifyTimeout = -1;
119    
120        /**
121         * Enable propagation of <tt>org.apache.geronimo.*</tt> and <tt>geronimo.*</tt>
122         * properties from Maven to the forked server process.
123         *
124         * @parameter expression="${propagateGeronimoProperties}" default-value="true"
125         */
126        private boolean propagateGeronimoProperties;
127    
128        /**
129         * An array of option sets which can be enabled by setting optionSetId.
130         *
131         * @parameter
132         */
133        private OptionSet[] optionSets = null;
134    
135        /**
136         * A comma seperated list of optionSets to enabled.
137         *
138         * @parameter expression="${options}"
139         */
140        private String options = null;
141    
142        /**
143         * A list of module names to be started using --override.
144         *
145         * @parameter
146         */
147        private String[] startModules = null;
148    
149        private Timer timer = new Timer(true);
150    
151        protected void doExecute() throws Exception {
152            if (install) {
153                installAssembly();
154            }
155            else {
156                log.info("Skipping assembly installation");
157    
158                if (!geronimoHome.exists()) {
159                    throw new MojoExecutionException("Missing pre-installed assembly directory: " + geronimoHome);
160                }
161            }
162    
163            log.info("Starting Geronimo server...");
164    
165            // Setup the JVM to start the server with
166            final Java java = (Java)createTask("java");
167            java.setJar(new File(geronimoHome, "bin/server.jar"));
168            java.setDir(geronimoHome);
169            java.setFailonerror(true);
170            java.setFork(true);
171    
172            if (javaVirtualMachine != null) {
173                if (!javaVirtualMachine.exists()) {
174                    throw new MojoExecutionException("Java virtual machine is not valid: " + javaVirtualMachine);
175                }
176                
177                log.info("Using Java virtual machine: " + javaVirtualMachine);
178                java.setJvm(javaVirtualMachine.getCanonicalPath());
179            }
180            
181            if (timeout > 0) {
182                java.setTimeout(new Long(timeout * 1000));
183            }
184    
185            if (maximumMemory != null) {
186                java.setMaxmemory(maximumMemory);
187            }
188    
189            // Load the Java programming language agent for JPA
190            File javaAgentJar = new File(geronimoHome, "bin/jpa.jar");
191            if (javaAgentJar.exists()) {
192                java.createJvmarg().setValue("-javaagent:" + javaAgentJar.getCanonicalPath());
193            }
194    
195            // Propagate some properties from Maven to the server if enabled
196            if (propagateGeronimoProperties) {
197                Properties props = System.getProperties();
198                Iterator iter = props.keySet().iterator();
199                while (iter.hasNext()) {
200                    String name = (String)iter.next();
201                    String value = System.getProperty(name);
202    
203                    if (name.equals("geronimo.bootstrap.logging.enabled")) {
204                        // Skip this property, never propagate it
205                    }
206                    else if (name.startsWith("org.apache.geronimo") || name.startsWith("geronimo")) {
207                        log.debug("Propagating: " + name + "=" + value);
208                        setSystemProperty(java, name, value);
209                    }
210                }
211            }
212    
213            // Apply option sets
214            if (options != null  && (optionSets == null || optionSets.length == 0)) {
215                throw new MojoExecutionException("At least one optionSet must be defined to select one using options");
216            }
217            else if (options == null) {
218                options = "default";
219            }
220    
221            if (optionSets != null && optionSets.length != 0) {
222                OptionSet[] sets = selectOptionSets();
223    
224                for (int i=0; i < sets.length; i++) {
225                    if (log.isDebugEnabled()) {
226                        log.debug("Selected option set: " + sets[i]);
227                    }
228                    else {
229                        log.info("Selected option set: " + sets[i].getId());
230                    }
231    
232                    String[] options = sets[i].getOptions();
233                    if (options != null) {
234                        for (int j=0; j < options.length; j++) {
235                            java.createJvmarg().setValue(options[j]);
236                        }
237                    }
238    
239                    Properties props = sets[i].getProperties();
240                    if (props != null) {
241                        Iterator iter = props.keySet().iterator();
242                        while (iter.hasNext()) {
243                            String name = (String)iter.next();
244                            String value = props.getProperty(name);
245    
246                            setSystemProperty(java, name, value);
247                        }
248                    }
249                }
250            }
251    
252            // Set the properties which we pass to the JVM from the startup script
253            setSystemProperty(java, "org.apache.geronimo.base.dir", geronimoHome);
254            // Use relative path
255            setSystemProperty(java, "java.io.tmpdir", "var/temp");
256            setSystemProperty(java, "java.endorsed.dirs", prefixSystemPath("java.endorsed.dirs", new File(geronimoHome, "lib/endorsed")));
257            setSystemProperty(java, "java.ext.dirs", prefixSystemPath("java.ext.dirs", new File(geronimoHome, "lib/ext")));
258    
259            if (quiet) {
260                java.createArg().setValue("--quiet");
261            }
262            else {
263                java.createArg().setValue("--long");
264            }
265    
266            if (verbose) {
267                java.createArg().setValue("--verbose");
268            }
269    
270            if (veryverbose) {
271                java.createArg().setValue("--veryverbose");
272            }
273    
274            if (startModules != null) {
275                if (startModules.length == 0) {
276                    throw new MojoExecutionException("At least one module name must be configured with startModule");
277                }
278    
279                log.info("Overriding the set of modules to be started");
280    
281                java.createArg().setValue("--override");
282    
283                for (int i=0; i < startModules.length; i++) {
284                    java.createArg().setValue(startModules[i]);
285                }
286            }
287    
288            //
289            // TODO: Check if this really does capture STDERR or not!
290            //
291    
292            if (logOutput) {
293                File file = getLogFile();
294                FileUtils.forceMkdir(file.getParentFile());
295    
296                log.info("Redirecting output to: " + file);
297    
298                java.setOutput(file);
299            }
300    
301            // Holds any exception that was thrown during startup
302            final ObjectHolder errorHolder = new ObjectHolder();
303    
304            StopWatch watch = new StopWatch();
305            watch.start();
306    
307            // Start the server int a seperate thread
308            Thread t = new Thread("Geronimo Server Runner") {
309                public void run() {
310                    try {
311                        java.execute();
312                    }
313                    catch (Exception e) {
314                        errorHolder.set(e);
315    
316                        //
317                        // NOTE: Don't log here, as when the JVM exists an exception will get thrown by Ant
318                        //       but that should be fine.
319                        //
320                    }
321                }
322            };
323            t.start();
324    
325            log.info("Waiting for Geronimo server...");
326    
327            // Setup a callback to time out verification
328            final ObjectHolder verifyTimedOut = new ObjectHolder();
329    
330            TimerTask timeoutTask = new TimerTask() {
331                public void run() {
332                    verifyTimedOut.set(Boolean.TRUE);
333                }
334            };
335    
336            if (verifyTimeout > 0) {
337                log.debug("Starting verify timeout task; triggers in: " + verifyTimeout + " seconds");
338                timer.schedule(timeoutTask, verifyTimeout * 1000);
339            }
340    
341            // Verify server started
342            ServerProxy server = new ServerProxy(hostname, port, username, password);
343            boolean started = false;
344            while (!started) {
345                if (verifyTimedOut.isSet()) {
346                    throw new MojoExecutionException("Unable to verify if the server was started in the given time (" + verifyTimeout + " seconds)");
347                }
348    
349                if (errorHolder.isSet()) {
350                    throw new MojoExecutionException("Failed to start Geronimo server", (Throwable)errorHolder.get());
351                }
352    
353                started = server.isFullyStarted();
354    
355                if (!started) {
356                    Throwable error = server.getLastError();
357                    if (error != null) {
358                        log.debug("Server query failed; ignoring", error);
359                    }
360    
361                    Thread.sleep(1000);
362                }
363            }
364    
365            // Stop the timer, server should be up now
366            timeoutTask.cancel();
367    
368            log.info("Geronimo server started in " + watch);
369    
370            if (!background) {
371                log.info("Waiting for Geronimo server to shutdown...");
372    
373                t.join();
374            }
375        }
376    
377        private String prefixSystemPath(final String name, final File file) {
378            assert name != null;
379            assert file != null;
380    
381            String dirs = file.getPath();
382            String prop = System.getProperty(name, "");
383            if (prop.length() > 0) {
384                dirs += File.pathSeparator;
385                dirs += prop;
386            }
387            return dirs;
388        }
389    
390        private OptionSet[] selectOptionSets() throws MojoExecutionException {
391            // Make a map of the option sets and validate ids
392            Map map = new HashMap();
393            for (int i=0; i<optionSets.length; i++) {
394                if (log.isDebugEnabled()) {
395                    log.debug("Checking option set: " + optionSets[i]);
396                }
397    
398                String id = optionSets[i].getId();
399    
400                if (id == null && optionSets.length > 1) {
401                    throw new MojoExecutionException("Must specify id for optionSet when more than one optionSet is configured");
402                }
403                else if (id == null && optionSets.length == 1) {
404                    id = "default";
405                    optionSets[i].setId(id);
406                }
407    
408                assert id != null;
409                id = id.trim();
410    
411                if (map.containsKey(id)) {
412                    throw new MojoExecutionException("Must specify unique id for optionSet: " + id);
413                }
414                map.put(id, optionSets[i]);
415            }
416    
417            StringTokenizer stok = new StringTokenizer(options, ",");
418    
419            List selected = new ArrayList();
420            while (stok.hasMoreTokens()) {
421                String id = stok.nextToken();
422                OptionSet set = (OptionSet)map.get(id);
423    
424                if (set == null) {
425                    if ("default".equals(id)) {
426                        log.debug("Default optionSet selected, but no optionSet defined with that id; ignoring");
427                    }
428                    else {
429                        log.warn("Missing optionSet for id: " + id);
430                    }
431                }
432                else {
433                    selected.add(set);
434                }
435            }
436    
437            return (OptionSet[]) selected.toArray(new OptionSet[selected.size()]);
438        }
439    
440        protected String getFullClassName() {
441            return this.getClass().getName();
442        }
443    }