View Javadoc

1   /**
2    * Copyright 2004-2012 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.maven.mojo;
17  
18  import java.io.File;
19  import java.io.IOException;
20  import java.io.LineNumberReader;
21  import java.util.HashMap;
22  import java.util.List;
23  import java.util.Map;
24  
25  import org.apache.maven.artifact.Artifact;
26  import org.apache.maven.artifact.DependencyResolutionRequiredException;
27  import org.apache.maven.artifact.repository.ArtifactRepository;
28  import org.apache.maven.plugin.AbstractMojo;
29  import org.apache.maven.plugin.MojoExecutionException;
30  import org.apache.maven.project.MavenProject;
31  import org.apache.maven.project.MavenProjectHelper;
32  import org.apache.tools.ant.BuildException;
33  import org.apache.tools.ant.BuildLogger;
34  import org.apache.tools.ant.Project;
35  import org.apache.tools.ant.ProjectHelper;
36  import org.apache.tools.ant.taskdefs.Typedef;
37  import org.apache.tools.ant.types.Path;
38  import org.codehaus.plexus.util.FileUtils;
39  import org.codehaus.plexus.util.IOUtil;
40  import org.codehaus.plexus.util.ReaderFactory;
41  import org.codehaus.plexus.util.StringUtils;
42  import org.kuali.maven.common.AntMavenUtils;
43  import org.kuali.maven.common.ResourceUtils;
44  
45  /**
46   * <p>
47   * Maven Ant Mojo. Allows Maven to invoke a target inside an Ant build file. The build file can be located on the file
48   * system, the ant-maven-plugin classpath, or any resource URL that Spring 3.0 can understand.
49   * </p>
50   *
51   * <p>
52   * By default, this mojo makes the following available to Ant build files as both properties and references:
53   * </p>
54   *
55   * maven.compile.classpath=The classpath Maven is using for compilation<br>
56   * maven.runtime.classpath=The classpath Maven is using at runtime<br>
57   * maven.test.classpath=The classpath Maven is using for testing<br>
58   * maven.plugin.classpath=The classpath for the ant-maven-plugin
59   *
60   *
61   * <p>
62   * These are available as Ant references:
63   * </p>
64   *
65   * maven.project=MavenProject<br>
66   * maven.project.helper=MavenProjectHelper<br>
67   * maven.local.repository=ArtifactRepository<br>
68   *
69   * @goal run
70   * @threadSafe
71   * @requiresDependencyResolution test
72   */
73  public class AntMojo extends AbstractMojo {
74      ResourceUtils resourceUtils = new ResourceUtils();
75      AntMavenUtils antMvnUtils = new AntMavenUtils();
76      private static final String FS = System.getProperty("file.separator");
77  
78      public static final String ANT_DIR = "ant";
79      public static final String ANT_BUILD_DIR = "target" + FS + ANT_DIR;
80  
81      /**
82       * The refid used to store the Maven project object in the Ant build.
83       */
84      public static final String DEFAULT_MAVEN_PROJECT_REFID = "maven.project";
85  
86      /**
87       * The refid used to store the Maven project helper object in the Ant build.
88       */
89      public static final String DEFAULT_MAVEN_PROJECT_HELPER_REFID = "maven.project.helper";
90  
91      /**
92       * The refid used to store the Maven local repository object in the Ant build.
93       */
94      public static final String DEFAULT_MAVEN_LOCAL_REPOSITORY_REFID = "maven.local.repository";
95  
96      /**
97       * The default target name.
98       */
99      public static final String DEFAULT_ANT_TARGET_NAME = "main";
100 
101     /**
102      * The default encoding to use for the generated Ant build.
103      */
104     public static final String UTF_8 = "UTF-8";
105 
106     public static final String XML_HEADER = "<?xml version=\"1.0\" encoding=\"" + UTF_8 + "\" ?>\n";
107 
108     /**
109      * The path to The XML file containing the definition of the Maven tasks.
110      */
111     public static final String ANTLIB = "org/apache/maven/ant/tasks/antlib.xml";
112 
113     /**
114      * The URI which defines the built in Ant tasks
115      */
116     public static final String TASK_URI = "antlib:org.apache.maven.ant.tasks";
117 
118     /**
119      * The Maven project object
120      *
121      * @parameter expression="${project}"
122      * @readonly
123      */
124     private MavenProject project;
125 
126     /**
127      * The Maven project helper object
128      *
129      * @component
130      */
131     private MavenProjectHelper projectHelper;
132 
133     /**
134      * The plugin dependencies.
135      *
136      * @parameter expression="${plugin.artifacts}"
137      * @required
138      * @readonly
139      */
140     private List<Artifact> pluginArtifacts;
141 
142     /**
143      * The local Maven repository
144      *
145      * @parameter expression="${localRepository}"
146      * @readonly
147      */
148     protected ArtifactRepository localRepository;
149 
150     /**
151      * String to prepend to project and dependency property names.
152      *
153      * @parameter expression="${ant.propertyPrefix}" default-value=""
154      */
155     private String propertyPrefix = "";
156 
157     /**
158      * The xml tag prefix to use for the built in Ant tasks. This prefix needs to be prepended to each task referenced.
159      * For example, a prefix of "mvn" means that the attachartifact task is referenced by "&lt;mvn:attachartifact&gt;"
160      * The default value of an empty string means that no prefix is used for the tasks.
161      *
162      * @parameter expression="${ant.customTaskPrefix}" default-value=""
163      */
164     private String customTaskPrefix = "";
165 
166     /**
167      * Specifies whether the Antrun execution should be skipped.
168      *
169      * @parameter expression="${ant.skip}" default-value="false"
170      */
171     private boolean skip;
172 
173     /**
174      * Specifies whether the Ant properties should be propagated to the Maven properties.
175      *
176      * @parameter expression="${ant.exportAntProperties}" default-value="false"
177      */
178     private boolean exportAntProperties;
179 
180     /**
181      * Specifies whether a failure in the ant build leads to a failure of the Maven build.
182      *
183      * If this value is 'false', the Maven build will proceed even if the ant build fails. If it is 'true', then the
184      * Maven build fails if the ant build fails.
185      *
186      * @parameter expression="${ant.failOnError}" default-value="true"
187      */
188     private boolean failOnError;
189 
190     /**
191      * The build file to use. This supports Spring 3.0 resource URL expressions eg "classpath:build.xml" or
192      * "http://myurl/build.xml". The ant-maven-plugin classpath is what is searched when using the "classpath:" notation
193      *
194      * @parameter expression="${ant.file}" default-value="build.xml"
195      * @required
196      */
197     private String file;
198 
199     /**
200      * The target inside the build file to invoke. If not provided, the default target from the specified build file
201      * will be executed
202      *
203      * @parameter expression="${ant.target}"
204      */
205     private String target;
206 
207     /**
208      * Filename to redirect the ant output to. This is relative to the base directory of the current project
209      *
210      * @parameter expression="${ant.output}"
211      */
212     private String output;
213 
214     /**
215      * If true, pass all Maven properties to Ant
216      *
217      * @parameter expression="${ant.inheritAll}" default-value="true"
218      */
219     private String inheritAll;
220 
221     /**
222      * If true, pass Maven object references to Ant (MavenProject, MavenProjectHelper, ArtifactRepository - for the
223      * local repo)
224      *
225      * @parameter expression="${ant.inheritRefs}" default-value="true"
226      */
227     private String inheritRefs;
228 
229     /**
230      * If they give us "http://myurl/mybuild.xml", this gets set to "mybuild.xml" by the handleAntFile() method
231      */
232     private String antFilename;
233 
234     /**
235      * If they give us "http://myurl/mybuild.xml" this gets set to "target/ant/mybuild.xml" by the handleAntFile()
236      * method
237      */
238     private String relativeLocalFilename;
239 
240     /**
241 	 *
242 	 */
243     @Override
244     public void execute() throws MojoExecutionException {
245         // Might be skipping this execution
246         if (isSkip()) {
247             return;
248         }
249 
250         // The currently executing project
251         MavenProject mavenProject = getMavenProject();
252 
253         try {
254             // Setup the build file
255             handleAntfile();
256 
257             // Initialize an Ant project
258             Project antProject = getAntProject();
259 
260             // Create the Ant equivalents of important Maven classpath's
261             Map<String, Path> pathRefs = antMvnUtils.getPathRefs(antProject, mavenProject, pluginArtifacts);
262 
263             // Collect some Maven objects
264             Map<String, ?> mavenRefs = getMavenRefs(mavenProject);
265 
266             // Add both as references to the Ant project
267             antMvnUtils.addRefs(antProject, pathRefs);
268             antMvnUtils.addRefs(antProject, mavenRefs);
269 
270             // Add the Maven classpath's as simple properties (for convenience)
271             antMvnUtils.setPathProperties(antProject, pathRefs);
272 
273             // Initialize Maven ant tasks
274             initMavenTasks(antProject);
275 
276             // Ant project needs actual properties vs. using expression evaluator when calling an external build file.
277             antMvnUtils.copyProperties(mavenProject, antProject, propertyPrefix, getLog(), localRepository);
278 
279             // Execute the target from our wrapper. This calls the target from the build file they supplied
280             getLog().info("Executing tasks");
281             antProject.executeTarget(DEFAULT_ANT_TARGET_NAME);
282             getLog().info("Executed tasks");
283 
284             // Copy properties from Ant back to Maven (if needed)
285             if (exportAntProperties) {
286                 antMvnUtils.copyProperties(antProject, mavenProject, getLog());
287             }
288         } catch (DependencyResolutionRequiredException e) {
289             throw new MojoExecutionException("DependencyResolutionRequiredException: " + e.getMessage(), e);
290         } catch (BuildException e) {
291             handleBuildException(e);
292         } catch (Throwable e) {
293             throw new MojoExecutionException("Error executing ant tasks: " + e.getMessage(), e);
294         }
295     }
296 
297     /**
298      * Determine if mojo execution should be skipped
299      */
300     protected boolean isSkip() {
301         if (skip) {
302             getLog().info("Skipping Ant execution");
303             return true;
304         }
305         return false;
306     }
307 
308     /**
309      * Create a wrapper build file that calls into the build file they supplied us with. Initialize an Ant project from
310      * the wrapper file.
311      */
312     public Project getAntProject() throws IOException {
313         Project antProject = new Project();
314         File antBuildFile = createBuildWrapper();
315         ProjectHelper.configureProject(antProject, antBuildFile);
316         antProject.init();
317         // Setup logging
318         BuildLogger antLogger = antMvnUtils.getBuildLogger(getLog());
319         antProject.addBuildListener(antLogger);
320 
321         return antProject;
322     }
323 
324     /**
325      * Collect Maven model objects, the currently executing project, the project helper, and the local repository
326      */
327     protected Map<String, ?> getMavenRefs(MavenProject mavenProject) {
328         Map<String, Object> mavenRefs = new HashMap<String, Object>();
329         mavenRefs.put(DEFAULT_MAVEN_PROJECT_REFID, getMavenProject());
330         mavenRefs.put(DEFAULT_MAVEN_PROJECT_HELPER_REFID, projectHelper);
331         mavenRefs.put(DEFAULT_MAVEN_LOCAL_REPOSITORY_REFID, localRepository);
332         return mavenRefs;
333     }
334 
335     /**
336      * There was an Ant build exception
337      */
338     protected void handleBuildException(BuildException e) throws MojoExecutionException {
339         StringBuffer sb = new StringBuffer();
340         sb.append("An Ant BuildException has occured: " + e.getMessage());
341         String fragment = findFragment(e);
342         if (fragment != null) {
343             sb.append("\n").append(fragment);
344         }
345         if (!failOnError) {
346             getLog().info(sb.toString(), e);
347             return; // do not register roots.
348         } else {
349             throw new MojoExecutionException(sb.toString(), e);
350         }
351     }
352 
353     /**
354      * Get the current Maven project
355      *
356      * @return current Maven project
357      */
358     public MavenProject getMavenProject() {
359         return this.project;
360     }
361 
362     public void initMavenTasks(Project antProject) {
363         getLog().debug("Initialize Maven Ant Tasks");
364         Typedef typedef = new Typedef();
365         typedef.setProject(antProject);
366         typedef.setResource(ANTLIB);
367         if (!customTaskPrefix.equals("")) {
368             typedef.setURI(TASK_URI);
369         }
370         typedef.execute();
371     }
372 
373     /**
374      * Default XML that wraps the build file they supplied us with
375      */
376     protected String getDefaultXML(AntTaskPojo atp) throws IOException {
377         StringBuilder sb = new StringBuilder();
378         sb.append(XML_HEADER);
379         sb.append(getProjectOpen());
380         sb.append("  <target name=\"" + DEFAULT_ANT_TARGET_NAME + "\">\n");
381         sb.append("    " + getXML(atp) + "\n");
382         sb.append("  </target>\n");
383         sb.append("</project>\n");
384         return sb.toString();
385     }
386 
387     /**
388      * Write the ant target and surrounding tags to a temporary file
389      */
390     protected File createBuildWrapper() throws IOException {
391         AntTaskPojo atp = getAntTaskPojo();
392         String xml = getDefaultXML(atp);
393 
394         File buildFile = new File(ANT_BUILD_DIR + FS + antFilename);
395 
396         buildFile.getParentFile().mkdirs();
397         FileUtils.fileWrite(buildFile.getAbsolutePath(), UTF_8, xml);
398         return buildFile;
399     }
400 
401     /**
402      * Copy the build file to a local temp directory and preserve some information about the filename
403      */
404     protected void handleAntfile() throws IOException {
405         String filename = resourceUtils.getFilename(file);
406 
407         // The fileName should probably use the plugin executionId instead of target
408         if (!StringUtils.isBlank(target)) {
409             filename = target + "-" + filename;
410         }
411         antFilename = filename;
412         relativeLocalFilename = ANT_BUILD_DIR + FS + "local-" + antFilename;
413         File localFile = new File(relativeLocalFilename);
414         resourceUtils.copy(file, localFile.getAbsolutePath());
415     }
416 
417     /**
418      * Aggregate some of the Maven configuration into a pojo
419      */
420     protected AntTaskPojo getAntTaskPojo() {
421         AntTaskPojo pojo = new AntTaskPojo();
422         pojo.setAntfile(relativeLocalFilename);
423         pojo.setTarget(target);
424         pojo.setOutput(output);
425         pojo.setInheritAll(Boolean.parseBoolean(inheritAll));
426         pojo.setInheritRefs(Boolean.parseBoolean(inheritRefs));
427         pojo.setDir(project.getBasedir().getAbsolutePath());
428         return pojo;
429     }
430 
431     /**
432      * XML for the Ant project tag
433      */
434     protected String getProjectOpen() {
435         StringBuilder sb = new StringBuilder();
436         sb.append("<project");
437         sb.append(" name=\"ant-maven\"");
438         sb.append(" default=\"" + DEFAULT_ANT_TARGET_NAME + "\"");
439         sb.append(" basedir=\"" + project.getBasedir().getAbsolutePath() + "\"");
440         if (!StringUtils.isBlank(customTaskPrefix)) {
441             sb.append(" xmlns:" + customTaskPrefix + "=\"" + TASK_URI + "\"");
442         }
443         sb.append(">");
444         sb.append("\n");
445         return sb.toString();
446     }
447 
448     /**
449      * @param buildException
450      *            not null
451      * @return the fragment XML part where the buildException occurs.
452      */
453     protected String findFragment(BuildException buildException) {
454         if (buildException == null || buildException.getLocation() == null
455                 || buildException.getLocation().getFileName() == null) {
456             return null;
457         }
458 
459         File antFile = new File(buildException.getLocation().getFileName());
460         if (!antFile.exists()) {
461             return null;
462         }
463 
464         LineNumberReader reader = null;
465         try {
466             reader = new LineNumberReader(ReaderFactory.newXmlReader(antFile));
467             String line = "";
468             while ((line = reader.readLine()) != null) {
469                 if (reader.getLineNumber() == buildException.getLocation().getLineNumber()) {
470                     return "around Ant part ..." + line.trim() + "... @ "
471                             + buildException.getLocation().getLineNumber() + ":"
472                             + buildException.getLocation().getColumnNumber() + " in " + antFile.getAbsolutePath();
473                 }
474             }
475         } catch (Exception e) {
476             getLog().debug(e.getMessage(), e);
477             return null;
478         } finally {
479             IOUtil.close(reader);
480         }
481 
482         return null;
483     }
484 
485     /**
486      * Convert an AntTaskPojo into XML
487      *
488      * http://ant.apache.org/manual/Tasks/ant.html
489      */
490     protected String getXML(AntTaskPojo atp) {
491         StringBuilder sb = new StringBuilder();
492         sb.append("<ant");
493         sb.append(attr("dir", atp.getDir()));
494         sb.append(attr("antfile", atp.getAntfile()));
495         sb.append(attr("target", atp.getTarget()));
496         sb.append(attr("output", atp.getOutput()));
497         // Only include if different from the default
498         if (!atp.isInheritAll()) {
499             sb.append(attr("inheritAll", atp.isInheritAll() + ""));
500         }
501         // Only include if different from the default
502         if (atp.isInheritRefs()) {
503             sb.append(attr("inheritRefs", atp.isInheritRefs() + ""));
504         }
505         // Only include if different from the default
506         if (atp.isUseNativeBasedir()) {
507             sb.append(attr("useNativeBaseDir", atp.isUseNativeBasedir() + ""));
508         }
509         sb.append(" />");
510         return sb.toString();
511     }
512 
513     /**
514      * Return XML for an attribute
515      */
516     protected String attr(String name, String value) {
517         if (StringUtils.isEmpty(value)) {
518             return "";
519         } else {
520             return " " + name + "=\"" + value + "\"";
521         }
522     }
523 
524     public String getPropertyPrefix() {
525         return propertyPrefix;
526     }
527 
528     public void setPropertyPrefix(String propertyPrefix) {
529         this.propertyPrefix = propertyPrefix;
530     }
531 
532     public String getCustomTaskPrefix() {
533         return customTaskPrefix;
534     }
535 
536     public void setCustomTaskPrefix(String customTaskPrefix) {
537         this.customTaskPrefix = customTaskPrefix;
538     }
539 
540     public boolean isExportAntProperties() {
541         return exportAntProperties;
542     }
543 
544     public void setExportAntProperties(boolean exportAntProperties) {
545         this.exportAntProperties = exportAntProperties;
546     }
547 
548     public boolean isFailOnError() {
549         return failOnError;
550     }
551 
552     public void setFailOnError(boolean failOnError) {
553         this.failOnError = failOnError;
554     }
555 
556     public String getFile() {
557         return file;
558     }
559 
560     public void setFile(String file) {
561         this.file = file;
562     }
563 
564     public String getTarget() {
565         return target;
566     }
567 
568     public void setTarget(String target) {
569         this.target = target;
570     }
571 
572     public String getOutput() {
573         return output;
574     }
575 
576     public void setOutput(String output) {
577         this.output = output;
578     }
579 
580     public String getInheritAll() {
581         return inheritAll;
582     }
583 
584     public void setInheritAll(String inheritAll) {
585         this.inheritAll = inheritAll;
586     }
587 
588     public String getInheritRefs() {
589         return inheritRefs;
590     }
591 
592     public void setInheritRefs(String inheritRefs) {
593         this.inheritRefs = inheritRefs;
594     }
595 
596     public MavenProject getProject() {
597         return project;
598     }
599 
600     public MavenProjectHelper getProjectHelper() {
601         return projectHelper;
602     }
603 
604     public List<Artifact> getPluginArtifacts() {
605         return pluginArtifacts;
606     }
607 
608     public ArtifactRepository getLocalRepository() {
609         return localRepository;
610     }
611 
612     public String getAntFilename() {
613         return antFilename;
614     }
615 
616     public String getRelativeLocalFilename() {
617         return relativeLocalFilename;
618     }
619 
620     public void setSkip(boolean skip) {
621         this.skip = skip;
622     }
623 
624 }