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: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $
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         * @parameter
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                        if (log.isDebugEnabled()) {
208                            log.debug("Propagating: " + name + "=" + value);
209                        }
210                        setSystemProperty(java, name, value);
211                    }
212                }
213            }
214    
215            // Apply option sets
216            if (options != null  && (optionSets == null || optionSets.length == 0)) {
217                throw new MojoExecutionException("At least one optionSet must be defined to select one using options");
218            }
219            else if (options == null) {
220                options = "default";
221            }
222    
223            if (optionSets != null && optionSets.length != 0) {
224                OptionSet[] sets = selectOptionSets();
225    
226                for (int i=0; i < sets.length; i++) {
227                    if (log.isDebugEnabled()) {
228                        log.debug("Selected option set: " + sets[i]);
229                    }
230                    else {
231                        log.info("Selected option set: " + sets[i].getId());
232                    }
233    
234                    String[] options = sets[i].getOptions();
235                    if (options != null) {
236                        for (int j=0; j < options.length; j++) {
237                            java.createJvmarg().setValue(options[j]);
238                        }
239                    }
240    
241                    Properties props = sets[i].getProperties();
242                    if (props != null) {
243                        Iterator iter = props.keySet().iterator();
244                        while (iter.hasNext()) {
245                            String name = (String)iter.next();
246                            String value = props.getProperty(name);
247    
248                            setSystemProperty(java, name, value);
249                        }
250                    }
251                }
252            }
253    
254            // Set the properties which we pass to the JVM from the startup script
255            setSystemProperty(java, "org.apache.geronimo.base.dir", geronimoHome);
256            // Use relative path
257            setSystemProperty(java, "java.io.tmpdir", "var/temp");
258            setSystemProperty(java, "java.endorsed.dirs", prefixSystemPath("java.endorsed.dirs", new File(geronimoHome, "lib/endorsed")));
259            setSystemProperty(java, "java.ext.dirs", prefixSystemPath("java.ext.dirs", new File(geronimoHome, "lib/ext")));
260    
261            if (quiet) {
262                java.createArg().setValue("--quiet");
263            }
264            else {
265                java.createArg().setValue("--long");
266            }
267    
268            if (verbose) {
269                java.createArg().setValue("--verbose");
270            }
271    
272            if (veryverbose) {
273                java.createArg().setValue("--veryverbose");
274            }
275    
276            if (startModules != null) {
277                if (startModules.length == 0) {
278                    throw new MojoExecutionException("At least one module name must be configured with startModule");
279                }
280    
281                log.info("Overriding the set of modules to be started");
282    
283                java.createArg().setValue("--override");
284    
285                for (int i=0; i < startModules.length; i++) {
286                    java.createArg().setValue(startModules[i]);
287                }
288            }
289    
290            //
291            // TODO: Check if this really does capture STDERR or not!
292            //
293    
294            if (logOutput) {
295                File file = getLogFile();
296                FileUtils.forceMkdir(file.getParentFile());
297    
298                log.info("Redirecting output to: " + file);
299    
300                java.setOutput(file);
301            }
302    
303            // Holds any exception that was thrown during startup
304            final ObjectHolder errorHolder = new ObjectHolder();
305    
306            StopWatch watch = new StopWatch();
307            watch.start();
308    
309            // Start the server int a seperate thread
310            Thread t = new Thread("Geronimo Server Runner") {
311                public void run() {
312                    try {
313                        java.execute();
314                    }
315                    catch (Exception e) {
316                        errorHolder.set(e);
317    
318                        //
319                        // NOTE: Don't log here, as when the JVM exists an exception will get thrown by Ant
320                        //       but that should be fine.
321                        //
322                    }
323                }
324            };
325            t.start();
326    
327            log.info("Waiting for Geronimo server...");
328    
329            // Setup a callback to time out verification
330            final ObjectHolder verifyTimedOut = new ObjectHolder();
331    
332            TimerTask timeoutTask = new TimerTask() {
333                public void run() {
334                    verifyTimedOut.set(Boolean.TRUE);
335                }
336            };
337    
338            if (verifyTimeout > 0) {
339                if (log.isDebugEnabled()) {
340                    log.debug("Starting verify timeout task; triggers in: " + verifyTimeout + " seconds");
341                }
342                timer.schedule(timeoutTask, verifyTimeout * 1000);
343            }
344    
345            // Verify server started
346            ServerProxy server = new ServerProxy(hostname, port, username, password);
347            boolean started = false;
348            while (!started) {
349                if (verifyTimedOut.isSet()) {
350                    throw new MojoExecutionException("Unable to verify if the server was started in the given time (" + verifyTimeout + " seconds)");
351                }
352    
353                if (errorHolder.isSet()) {
354                    throw new MojoExecutionException("Failed to start Geronimo server", (Throwable)errorHolder.get());
355                }
356    
357                started = server.isFullyStarted();
358    
359                if (!started) {
360                    Throwable error = server.getLastError();
361                    if ((error != null) && (log.isDebugEnabled())) {
362                        log.debug("Server query failed; ignoring", error);
363                    }
364    
365                    Thread.sleep(1000);
366                }
367            }
368            server.closeConnection();
369    
370            // Stop the timer, server should be up now
371            timeoutTask.cancel();
372    
373            log.info("Geronimo server started in " + watch);
374    
375            if (!background) {
376                log.info("Waiting for Geronimo server to shutdown...");
377    
378                t.join();
379            }
380        }
381    
382        private String prefixSystemPath(final String name, final File file) {
383            assert name != null;
384            assert file != null;
385    
386            String dirs = file.getPath();
387            String prop = System.getProperty(name, "");
388            if (prop.length() > 0) {
389                dirs += File.pathSeparator;
390                dirs += prop;
391            }
392            return dirs;
393        }
394    
395        private OptionSet[] selectOptionSets() throws MojoExecutionException {
396            // Make a map of the option sets and validate ids
397            Map map = new HashMap();
398            for (int i=0; i<optionSets.length; i++) {
399                if (log.isDebugEnabled()) {
400                    log.debug("Checking option set: " + optionSets[i]);
401                }
402    
403                String id = optionSets[i].getId();
404    
405                if (id == null && optionSets.length > 1) {
406                    throw new MojoExecutionException("Must specify id for optionSet when more than one optionSet is configured");
407                }
408                else if (id == null && optionSets.length == 1) {
409                    id = "default";
410                    optionSets[i].setId(id);
411                }
412    
413                assert id != null;
414                id = id.trim();
415    
416                if (map.containsKey(id)) {
417                    throw new MojoExecutionException("Must specify unique id for optionSet: " + id);
418                }
419                map.put(id, optionSets[i]);
420            }
421    
422            StringTokenizer stok = new StringTokenizer(options, ",");
423    
424            List selected = new ArrayList();
425            while (stok.hasMoreTokens()) {
426                String id = stok.nextToken();
427                OptionSet set = (OptionSet)map.get(id);
428    
429                if (set == null) {
430                    if ("default".equals(id)) {
431                        if (log.isDebugEnabled()) {
432                            log.debug("Default optionSet selected, but no optionSet defined with that id; ignoring");
433                        }
434                    }
435                    else {
436                        log.warn("Missing optionSet for id: " + id);
437                    }
438                }
439                else {
440                    selected.add(set);
441                }
442            }
443    
444            return (OptionSet[]) selected.toArray(new OptionSet[selected.size()]);
445        }
446    
447        protected String getFullClassName() {
448            return this.getClass().getName();
449        }
450    }