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