1   package com.atlassian.maven.plugins.amps;
2   
3   import java.io.File;
4   import java.io.FileOutputStream;
5   import java.io.IOException;
6   import java.net.ServerSocket;
7   import java.net.URL;
8   import java.net.URLClassLoader;
9   import java.util.*;
10  import java.util.jar.Manifest;
11  import java.util.regex.Matcher;
12  
13  import com.atlassian.core.util.FileUtils;
14  import com.atlassian.maven.plugins.amps.util.PluginXmlUtils;
15  import com.atlassian.maven.plugins.amps.util.VersionUtils;
16  
17  import com.sun.jersey.wadl.resourcedoc.ResourceDocletJSON;
18  
19  import org.apache.commons.io.IOUtils;
20  import org.apache.commons.lang.StringUtils;
21  import org.apache.maven.artifact.DependencyResolutionRequiredException;
22  import org.apache.maven.model.Plugin;
23  import org.apache.maven.plugin.MojoExecutionException;
24  import org.apache.maven.plugin.logging.Log;
25  import org.apache.maven.project.MavenProject;
26  import org.codehaus.plexus.util.xml.Xpp3Dom;
27  import org.twdata.maven.mojoexecutor.MojoExecutor.Element;
28  import org.twdata.maven.mojoexecutor.MojoExecutor.ExecutionEnvironment;
29  
30  import static com.atlassian.maven.plugins.amps.util.FileUtils.fixWindowsSlashes;
31  import static com.atlassian.maven.plugins.amps.util.FileUtils.file;
32  import static org.twdata.maven.mojoexecutor.MojoExecutor.artifactId;
33  import static org.twdata.maven.mojoexecutor.MojoExecutor.configuration;
34  import static org.twdata.maven.mojoexecutor.MojoExecutor.element;
35  import static org.twdata.maven.mojoexecutor.MojoExecutor.executeMojo;
36  import static org.twdata.maven.mojoexecutor.MojoExecutor.goal;
37  import static org.twdata.maven.mojoexecutor.MojoExecutor.groupId;
38  import static org.twdata.maven.mojoexecutor.MojoExecutor.name;
39  import static org.twdata.maven.mojoexecutor.MojoExecutor.plugin;
40  import static org.twdata.maven.mojoexecutor.MojoExecutor.version;
41  
42  /**
43   * Executes specific maven goals
44   */
45  public class MavenGoals
46  {
47      private final MavenContext ctx;
48  
49      private final Log log;
50      private final Map<String, String> pluginArtifactIdToVersionMap;
51  
52      private final Map<String, Container> idToContainerMap = new HashMap<String, Container>()
53      {{
54              put("tomcat5x", new Container("tomcat5x", "org.apache.tomcat", "apache-tomcat", "5.5.26"));
55              put("tomcat6x", new Container("tomcat6x", "org.apache.tomcat", "apache-tomcat", "6.0.20"));
56              put("resin3x", new Container("resin3x", "com.caucho", "resin", "3.0.26"));
57              put("jboss42x", new Container("jboss42x", "org.jboss.jbossas", "jbossas", "4.2.3.GA"));
58              put("jetty6x", new Container("jetty6x"));
59          }};
60  
61      private final Map<String, String> defaultArtifactIdToVersionMap = new HashMap<String, String>()
62      {{
63              put("maven-cli-plugin", "0.7");
64              put("cargo-maven2-plugin", "1.0-beta-2-db2");
65              // Below is a second definition of 'cargo-maven2-plugin', using CodeHaus instead of TwData.
66              put("org.codehaus.cargo:cargo-maven2-plugin", "1.1.3");
67              put("atlassian-pdk", "2.3.1");
68              put("maven-archetype-plugin", "2.0-alpha-4");
69              put("maven-bundle-plugin", "2.0.0");
70              put("yuicompressor-maven-plugin", "0.7.1");
71              put("build-helper-maven-plugin", "1.7");
72  
73              // You can't actually override the version a plugin if defined in the project, so these don't actually do
74              // anything, since the super pom already defines versions.
75              put("maven-dependency-plugin", "2.0");
76              put("maven-resources-plugin", "2.3");
77              put("maven-jar-plugin", "2.2");
78              put("maven-surefire-plugin", "2.4.3");
79  
80          }};
81  
82      public MavenGoals(final MavenContext ctx)
83      {
84          this.ctx = ctx;
85  
86          this.log = ctx.getLog();
87  
88          this.pluginArtifactIdToVersionMap = Collections.unmodifiableMap(defaultArtifactIdToVersionMap);
89      }
90  
91      private ExecutionEnvironment executionEnvironment()
92      {
93          return ctx.getExecutionEnvironment();
94      }
95  
96      public MavenProject getContextProject()
97      {
98          return ctx.getProject();
99      }
100 
101     public void executeAmpsRecursively(final String ampsVersion, final String ampsGoal, Xpp3Dom cfg) throws MojoExecutionException
102     {
103         executeMojo(
104             plugin(
105                 groupId("com.atlassian.maven.plugins"),
106                 artifactId("maven-amps-plugin"),
107                 version(ampsVersion)
108             ),
109             goal(ampsGoal),
110             cfg,
111             executionEnvironment());
112     }
113 
114     public void startCli(final PluginInformation pluginInformation, final int port) throws MojoExecutionException
115     {
116         final String pluginId = pluginInformation.getId();
117 
118         final List<Element> configs = new ArrayList<Element>();
119         configs.add(element(name("commands"),
120                 element(name("pi"),
121                         "resources" + " "
122                         + "com.atlassian.maven.plugins:maven-" + pluginId + "-plugin:filter-plugin-descriptor" + " "
123                         + "compile" + " "
124                         + "com.atlassian.maven.plugins:maven-" + pluginId + "-plugin:copy-bundled-dependencies" + " "
125                         + "com.atlassian.maven.plugins:maven-" + pluginId + "-plugin:compress-resources" + " "
126                         + "com.atlassian.maven.plugins:maven-" + pluginId + "-plugin:generate-manifest" + " "
127                         + "com.atlassian.maven.plugins:maven-" + pluginId + "-plugin:validate-manifest" + " "
128                         + "com.atlassian.maven.plugins:maven-" + pluginId + "-plugin:jar" + " "
129                         + "com.atlassian.maven.plugins:maven-" + pluginId + "-plugin:install"),
130                 element(name("tpi"),
131                         "testResources" + " "
132                         + "testCompile" + " "
133                         + "com.atlassian.maven.plugins:maven-" + pluginId + "-plugin:test-jar" + " "
134                         + "com.atlassian.maven.plugins:maven-" + pluginId + "-plugin:test-install"),
135                 element(name("package"),
136                         "resources" + " "
137                         + "com.atlassian.maven.plugins:maven-" + pluginId + "-plugin:filter-plugin-descriptor" + " "
138                         + "compile" + " "
139                         + "com.atlassian.maven.plugins:maven-" + pluginId + "-plugin:copy-bundled-dependencies" + " "
140                         + "com.atlassian.maven.plugins:maven-" + pluginId + "-plugin:generate-manifest" + " "
141                         + "com.atlassian.maven.plugins:maven-" + pluginId + "-plugin:jar" + " ")));
142         if (port > 0)
143         {
144             configs.add(element(name("port"), String.valueOf(port)));
145         }
146         executeMojo(
147                 plugin(
148                         groupId("org.twdata.maven"),
149                         artifactId("maven-cli-plugin"),
150                         version(pluginArtifactIdToVersionMap.get("maven-cli-plugin"))
151                 ),
152                 goal("execute"),
153                 configuration(configs.toArray(new Element[0])),
154                 executionEnvironment());
155     }
156 
157     public void createPlugin(final String productId) throws MojoExecutionException
158     {
159         executeMojo(
160                 plugin(
161                         groupId("org.apache.maven.plugins"),
162                         artifactId("maven-archetype-plugin"),
163                         version(defaultArtifactIdToVersionMap.get("maven-archetype-plugin"))
164                 ),
165                 goal("generate"),
166                 configuration(
167                         element(name("archetypeGroupId"), "com.atlassian.maven.archetypes"),
168                         element(name("archetypeArtifactId"), (productId.equals("all") ? "" : productId + "-" ) + "plugin-archetype"),
169                         element(name("archetypeVersion"), VersionUtils.getVersion())
170                 ),
171                 executionEnvironment());
172     }
173 
174     public void copyBundledDependencies() throws MojoExecutionException
175     {
176         executeMojo(
177                 plugin(
178                         groupId("org.apache.maven.plugins"),
179                         artifactId("maven-dependency-plugin"),
180                         version(defaultArtifactIdToVersionMap.get("maven-dependency-plugin"))
181                 ),
182                 goal("copy-dependencies"),
183                 configuration(
184                         element(name("includeScope"), "runtime"),
185                         element(name("excludeScope"), "provided"),
186                         element(name("excludeScope"), "test"),
187                         element(name("includeTypes"), "jar"),
188                         element(name("outputDirectory"), "${project.build.outputDirectory}/META-INF/lib")
189                 ),
190                 executionEnvironment()
191         );
192     }
193 
194     public void extractBundledDependencies() throws MojoExecutionException
195     {
196          executeMojo(
197                  plugin(
198                         groupId("org.apache.maven.plugins"),
199                         artifactId("maven-dependency-plugin"),
200                         version(defaultArtifactIdToVersionMap.get("maven-dependency-plugin"))
201                 ),
202                 goal("unpack-dependencies"),
203                 configuration(
204                         element(name("includeScope"), "runtime"),
205                         element(name("excludeScope"), "provided"),
206                         element(name("excludeScope"), "test"),
207                         element(name("includeTypes"), "jar"),
208                         element(name("excludes"), "META-INF/MANIFEST.MF, META-INF/*.DSA, META-INF/*.SF"),
209                         element(name("outputDirectory"), "${project.build.outputDirectory}")
210                 ),
211                 executionEnvironment()
212         );
213     }
214 
215     public void compressResources() throws MojoExecutionException
216     {
217         executeMojo(
218                 plugin(
219                         groupId("net.sf.alchim"),
220                         artifactId("yuicompressor-maven-plugin"),
221                         version(defaultArtifactIdToVersionMap.get("yuicompressor-maven-plugin"))
222                 ),
223                 goal("compress"),
224                 configuration(
225                         element(name("suffix"), "-min"),
226                         element(name("jswarn"), "false")
227                 ),
228                 executionEnvironment()
229         );
230     }
231 
232     public void filterPluginDescriptor() throws MojoExecutionException
233     {
234         executeMojo(
235                 plugin(
236                         groupId("org.apache.maven.plugins"),
237                         artifactId("maven-resources-plugin"),
238                         version(defaultArtifactIdToVersionMap.get("maven-resources-plugin"))
239                 ),
240                 goal("copy-resources"),
241                 configuration(
242                         element(name("encoding"), "UTF-8"),
243                         element(name("resources"),
244                                 element(name("resource"),
245                                         element(name("directory"), "src/main/resources"),
246                                         element(name("filtering"), "true"),
247                                         element(name("includes"),
248                                                 element(name("include"), "atlassian-plugin.xml"))
249                                 )
250                         ),
251                         element(name("outputDirectory"), "${project.build.outputDirectory}")
252                 ),
253                 executionEnvironment()
254         );
255     }
256 
257     public void runUnitTests(Map<String, Object> systemProperties) throws MojoExecutionException
258     {
259         final Element systemProps = convertPropsToElements(systemProperties);
260 
261         executeMojo(
262                 plugin(
263                         groupId("org.apache.maven.plugins"),
264                         artifactId("maven-surefire-plugin"),
265                         version(defaultArtifactIdToVersionMap.get("maven-surefire-plugin"))
266                 ),
267                 goal("test"),
268                 configuration(
269                         systemProps,
270                         element(name("excludes"),
271                                 element(name("exclude"), "it/**"),
272                                 element(name("exclude"), "**/*$*"))
273                 ),
274                 executionEnvironment()
275         );
276     }
277 
278     public File copyWebappWar(final String productId, final File targetDirectory, final ProductArtifact artifact)
279             throws MojoExecutionException
280     {
281         final File webappWarFile = new File(targetDirectory, productId + "-original.war");
282         executeMojo(
283                 plugin(
284                         groupId("org.apache.maven.plugins"),
285                         artifactId("maven-dependency-plugin"),
286                         version(defaultArtifactIdToVersionMap.get("maven-dependency-plugin"))
287                 ),
288                 goal("copy"),
289                 configuration(
290                         element(name("artifactItems"),
291                                 element(name("artifactItem"),
292                                         element(name("groupId"), artifact.getGroupId()),
293                                         element(name("artifactId"), artifact.getArtifactId()),
294                                         element(name("type"), "war"),
295                                         element(name("version"), artifact.getVersion()),
296                                         element(name("destFileName"), webappWarFile.getName()))),
297                         element(name("outputDirectory"), targetDirectory.getPath())
298                 ),
299                 executionEnvironment()
300         );
301         return webappWarFile;
302     }
303 
304     public File copyArtifact(final String targetFileName, final File targetDirectory,
305                              final ProductArtifact artifact, String type) throws MojoExecutionException
306     {
307         final File targetFile = new File(targetDirectory, targetFileName);
308         executeMojo(
309                 plugin(
310                         groupId("org.apache.maven.plugins"),
311                         artifactId("maven-dependency-plugin"),
312                         version(defaultArtifactIdToVersionMap.get("maven-dependency-plugin"))
313                 ),
314                 goal("copy"),
315                 configuration(
316                         element(name("artifactItems"),
317                                 element(name("artifactItem"),
318                                         element(name("groupId"), artifact.getGroupId()),
319                                         element(name("artifactId"), artifact.getArtifactId()),
320                                         element(name("type"), type),
321                                         element(name("version"), artifact.getVersion()),
322                                         element(name("destFileName"), targetFile.getName()))),
323                         element(name("outputDirectory"), targetDirectory.getPath())
324                 ),
325                 executionEnvironment()
326         );
327         return targetFile;
328     }
329 
330     /**
331      * Copies {@code artifacts} to the {@code outputDirectory}. Artifacts are looked up in order: <ol> <li>in the maven
332      * reactor</li> <li>in the maven repositories</li> </ol> This can't be used in a goal that happens before the
333      * <em>package</em> phase as artifacts in the reactor will be not be packaged (and therefore 'copiable') until this
334      * phase.
335      *
336      * @param outputDirectory the directory to copy artifacts to
337      * @param artifacts       the list of artifact to copy to the given directory
338      */
339     public void copyPlugins(final File outputDirectory, final List<ProductArtifact> artifacts)
340             throws MojoExecutionException
341     {
342         for (ProductArtifact artifact : artifacts)
343         {
344             final MavenProject artifactReactorProject = getReactorProjectForArtifact(artifact);
345             if (artifactReactorProject != null)
346             {
347 
348                 log.debug(artifact + " will be copied from reactor project " + artifactReactorProject);
349                 final File artifactFile = artifactReactorProject.getArtifact().getFile();
350                 if (artifactFile == null)
351                 {
352                     log.warn("The plugin " + artifact + " is in the reactor but not the file hasn't been attached.  Skipping.");
353                 }
354                 else
355                 {
356                     log.debug("Copying " + artifactFile + " to " + outputDirectory);
357                     try
358                     {
359                         FileUtils.copyFile(artifactFile, new File(outputDirectory, artifactFile.getName()));
360                     }
361                     catch (IOException e)
362                     {
363                         throw new MojoExecutionException("Could not copy " + artifact + " to " + outputDirectory, e);
364                     }
365                 }
366 
367             }
368             else
369             {
370                 executeMojo(
371                         plugin(
372                                 groupId("org.apache.maven.plugins"),
373                                 artifactId("maven-dependency-plugin"),
374                                 version(defaultArtifactIdToVersionMap.get("maven-dependency-plugin"))
375                         ),
376                         goal("copy"),
377                         configuration(
378                                 element(name("artifactItems"),
379                                         element(name("artifactItem"),
380                                                 element(name("groupId"), artifact.getGroupId()),
381                                                 element(name("artifactId"), artifact.getArtifactId()),
382                                                 element(name("version"), artifact.getVersion()))),
383                                 element(name("outputDirectory"), outputDirectory.getPath())
384                         ),
385                         executionEnvironment());
386             }
387         }
388     }
389 
390     private MavenProject getReactorProjectForArtifact(ProductArtifact artifact)
391     {
392         for (final MavenProject project : ctx.getReactor())
393         {
394             if (project.getGroupId().equals(artifact.getGroupId())
395                     && project.getArtifactId().equals(artifact.getArtifactId())
396                     && project.getVersion().equals(artifact.getVersion()))
397             {
398                 return project;
399             }
400         }
401         return null;
402     }
403 
404     private void unpackContainer(final Container container) throws MojoExecutionException
405     {
406         executeMojo(
407                 plugin(
408                         groupId("org.apache.maven.plugins"),
409                         artifactId("maven-dependency-plugin"),
410                         version(defaultArtifactIdToVersionMap.get("maven-dependency-plugin"))
411                 ),
412                 goal("unpack"),
413                 configuration(
414                         element(name("artifactItems"),
415                                 element(name("artifactItem"),
416                                         element(name("groupId"), container.getGroupId()),
417                                         element(name("artifactId"), container.getArtifactId()),
418                                         element(name("version"), container.getVersion()),
419                                         element(name("type"), "zip"))),
420                         element(name("outputDirectory"), container.getRootDirectory(getBuildDirectory()))
421                 ),
422                 executionEnvironment());
423     }
424 
425     private String getBuildDirectory()
426     {
427         return ctx.getProject().getBuild().getDirectory();
428     }
429 
430     public int startWebapp(final String productInstanceId, final File war, final Map<String, String> systemProperties, final List<ProductArtifact> extraContainerDependencies,
431                            final Product webappContext) throws MojoExecutionException
432     {
433         final Container container = findContainer(webappContext.getContainerId());
434         File containerDir = new File(container.getRootDirectory(getBuildDirectory()));
435 
436         // retrieve non-embedded containers
437         if (!container.isEmbedded())
438         {
439             if (containerDir.exists())
440             {
441                 log.info("Reusing unpacked container '" + container.getId() + "' from " + containerDir.getPath());
442             }
443             else
444             {
445                 log.info("Unpacking container '" + container.getId() + "' from container artifact: " + container.toString());
446                 unpackContainer(container);
447             }
448         }
449 
450         final int rmiPort = pickFreePort(0);
451         final int actualHttpPort = pickFreePort(webappContext.getHttpPort());
452         final List<Element> sysProps = new ArrayList<Element>();
453 
454         for (final Map.Entry<String, String> entry : systemProperties.entrySet())
455         {
456             sysProps.add(element(name(entry.getKey()), entry.getValue()));
457         }
458         log.info("Starting " + productInstanceId + " on the " + container.getId() + " container on ports "
459                 + actualHttpPort + " (http) and " + rmiPort + " (rmi)");
460 
461         final String baseUrl = getBaseUrl(webappContext.getServer(), actualHttpPort, webappContext.getContextPath());
462         sysProps.add(element(name("baseurl"), baseUrl));
463 
464         final List<Element> deps = new ArrayList<Element>();
465         for (final ProductArtifact dep : extraContainerDependencies)
466         {
467             deps.add(element(name("dependency"),
468                     element(name("location"), webappContext.getArtifactRetriever().resolve(dep))
469             ));
470         }
471 
472         final List<Element> props = new ArrayList<Element>();
473         for (final Map.Entry<String, String> entry : systemProperties.entrySet())
474         {
475             props.add(element(name(entry.getKey()), entry.getValue()));
476         }
477         props.add(element(name("cargo.servlet.port"), String.valueOf(actualHttpPort)));
478         props.add(element(name("cargo.rmi.port"), String.valueOf(rmiPort)));
479         props.add(element(name("cargo.jvmargs"), webappContext.getJvmArgs()));
480 
481         int startupTimeout = webappContext.getStartupTimeout();
482         if (Boolean.FALSE.equals(webappContext.getSynchronousStartup()))
483         {
484             startupTimeout = 0;
485         }
486 
487         executeMojo(
488                 cargo(webappContext),
489                 goal("start"),
490                 configuration(
491                         element(name("wait"), "false"),
492                         element(name("container"),
493                                 element(name("containerId"), container.getId()),
494                                 element(name("type"), container.getType()),
495                                 element(name("home"), container.getInstallDirectory(getBuildDirectory())),
496                                 element(name("output"), webappContext.getOutput()),
497                                 element(name("systemProperties"), sysProps.toArray(new Element[sysProps.size()])),
498                                 element(name("dependencies"), deps.toArray(new Element[deps.size()])),
499                                 element(name("timeout"), String.valueOf(startupTimeout))
500                         ),
501                         element(name("configuration"),
502                                 element(name("home"), container.getConfigDirectory(getBuildDirectory(), productInstanceId)),
503                                 element(name("type"), "standalone"),
504                                 element(name("properties"), props.toArray(new Element[props.size()])),
505                                 element(name("deployables"),
506                                         element(name("deployable"),
507                                                 element(name("groupId"), "foo"),
508                                                 element(name("artifactId"), "bar"),
509                                                 element(name("type"), "war"),
510                                                 element(name("location"), war.getPath()),
511                                                 element(name("properties"),
512                                                         element(name("context"), webappContext.getContextPath())
513                                                 )
514                                         )
515                                 )
516                         )
517                 ),
518                 executionEnvironment()
519         );
520         return actualHttpPort;
521     }
522     
523     public void stopWebapp(final String productId, final String containerId, final Product webappContext) throws MojoExecutionException
524     {
525         final Container container = findContainer(containerId);
526 
527         String actualShutdownTimeout = webappContext.getSynchronousStartup() ? "0" : String.valueOf(webappContext.getShutdownTimeout());
528         
529         executeMojo(
530         cargo(webappContext),
531         goal("stop"),
532         configuration(
533              element(name("container"),
534                      element(name("containerId"), container.getId()),
535                      element(name("type"), container.getType()),
536                      element(name("timeout"), actualShutdownTimeout),
537                      // org.codehaus.cargo
538                      element(name("home"), container.getInstallDirectory(getBuildDirectory()))
539              ),
540              element(name("configuration"),
541                      // org.twdata.maven
542                      element(name("home"), container.getConfigDirectory(getBuildDirectory(), productId))/*,
543                      // we don't need that atm. since timeout is 0 for org.codehaus.cargo
544                      element(name("properties"), createShutdownPortsPropertiesConfiguration(webappContext)) */
545              )
546         ),
547         executionEnvironment()
548         );
549     }
550     
551     /**
552      * Cargo waits (org.codehaus.cargo.container.tomcat.internal.AbstractCatalinaInstalledLocalContainer#waitForCompletion(boolean waitForStarting)) for 3 ports, but the AJP and RMI ports may 
553      * not be correct (see below), so we configure it to wait on the HTTP port only.
554      * 
555      * Since we're not configuring the AJP port it defaults to 8009. All the Studio applications are currently using 8009 (by default, since not configured in startWebapp)
556      * which means that this port might have been taken by a different application (the container will still come up though, see 
557      * "INFO: Port busy 8009 java.net.BindException: Address already in use" in the log). Thus we don't want to wait for it because it might be still open also the container 
558      * is shut down. 
559      * 
560      * The RMI port is randomly chosen (see startWebapp), thus we don't have any information close at hand. As a future optimisation, e.g. when we move away from cargo to let's say
561      * Apache's Tomcat Maven Plugin we could retrieve the actualy configuration from the server.xml on shutdown and thus know exactly for what which port to wait until it gets closed.
562      * We could do that already in cargo (e.g. container/tomcat6x/<productHome>/conf/server.xml) but that means that we have to support all the containers we are supporting with cargo.
563      * 
564      * Since the HTTP port is the only one that interests us, we set all three ports to this one when calling stop. But since that may be randomly chosen as well we might be waiting
565      * for the wrong port to get closed. Since this is the minor use case, one has to either accept the timeout if the default port is open, or configure product.stop.timeout to 0 in
566      * order to skip the wait.
567      */
568     private Element[] createShutdownPortsPropertiesConfiguration(final Product webappContext)
569     {
570         final List<Element> properties = new ArrayList<Element>();
571         String portUsedToDetermineIfShutdownSucceeded = String.valueOf(webappContext.getHttpPort());
572         properties.add(element(name("cargo.servlet.port"), portUsedToDetermineIfShutdownSucceeded));
573         properties.add(element(name("cargo.rmi.port"), portUsedToDetermineIfShutdownSucceeded));
574         properties.add(element(name("cargo.tomcat.ajp.port"), portUsedToDetermineIfShutdownSucceeded));
575         return properties.toArray(new Element[properties.size()]);
576     }
577 
578     /**
579      * Decides whether to use the org.twdata.maven.cargo-maven2-plugin or the org.codehaus.cargo.cargo-maven2-plugin.
580      * <p/>
581      * The org.twdata.maven plugin is a fork of the org.codehaus.cargo plugin that has been used in AMPS so far. The
582      * org.codehaus.cargo plugin in the more recent version has the advantage of setting the timeout to 0. This skips
583      * waiting for start/stop of the container in order to perform these operations in parallel.
584      * 
585      * @param if {@link Product#getSynchronousStartup()} is true, org.twdata.maven.cargo-maven2-plugin is chosen, if it
586      *        is false, org.codehaus.cargo.cargo-maven2-plugin is chosen
587      */
588     private Plugin cargo(Product webappContext)
589     {
590         if (Boolean.TRUE.equals(webappContext.getSynchronousStartup()))
591         {
592             return plugin(
593                 groupId("org.twdata.maven"),
594                 artifactId("cargo-maven2-plugin"),
595                 version(pluginArtifactIdToVersionMap.get("cargo-maven2-plugin")));
596         }
597         else
598         {
599             return plugin(
600                 groupId("org.codehaus.cargo"),
601                 artifactId("cargo-maven2-plugin"),
602                 version(pluginArtifactIdToVersionMap.get("org.codehaus.cargo:cargo-maven2-plugin")));
603         }
604     }
605 
606     public static String getBaseUrl(final String server, final int actualHttpPort, final String contextPath)
607     {
608         String port = actualHttpPort != 80 ? ":" + actualHttpPort : "";
609         if (server.startsWith("http")) {
610             return server + port + contextPath;
611         } else {
612             return "http://" + server + port + contextPath;
613         }
614     }
615 
616     public void runTests(String testGroupId, String containerId, List<String> includes, List<String> excludes, Map<String, Object> systemProperties, final File targetDirectory)
617     		throws MojoExecutionException
618 	{
619     	List<Element> includeElements = new ArrayList<Element>(includes.size());
620     	for (String include : includes)
621     	{
622     		includeElements.add(element(name("include"), include));
623     	}
624 
625         List<Element> excludeElements = new ArrayList<Element>(excludes.size() + 2);
626         excludeElements.add(element(name("exclude"), "**/*$*"));
627         excludeElements.add(element(name("exclude"), "**/Abstract*"));
628         for (String exclude : excludes)
629         {
630         	excludeElements.add(element(name("exclude"), exclude));
631         }
632 
633         final String testOutputDir = targetDirectory.getAbsolutePath() + "/" + testGroupId + "/" + containerId + "/surefire-reports";
634         final String reportsDirectory = "reportsDirectory";
635         systemProperties.put(reportsDirectory, testOutputDir);
636 
637         final Element systemProps = convertPropsToElements(systemProperties);
638 
639 
640         executeMojo(
641                 plugin(
642                         groupId("org.apache.maven.plugins"),
643                         artifactId("maven-surefire-plugin"),
644                         version(defaultArtifactIdToVersionMap.get("maven-surefire-plugin"))
645                 ),
646                 goal("test"),
647                 configuration(
648                         element(name("includes"),
649                         		includeElements.toArray(new Element[includeElements.size()])
650                         ),
651                         element(name("excludes"),
652                                 excludeElements.toArray(new Element[excludeElements.size()])
653                         ),
654                         systemProps,
655                         element(name(reportsDirectory), testOutputDir)
656                 ),
657                 executionEnvironment()
658         );
659 	}
660 
661     /**
662      * Converts a map of System properties to maven config elements
663      */
664     private Element convertPropsToElements(Map<String, Object> systemProperties)
665     {
666         ArrayList<Element> properties = new ArrayList<Element>();
667 
668         // add extra system properties... overwriting any of the hard coded values above.
669         for (Map.Entry<String, Object> entry: systemProperties.entrySet())
670         {
671             properties.add(
672                     element(name("property"),
673                             element(name("name"), entry.getKey()),
674                             element(name("value"), entry.getValue().toString())));
675         }
676 
677         return element(name("systemProperties"), properties.toArray(new Element[properties.size()]));
678     }
679 
680     private Container findContainer(final String containerId)
681     {
682         final Container container = idToContainerMap.get(containerId);
683         if (container == null)
684         {
685             throw new IllegalArgumentException("Container " + containerId + " not supported");
686         }
687         return container;
688     }
689 
690     int pickFreePort(final int requestedPort)
691     {
692         ServerSocket socket = null;
693         try
694         {
695             socket = new ServerSocket(requestedPort);
696             return requestedPort > 0 ? requestedPort : socket.getLocalPort();
697         }
698         catch (final IOException e)
699         {
700             // happens if the requested port is taken, so we need to pick a new one
701             ServerSocket zeroSocket = null;
702             try
703             {
704                 zeroSocket = new ServerSocket(0);
705                 return zeroSocket.getLocalPort();
706             }
707             catch (final IOException ex)
708             {
709                 throw new RuntimeException("Error opening socket", ex);
710             }
711             finally
712             {
713                 closeSocket(zeroSocket);
714             }
715         }
716         finally
717         {
718             closeSocket(socket);
719         }
720     }
721 
722     private void closeSocket(ServerSocket socket)
723     {
724         if (socket != null)
725         {
726             try
727             {
728                 socket.close();
729             }
730             catch (final IOException e)
731             {
732                 throw new RuntimeException("Error closing socket", e);
733             }
734         }
735     }
736 
737     public void installPlugin(PdkParams pdkParams)
738             throws MojoExecutionException
739     {
740         final String baseUrl = getBaseUrl(pdkParams.getServer(), pdkParams.getPort(), pdkParams.getContextPath());
741         executeMojo(
742                 plugin(
743                         groupId("com.atlassian.maven.plugins"),
744                         artifactId("atlassian-pdk"),
745                         version(pluginArtifactIdToVersionMap.get("atlassian-pdk"))
746                 ),
747                 goal("install"),
748                 configuration(
749                         element(name("pluginFile"), pdkParams.getPluginFile()),
750                         element(name("username"), pdkParams.getUsername()),
751                         element(name("password"), pdkParams.getPassword()),
752                         element(name("serverUrl"), baseUrl),
753                         element(name("pluginKey"), pdkParams.getPluginKey())
754                 ),
755                 executionEnvironment()
756         );
757     }
758 
759     public void uninstallPlugin(final String pluginKey, final String server, final int port, final String contextPath)
760             throws MojoExecutionException
761     {
762         final String baseUrl = getBaseUrl(server, port, contextPath);
763         executeMojo(
764                 plugin(
765                         groupId("com.atlassian.maven.plugins"),
766                         artifactId("atlassian-pdk"),
767                         version(pluginArtifactIdToVersionMap.get("atlassian-pdk"))
768                 ),
769                 goal("uninstall"),
770                 configuration(
771                         element(name("username"), "admin"),
772                         element(name("password"), "admin"),
773                         element(name("serverUrl"), baseUrl),
774                         element(name("pluginKey"), pluginKey)
775                 ),
776                 executionEnvironment()
777         );
778     }
779 
780     public void installIdeaPlugin() throws MojoExecutionException
781     {
782         executeMojo(
783                 plugin(
784                         groupId("org.twdata.maven"),
785                         artifactId("maven-cli-plugin"),
786                         version(pluginArtifactIdToVersionMap.get("maven-cli-plugin"))
787                 ),
788                 goal("idea"),
789                 configuration(),
790                 executionEnvironment()
791         );
792     }
793 
794     public File copyDist(final File targetDirectory, final ProductArtifact artifact) throws MojoExecutionException
795     {
796         return copyZip(targetDirectory, artifact, "test-dist.zip");
797     }
798 
799     public File copyHome(final File targetDirectory, final ProductArtifact artifact) throws MojoExecutionException
800     {
801         return copyZip(targetDirectory, artifact, artifact.getArtifactId() + ".zip");
802     }
803 
804     public File copyZip(final File targetDirectory, final ProductArtifact artifact, final String localName) throws MojoExecutionException
805     {
806         final File artifactZip = new File(targetDirectory, localName);
807         executeMojo(
808                 plugin(
809                         groupId("org.apache.maven.plugins"),
810                         artifactId("maven-dependency-plugin"),
811                         version(defaultArtifactIdToVersionMap.get("maven-dependency-plugin"))
812                 ),
813                 goal("copy"),
814                 configuration(
815                         element(name("artifactItems"),
816                                 element(name("artifactItem"),
817                                         element(name("groupId"), artifact.getGroupId()),
818                                         element(name("artifactId"), artifact.getArtifactId()),
819                                         element(name("type"), "zip"),
820                                         element(name("version"), artifact.getVersion()),
821                                         element(name("destFileName"), artifactZip.getName()))),
822                         element(name("outputDirectory"), artifactZip.getParent())
823                 ),
824                 executionEnvironment()
825         );
826         return artifactZip;
827     }
828 
829     public void generateBundleManifest(final Map<String, String> instructions, final Map<String, String> basicAttributes) throws MojoExecutionException
830     {
831         final List<Element> instlist = new ArrayList<Element>();
832         for (final Map.Entry<String, String> entry : instructions.entrySet())
833         {
834             instlist.add(element(entry.getKey(), entry.getValue()));
835         }
836         for (final Map.Entry<String, String> entry : basicAttributes.entrySet())
837         {
838             instlist.add(element(entry.getKey(), entry.getValue()));
839         }
840         executeMojo(
841                 plugin(
842                         groupId("org.apache.felix"),
843                         artifactId("maven-bundle-plugin"),
844                         version(defaultArtifactIdToVersionMap.get("maven-bundle-plugin"))
845                 ),
846                 goal("manifest"),
847                 configuration(
848                         element(name("supportedProjectTypes"),
849                                 element(name("supportedProjectType"), "jar"),
850                                 element(name("supportedProjectType"), "bundle"),
851                                 element(name("supportedProjectType"), "war"),
852                                 element(name("supportedProjectType"), "atlassian-plugin")),
853                         element(name("instructions"), instlist.toArray(new Element[instlist.size()]))
854                 ),
855                 executionEnvironment()
856         );
857     }
858 
859     public void generateMinimalManifest(final Map<String, String> basicAttributes) throws MojoExecutionException
860     {
861         File metaInf = file(ctx.getProject().getBuild().getOutputDirectory(), "META-INF");
862         if (!metaInf.exists())
863         {
864             metaInf.mkdirs();
865         }
866         File mf = file(ctx.getProject().getBuild().getOutputDirectory(), "META-INF", "MANIFEST.MF");
867         Manifest m = new Manifest();
868         m.getMainAttributes().putValue("Manifest-Version", "1.0");
869         for (Map.Entry<String, String> entry : basicAttributes.entrySet())
870         {
871             m.getMainAttributes().putValue(entry.getKey(), entry.getValue());
872         }
873         FileOutputStream fos = null;
874         try
875         {
876             fos = new FileOutputStream(mf);
877             m.write(fos);
878         }
879         catch (IOException e)
880         {
881             throw new MojoExecutionException("Unable to create manifest", e);
882         }
883         finally
884         {
885             IOUtils.closeQuietly(fos);
886         }
887     }
888 
889     public void jarWithOptionalManifest(final boolean manifestExists) throws MojoExecutionException
890     {
891         Element[] archive = new Element[0];
892         if (manifestExists)
893         {
894             archive = new Element[]{element(name("manifestFile"), "${project.build.outputDirectory}/META-INF/MANIFEST.MF")};
895         }
896 
897         executeMojo(
898                 plugin(
899                         groupId("org.apache.maven.plugins"),
900                         artifactId("maven-jar-plugin"),
901                         version(defaultArtifactIdToVersionMap.get("maven-jar-plugin"))
902                 ),
903                 goal("jar"),
904                 configuration(
905                         element(name("archive"), archive)
906                 ),
907                 executionEnvironment()
908         );
909     }
910 
911     public void jarTests(String finalName) throws MojoExecutionException
912     {
913         executeMojo(
914                 plugin(
915                         groupId("org.apache.maven.plugins"),
916                         artifactId("maven-jar-plugin"),
917                         version(defaultArtifactIdToVersionMap.get("maven-jar-plugin"))
918                 ),
919                 goal("test-jar"),
920                 configuration(
921                         element(name("finalName"), finalName),
922                         element(name("archive"),
923                             element(name("manifestFile"), "${project.build.testOutputDirectory}/META-INF/MANIFEST.MF"))
924                 ),
925                 executionEnvironment()
926         );
927     }
928 
929     public void generateObrXml(File dep, File obrXml) throws MojoExecutionException
930     {
931         executeMojo(
932                 plugin(
933                         groupId("org.apache.felix"),
934                         artifactId("maven-bundle-plugin"),
935                         version(defaultArtifactIdToVersionMap.get("maven-bundle-plugin"))
936                 ),
937                 goal("install-file"),
938                 configuration(
939                         element(name("obrRepository"), obrXml.getPath()),
940 
941                         // the following three settings are required but not really used
942                         element(name("groupId"), "doesntmatter"),
943                         element(name("artifactId"), "doesntmatter"),
944                         element(name("version"), "doesntmatter"),
945 
946                         element(name("packaging"), "jar"),
947                         element(name("file"), dep.getPath())
948 
949                 ),
950                 executionEnvironment()
951         );
952     }
953 
954     /**
955      * Adds the file to the artifacts of this build.
956      * The artifact will be deployed using the name and version of the current project,
957      * as in if your artifactId is 'MyProject', it will be MyProject-1.0-SNAPSHOT.jar,
958      * overriding any artifact created at compilation time.
959      *
960      * Attached artifacts get installed (at install phase) and deployed (at deploy phase)
961      * @param file the file
962      * @param type the type of the file, default 'jar'
963      */
964     public void attachArtifact(File file, String type) throws MojoExecutionException
965     {
966 
967         executeMojo(
968                 plugin(
969                         groupId("org.codehaus.mojo"),
970                         artifactId("build-helper-maven-plugin"),
971                         version(defaultArtifactIdToVersionMap.get("build-helper-maven-plugin"))
972                 ),
973                 goal("attach-artifact"),
974                 configuration(
975                         element(name("artifacts"),
976                                 element(name("artifact"),
977                                         element(name("file"), file.getAbsolutePath()),
978                                         element(name("type"), type)
979                                     )
980                                 )
981                         ),
982                 executionEnvironment());
983 
984     }
985 
986     public void release(String mavenArgs) throws MojoExecutionException
987     {
988         String args = "";
989 
990         if(StringUtils.isNotBlank(mavenArgs)) {
991             args = mavenArgs;
992         }
993 
994         executeMojo(
995                 plugin(
996                         groupId("org.apache.maven.plugins"),
997                         artifactId("maven-release-plugin"),
998                         version(defaultArtifactIdToVersionMap.get("maven-release-plugin"))
999                 ),
1000                 goal("prepare"),
1001                 configuration(
1002                         element(name("arguments"), args)
1003                 ),
1004                 executionEnvironment()
1005         );
1006 
1007         executeMojo(
1008                 plugin(
1009                         groupId("org.apache.maven.plugins"),
1010                         artifactId("maven-release-plugin"),
1011                         version(defaultArtifactIdToVersionMap.get("maven-release-plugin"))
1012                 ),
1013                 goal("perform"),
1014                 configuration(
1015                         element(name("arguments"), args)
1016                 ),
1017                 executionEnvironment()
1018         );
1019     }
1020 
1021     public void generateRestDocs() throws MojoExecutionException
1022     {
1023         MavenProject prj = ctx.getProject();
1024         StringBuffer packagesPath = new StringBuffer();
1025         List<PluginXmlUtils.RESTModuleInfo> restModules = PluginXmlUtils.getRestModules(ctx);
1026 
1027         for(PluginXmlUtils.RESTModuleInfo moduleInfo : restModules)
1028         {
1029             List<String> packageList = moduleInfo.getPackagesToScan();
1030 
1031             for(String packageToScan : packageList)
1032             {
1033                 if(packagesPath.length() > 0)
1034                 {
1035                     packagesPath.append(File.pathSeparator);
1036                 }
1037 
1038                 String filePath = prj.getBuild().getSourceDirectory() + File.separator + packageToScan.replaceAll("\\.", Matcher.quoteReplacement(File.separator));
1039                 packagesPath.append(filePath);
1040             }
1041         }
1042 
1043         if(!restModules.isEmpty() && packagesPath.length() > 0)
1044         {
1045             Set<String> docletPaths = new HashSet<String>();
1046             StringBuffer docletPath = new StringBuffer(":" + prj.getBuild().getOutputDirectory());
1047             String resourcedocPath = fixWindowsSlashes(prj.getBuild().getOutputDirectory() + File.separator + "resourcedoc.xml");
1048 
1049             PluginXmlUtils.PluginInfo pluginInfo = PluginXmlUtils.getPluginInfo(ctx);
1050 
1051             try
1052             {
1053                 docletPaths.addAll(prj.getCompileClasspathElements());
1054                 docletPaths.addAll(prj.getRuntimeClasspathElements());
1055                 docletPaths.addAll(prj.getSystemClasspathElements());
1056 
1057                 //AMPS-663: add plugin execution classes to doclet path
1058                 URL[] pluginUrls = ((URLClassLoader)Thread.currentThread().getContextClassLoader()).getURLs();
1059                 for(URL pluginUrl : pluginUrls)
1060                 {
1061                     docletPaths.add(pluginUrl.getFile());
1062                 }
1063 
1064                 for(String path : docletPaths) {
1065                     docletPath.append(File.pathSeparator);
1066                     docletPath.append(path);
1067                 }
1068 
1069             } catch (DependencyResolutionRequiredException e)
1070             {
1071                 throw new MojoExecutionException("Dependencies must be resolved", e);
1072             }
1073 
1074             executeMojo(
1075                     plugin(
1076                             groupId("org.apache.maven.plugins"),
1077                             artifactId("maven-javadoc-plugin"),
1078                             version("2.8.1")
1079                     ),
1080                     goal("javadoc"),
1081                     configuration(
1082                             element(name("maxmemory"),"1024m"),
1083                             element(name("sourcepath"),packagesPath.toString()),
1084                             element(name("doclet"), ResourceDocletJSON.class.getName()),
1085                             element(name("docletPath"), docletPath.toString()),
1086                             element(name("docletArtifacts"),
1087                                 element(name("docletArtifact"),
1088                                         element(name("groupId"),"xerces"),
1089                                         element(name("artifactId"),"xercesImpl"),
1090                                         element(name("version"),"2.9.1")
1091                                 ),
1092                                 element(name("docletArtifact"),
1093                                         element(name("groupId"),"commons-lang"),
1094                                         element(name("artifactId"),"commons-lang"),
1095                                         element(name("version"),"2.6")
1096                                 )
1097                             ),
1098                             element(name("additionalparam"),"-output \"" + resourcedocPath + "\""),
1099                             element(name("useStandardDocletOptions"),"false")
1100                     ),
1101                     executionEnvironment()
1102             );
1103 
1104             try {
1105 
1106                 File userAppDocs = new File(prj.getBuild().getOutputDirectory(),"application-doc.xml");
1107                 if(!userAppDocs.exists())
1108                 {
1109                     String appDocText = FileUtils.getResourceContent("application-doc.xml");
1110                     appDocText = StringUtils.replace(appDocText, "${rest.doc.title}", pluginInfo.getName());
1111                     appDocText = StringUtils.replace(appDocText,"${rest.doc.description}",pluginInfo.getDescription());
1112                     File appDocFile = new File(prj.getBuild().getOutputDirectory(), "application-doc.xml");
1113 
1114                     FileUtils.saveTextFile(appDocText, appDocFile);
1115                     log.info("Wrote " + appDocFile.getAbsolutePath());
1116                 }
1117 
1118                 File userGrammars = new File(prj.getBuild().getOutputDirectory(),"application-grammars.xml");
1119                 if(!userGrammars.exists())
1120                 {
1121                     String grammarText = FileUtils.getResourceContent("application-grammars.xml");
1122                     File grammarFile = new File(prj.getBuild().getOutputDirectory(), "application-grammars.xml");
1123 
1124                     FileUtils.saveTextFile(grammarText, grammarFile);
1125 
1126                     log.info("Wrote " + grammarFile.getAbsolutePath());
1127                 }
1128 
1129             } catch (Exception e)
1130             {
1131                 throw new MojoExecutionException("Error writing REST application xml files",e);
1132             }
1133         }
1134     }
1135 
1136     private static class Container extends ProductArtifact
1137     {
1138         private final String id;
1139         private final String type;
1140 
1141         /**
1142          * Installable container that can be downloaded by Maven.
1143          *
1144          * @param id         identifier of container, eg. "tomcat5x".
1145          * @param groupId    groupId of container.
1146          * @param artifactId artifactId of container.
1147          * @param version    version number of container.
1148          */
1149         public Container(final String id, final String groupId, final String artifactId, final String version)
1150         {
1151             super(groupId, artifactId, version);
1152             this.id = id;
1153             this.type = "installed";
1154         }
1155 
1156         /**
1157          * Embedded container packaged with Cargo.
1158          *
1159          * @param id identifier of container, eg. "jetty6x".
1160          */
1161         public Container(final String id)
1162         {
1163             this.id = id;
1164             this.type = "embedded";
1165         }
1166 
1167         /**
1168          * @return identifier of container.
1169          */
1170         public String getId()
1171         {
1172             return id;
1173         }
1174 
1175         /**
1176          * @return "installed" or "embedded".
1177          */
1178         public String getType()
1179         {
1180             return type;
1181         }
1182 
1183         /**
1184          * @return <code>true</code> if the container type is "embedded".
1185          */
1186         public boolean isEmbedded()
1187         {
1188             return "embedded".equals(type);
1189         }
1190 
1191         /**
1192          * @param buildDir project.build.directory.
1193          * @return root directory of the container that will house the container installation and configuration.
1194          */
1195         public String getRootDirectory(String buildDir)
1196         {
1197             return buildDir + File.separator + "container" + File.separator + getId();
1198         }
1199 
1200         /**
1201          * @param buildDir project.build.directory.
1202          * @return directory housing the installed container.
1203          */
1204         public String getInstallDirectory(String buildDir)
1205         {
1206             return getRootDirectory(buildDir) + File.separator + getArtifactId() + "-" + getVersion();
1207         }
1208 
1209         /**
1210          * @param buildDir  project.build.directory.
1211          * @param productId product name.
1212          * @return directory to house the container configuration for the specified product.
1213          */
1214         public String getConfigDirectory(String buildDir, String productId)
1215         {
1216             return getRootDirectory(buildDir) + File.separator + "cargo-" + productId + "-home";
1217         }
1218     }
1219 }