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.genesis.ant;
021    
022    import java.io.File;
023    import java.util.ArrayList;
024    import java.util.HashMap;
025    import java.util.Iterator;
026    import java.util.List;
027    import java.util.Map;
028    import java.util.Properties;
029    import java.util.StringTokenizer;
030    import java.util.Timer;
031    import java.util.TimerTask;
032    
033    import org.apache.maven.artifact.Artifact;
034    import org.apache.maven.artifact.repository.ArtifactRepository;
035    import org.apache.maven.plugin.MojoExecutionException;
036    import org.apache.maven.project.MavenProject;
037    import org.apache.tools.ant.taskdefs.Java;
038    import org.apache.tools.ant.types.Path;
039    import org.codehaus.plexus.util.FileUtils;
040    
041    import org.apache.geronimo.genesis.util.ObjectHolder;
042    
043    //
044    // FIXME: Need to find a better way to allow plugins to re-use the parameter configuration!
045    //
046    
047    /**
048     * Support for mojos that launch Java processes.
049     *
050     * @version $Rev: 463461 $ $Date: 2006-10-12 15:11:08 -0700 (Thu, 12 Oct 2006) $
051     */
052    public abstract class JavaLauncherMojoSupport
053        extends AntMojoSupport
054    {
055        //
056        // TODO: Use AntHelper component and extend from MojoSupport
057        //
058        
059        private Timer timer = new Timer(true);
060    
061        /**
062         * Set the maximum memory for the forked JVM.
063         *
064         * @parameter expression="${maximumMemory}"
065         */
066        private String maximumMemory = null;
067        
068        /**
069         * The base working directory where process will be started from, a sub-directory
070         * the process name will be used for the effective working directory.
071         *
072         * @parameter expression="${project.build.directory}"
073         * @required
074         */
075        protected File baseWorkingDirectory = null;
076    
077        /**
078         * Enable logging mode.
079         *
080         * @parameter expression="${logOutput}" default-value="false"
081         */
082        protected boolean logOutput = false;
083    
084        /**
085         * Flag to control if we background the process or block Maven execution.
086         *
087         * @parameter default-value="false"
088         * @required
089         */
090        protected boolean background = false;
091    
092        /**
093         * Timeout for the process in seconds.
094         *
095         * @parameter expression="${timeout}" default-value="-1"
096         */
097        protected int timeout = -1;
098    
099        /**
100         * Time in seconds to wait while verifing that the process has started (if there is custom validation).
101         *
102         * @parameter expression="${verifyTimeout}" default-value="-1"
103         */
104        private int verifyTimeout = -1;
105    
106        /**
107         * An array of option sets which can be enabled by setting <tt>options</tt>.
108         *
109         * @parameter
110         */
111        protected OptionSet[] optionSets = null;
112    
113        /**
114         * A comma seperated list of <tt>optionSets</tt> to enabled.
115         *
116         * @parameter expression="${options}"
117         */
118        protected String options = null;
119    
120        /**
121         * Map of of plugin artifacts.
122         *
123         * @parameter expression="${plugin.artifactMap}"
124         * @required
125         * @readonly
126         */
127        protected Map pluginArtifactMap = null;
128    
129        protected void doExecute() throws Exception {
130            log.info("Starting " + getProcessTitle() + "...");
131    
132            final Java java = (Java)createTask("java");
133    
134            File workingDirectory = getWorkingDirectory();
135            FileUtils.forceMkdir(workingDirectory);
136            java.setDir(workingDirectory);
137    
138            java.setFailonerror(true);
139            java.setFork(true);
140    
141            if (maximumMemory != null) {
142                java.setMaxmemory(maximumMemory);
143            }
144            
145            if (timeout > 0) {
146                log.info("Timeout after: " + timeout + " seconds");
147    
148                java.setTimeout(new Long(timeout * 1000));
149            }
150    
151            if (logOutput) {
152                File file = getLogFile();
153                log.info("Redirecting output to: " + file);
154                FileUtils.forceMkdir(file.getParentFile());
155    
156                java.setLogError(true);
157                java.setOutput(file);
158            }
159    
160            java.setClassname(getClassName());
161            setClassPath(java.createClasspath());
162    
163            applyOptionSets(java);
164    
165            customizeJava(java);
166    
167            // Holds any exception that was thrown during startup
168            final ObjectHolder errorHolder = new ObjectHolder();
169    
170            // Start the process in a seperate thread
171            Thread t = new Thread(getProcessTitle() + " Runner") {
172                public void run() {
173                    try {
174                        java.execute();
175                    }
176                    catch (Exception e) {
177                        errorHolder.set(e);
178    
179                        //
180                        // NOTE: Don't log here, as when the JVM exists an exception will get thrown by Ant
181                        //       but that should be fine.
182                        //
183                    }
184                }
185            };
186            t.start();
187    
188            log.debug("Waiting for " + getProcessTitle() + "...");
189    
190            // Setup a callback to time out verification
191            final ObjectHolder verifyTimedOut = new ObjectHolder();
192    
193            TimerTask timeoutTask = new TimerTask() {
194                public void run() {
195                    verifyTimedOut.set(Boolean.TRUE);
196                }
197            };
198    
199            if (verifyTimeout > 0) {
200                log.debug("Starting verify timeout task; triggers in: " + verifyTimeout + "s");
201                timer.schedule(timeoutTask, verifyTimeout * 1000);
202            }
203            
204            // Verify the process has started
205            boolean started = false;
206            while (!started) {
207                if (verifyTimedOut.isSet()) {
208                    throw new MojoExecutionException("Unable to verify if the " + getProcessTitle() + " process was started in the given time");
209                }
210    
211                if (errorHolder.isSet()) {
212                    throw new MojoExecutionException("Failed to launch " + getProcessTitle(), (Throwable)errorHolder.get());
213                }
214    
215                try {
216                    started = verifyProcessStarted();
217                }
218                catch (Exception e) {
219                    // ignore
220                }
221    
222                Thread.sleep(1000);
223            }
224    
225            log.info(getProcessTitle() + " started");
226    
227            if (!background) {
228                log.info("Waiting for " + getProcessTitle() + " to shutdown...");
229    
230                t.join();
231            }
232        }
233    
234        protected Artifact getPluginArtifact(final String name) throws MojoExecutionException {
235            assert name != null;
236    
237            Artifact artifact = (Artifact)pluginArtifactMap.get(name);
238            if (artifact == null) {
239                throw new MojoExecutionException("Unable to locate '" + name + "' in the list of plugin artifacts");
240            }
241    
242            return artifact;
243        }
244    
245        protected void appendArtifactFile(final Path classpath, final String name) throws MojoExecutionException {
246            assert classpath != null;
247            assert name != null;
248    
249            appendArtifact(classpath, getPluginArtifact(name));
250        }
251    
252        protected void appendArtifact(final Path classpath, final Artifact artifact) throws MojoExecutionException {
253            assert classpath != null;
254            assert artifact != null;
255    
256            File file = artifact.getFile();
257            if (file == null) {
258                throw new MojoExecutionException("Artifact does not have an attached file: " + artifact);
259            }
260            
261            classpath.createPathElement().setLocation(file);
262        }
263    
264        private void applyOptionSets(final Java java) throws MojoExecutionException {
265            assert java != null;
266    
267            //
268            // TODO: Add optionSet activation
269            //
270    
271            // Apply option sets
272            if (options != null  && (optionSets == null || optionSets.length == 0)) {
273                throw new MojoExecutionException("At least one optionSet must be defined to select one using options");
274            }
275            else if (options == null) {
276                options = "default";
277            }
278    
279            if (optionSets != null && optionSets.length != 0) {
280                OptionSet[] sets = selectOptionSets();
281    
282                for (int i=0; i < sets.length; i++) {
283                    if (log.isDebugEnabled()) {
284                        log.debug("Selected option set: " + sets[i]);
285                    }
286                    else {
287                        log.info("Selected option set: " + sets[i].getId());
288                    }
289    
290                    String[] options = sets[i].getOptions();
291                    if (options != null) {
292                        for (int j=0; j < options.length; j++) {
293                            java.createJvmarg().setValue(options[j]);
294                        }
295                    }
296    
297                    Properties props = sets[i].getProperties();
298                    if (props != null) {
299                        Iterator iter = props.keySet().iterator();
300                        while (iter.hasNext()) {
301                            String name = (String)iter.next();
302                            String value = props.getProperty(name);
303    
304                            setSystemProperty(java, name, value);
305                        }
306                    }
307                }
308            }
309        }
310    
311        private OptionSet[] selectOptionSets() throws MojoExecutionException {
312            // Make a map of the option sets and validate ids
313            Map map = new HashMap();
314            for (int i=0; i<optionSets.length; i++) {
315                if (log.isDebugEnabled()) {
316                    log.debug("Checking option set: " + optionSets[i]);
317                }
318    
319                String id = optionSets[i].getId();
320    
321                if (id == null && optionSets.length > 1) {
322                    throw new MojoExecutionException("Must specify id for optionSet when more than one optionSet is configured");
323                }
324                else if (id == null && optionSets.length == 1) {
325                    id = "default";
326                    optionSets[i].setId(id);
327                }
328    
329                assert id != null;
330                id = id.trim();
331    
332                if (map.containsKey(id)) {
333                    throw new MojoExecutionException("Must specify unique id for optionSet: " + optionSets[i]);
334                }
335                map.put(id, optionSets[i]);
336            }
337    
338            StringTokenizer stok = new StringTokenizer(options, ",");
339    
340            List selected = new ArrayList();
341            while (stok.hasMoreTokens()) {
342                String id = stok.nextToken();
343                OptionSet set = (OptionSet)map.get(options);
344    
345                if (set == null) {
346                    if ("default".equals(options)) {
347                        log.debug("Default optionSet selected, but no optionSet defined with that id; ignoring");
348                    }
349                    else {
350                        throw new MojoExecutionException("Missing optionSet for id: " + id);
351                    }
352                }
353                else {
354                    selected.add(set);
355                }
356            }
357    
358            return (OptionSet[]) selected.toArray(new OptionSet[selected.size()]);
359        }
360    
361        //
362        // MojoSupport Hooks
363        //
364    
365        /**
366         * The maven project.
367         *
368         * @parameter expression="${project}"
369         * @required
370         * @readonly
371         */
372        protected MavenProject project = null;
373    
374        protected MavenProject getProject() {
375            return project;
376        }
377    
378        /**
379         * @parameter expression="${localRepository}"
380         * @readonly
381         * @required
382         */
383        protected ArtifactRepository artifactRepository = null;
384    
385        protected ArtifactRepository getArtifactRepository() {
386            return artifactRepository;
387        }
388    
389        //
390        // Sub-class API
391        //
392    
393        protected abstract String getProcessName();
394    
395        protected String getProcessTitle() {
396            return getProcessName();
397        }
398    
399        protected File getWorkingDirectory() {
400            return new File(baseWorkingDirectory, getProcessName());
401        }
402    
403        protected File getLogFile() {
404            return new File(getWorkingDirectory(), getProcessName() + ".log");
405        }
406    
407        protected abstract String getClassName();
408    
409        protected abstract void setClassPath(Path classpath) throws Exception;
410    
411        protected void customizeJava(final Java java) throws MojoExecutionException {
412            assert java != null;
413    
414            // nothing by default
415        }
416        
417        protected boolean verifyProcessStarted() throws Exception {
418            return true;
419        }
420    }