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