View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *  http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.geronimo.genesis.ant;
21  
22  import java.io.File;
23  import java.util.ArrayList;
24  import java.util.HashMap;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Properties;
29  import java.util.StringTokenizer;
30  import java.util.Timer;
31  import java.util.TimerTask;
32  
33  import org.apache.maven.artifact.Artifact;
34  import org.apache.maven.artifact.repository.ArtifactRepository;
35  import org.apache.maven.plugin.MojoExecutionException;
36  import org.apache.maven.project.MavenProject;
37  import org.apache.tools.ant.taskdefs.Java;
38  import org.apache.tools.ant.types.Path;
39  import org.codehaus.plexus.util.FileUtils;
40  
41  import org.apache.geronimo.genesis.util.ObjectHolder;
42  
43  //
44  // FIXME: Need to find a better way to allow plugins to re-use the parameter configuration!
45  //
46  
47  /**
48   * Support for mojos that launch Java processes.
49   *
50   * @version $Rev: 463461 $ $Date: 2006-10-12 15:11:08 -0700 (Thu, 12 Oct 2006) $
51   */
52  public abstract class JavaLauncherMojoSupport
53      extends AntMojoSupport
54  {
55      //
56      // TODO: Use AntHelper component and extend from MojoSupport
57      //
58      
59      private Timer timer = new Timer(true);
60  
61      /**
62       * Set the maximum memory for the forked JVM.
63       *
64       * @parameter expression="${maximumMemory}"
65       */
66      private String maximumMemory = null;
67      
68      /**
69       * The base working directory where process will be started from, a sub-directory
70       * the process name will be used for the effective working directory.
71       *
72       * @parameter expression="${project.build.directory}"
73       * @required
74       */
75      protected File baseWorkingDirectory = null;
76  
77      /**
78       * Enable logging mode.
79       *
80       * @parameter expression="${logOutput}" default-value="false"
81       */
82      protected boolean logOutput = false;
83  
84      /**
85       * Flag to control if we background the process or block Maven execution.
86       *
87       * @parameter default-value="false"
88       * @required
89       */
90      protected boolean background = false;
91  
92      /**
93       * Timeout for the process in seconds.
94       *
95       * @parameter expression="${timeout}" default-value="-1"
96       */
97      protected int timeout = -1;
98  
99      /**
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 }