1   package com.atlassian.maven.plugins.amps;
2   
3   import com.atlassian.maven.plugins.amps.product.ProductHandler;
4   import com.atlassian.maven.plugins.amps.product.ProductHandlerFactory;
5   import com.atlassian.maven.plugins.amps.util.ArtifactRetriever;
6   import com.atlassian.maven.plugins.amps.util.ProjectUtils;
7   import org.apache.maven.artifact.factory.ArtifactFactory;
8   import org.apache.maven.artifact.repository.ArtifactRepository;
9   import org.apache.maven.artifact.resolver.ArtifactResolver;
10  import org.apache.maven.model.Resource;
11  import org.apache.maven.plugin.MojoExecutionException;
12  import org.apache.maven.plugin.MojoFailureException;
13  import org.apache.maven.project.MavenProject;
14  import org.jfrog.maven.annomojo.annotations.MojoComponent;
15  import org.jfrog.maven.annomojo.annotations.MojoParameter;
16  
17  import java.io.File;
18  import java.util.ArrayList;
19  import java.util.HashMap;
20  import java.util.List;
21  import java.util.Map;
22  import java.util.Properties;
23  
24  /**
25   * Base class for webapp mojos
26   */
27  public abstract class AbstractProductHandlerMojo extends AbstractProductHandlerAwareMojo {
28  
29      // ------ start inline product context
30  
31      private static final String DEFAULT_CONTAINER = "tomcat6x";
32      private static final String DEFAULT_SERVER = "localhost";
33      private static final String DEFAULT_PRODUCT_DATA_VERSION = "LATEST";
34      private static final String DEFAULT_PDK_VERSION = "0.4";
35      private static final String DEFAULT_WEB_CONSOLE_VERSION = "1.2.8";
36  
37      /**
38        * Default product startup timeout: three minutes
39       */
40      private static final int DEFAULT_PRODUCT_STARTUP_TIMEOUT = 1000 * 60 * 3;
41  
42      /**
43        * Default product shutdown timeout: three minutes
44        */
45      private static final int DEFAULT_PRODUCT_SHUTDOWN_TIMEOUT = 1000 * 60 * 3;
46  
47      /**
48       * Container to run in
49       */
50      @MojoParameter(expression = "${container}", defaultValue = DEFAULT_CONTAINER)
51      protected String containerId;
52  
53      /**
54       * HTTP port for the servlet containers
55       */
56      @MojoParameter(expression = "${http.port}", defaultValue = "0")
57      private int httpPort;
58  
59      /**
60       * Application context path
61       */
62      @MojoParameter(expression = "${context.path}")
63      protected String contextPath;
64  
65      /**
66       * Application server
67       */
68      @MojoParameter(expression = "${server}", defaultValue = DEFAULT_SERVER)
69      protected String server;
70  
71      /**
72       * Webapp version
73       */
74      @MojoParameter(expression = "${product.version}")
75      private String productVersion;
76  
77      /**
78       * JVM arguments to pass to cargo
79       */
80      @MojoParameter(expression = "${jvmargs}")
81      protected String jvmArgs;
82  
83      /**
84       * Product startup timeout in milliseconds
85       */
86      @MojoParameter(expression = "${product.start.timeout}")
87      private int startupTimeout;
88  
89      /**
90       * Product shutdown timeout in milliseconds
91       */
92      @MojoParameter(expression = "${product.stop.timeout}")
93      private int shutdownTimeout;
94  
95      /**
96       * System systemProperties to pass to cargo
97       *
98       * @deprecated Since 3.2, use systemPropertyVariables instead
99       */
100     @MojoParameter
101     @Deprecated
102     protected Properties systemProperties = new Properties();
103 
104     /**
105      * System Properties to pass to cargo using a more familiar syntax.
106      *
107      * @since 3.2
108      */
109     @MojoParameter
110     protected Map<String, Object> systemPropertyVariables = new HashMap<String, Object>();
111 
112 
113     /**
114      * A log4j systemProperties file
115      */
116     @MojoParameter
117     protected File log4jProperties;
118 
119     /**
120      * The test resources version
121      * @deprecated Since 3.0-beta2
122      */
123     @MojoParameter(expression = "${test.resources.version}")
124     private String testResourcesVersion;
125 
126     /**
127      * The test resources version
128      */
129     @MojoParameter(expression = "${product.data.version}", defaultValue = DEFAULT_PRODUCT_DATA_VERSION)
130     private String productDataVersion;
131 
132     /**
133      * The path to a custom test resources zip
134      */
135     @MojoParameter(expression = "${product.data.path}")
136     private String productDataPath;
137 
138     /**
139      */
140     @MojoParameter
141     private List<ProductArtifact> pluginArtifacts = new ArrayList<ProductArtifact>();
142 
143     /**
144      */
145     @MojoParameter
146     private List<ProductArtifact> libArtifacts = new ArrayList<ProductArtifact>();
147 
148     /**
149      */
150     @MojoParameter
151     private List<ProductArtifact> bundledArtifacts = new ArrayList<ProductArtifact>();
152 
153     /**
154      * SAL version
155      * @deprecated Since 3.2, use {@link #pluginArtifacts} instead
156      */
157     @MojoParameter
158     private String salVersion;
159 
160     /**
161      * Atlassian Plugin Development Kit (PDK) version
162      * @deprecated Since 3.2, use {@link #pluginArtifacts} instead
163      */
164     @MojoParameter(defaultValue = DEFAULT_PDK_VERSION)
165     private String pdkVersion;
166 
167     /**
168      * Atlassian REST module version
169      * @deprecated Since 3.2, use {@link #pluginArtifacts} instead
170      */
171     @MojoParameter
172     private String restVersion;
173 
174 
175     /**
176      * Felix OSGi web console version
177      * @deprecated Since 3.2, use {@link #pluginArtifacts} instead
178      */
179     @MojoParameter(defaultValue =  DEFAULT_WEB_CONSOLE_VERSION)
180     private String webConsoleVersion;
181 
182     // ---------------- end product context
183 
184     /**
185      * Comma-delimited list of plugin artifacts in GROUP_ID:ARTIFACT_ID:VERSION form, where version can be
186      * ommitted, defaulting to LATEST
187      */
188     @MojoParameter(expression = "${plugins}")
189     private String pluginArtifactsString;
190 
191     /**
192      * Comma-delimited list of lib artifacts in GROUP_ID:ARTIFACT_ID:VERSION form, where version can be
193      * ommitted, defaulting to LATEST
194      */
195     @MojoParameter(expression = "${lib.plugins}")
196     private String libArtifactsString;
197 
198     /**
199      * Comma-delimited list of bundled plugin artifacts in GROUP_ID:ARTIFACT_ID:VERSION form, where version can be
200      * ommitted, defaulting to LATEST
201      */
202     @MojoParameter(expression = "${bundled.plugins}")
203     private String bundledArtifactsString;
204 
205     /**
206      * The build directory
207      */
208     @MojoParameter(expression = "${project.build.directory}", required = true)
209     protected File targetDirectory;
210 
211     /**
212      * The jar name
213      */
214     @MojoParameter(expression = "${project.build.finalName}", required = true)
215     protected String finalName;
216 
217     /**
218      * If the plugin and optionally its test plugin should be installed
219      */
220     @MojoParameter (expression = "${install.plugin}", defaultValue = "true")
221     protected boolean installPlugin;
222 
223     /**
224      * The artifact resolver is used to dynamically resolve JARs that have to be in the embedded
225      * container's classpaths. Another solution would have been to statitically define them a
226      * dependencies in the plugin's POM. Resolving them in a dynamic manner is much better as only
227      * the required JARs for the defined embedded container are downloaded.
228      */
229     @MojoComponent
230     protected ArtifactResolver artifactResolver;
231 
232     /**
233      * The local Maven repository. This is used by the artifact resolver to download resolved
234      * JARs and put them in the local repository so that they won't have to be fetched again next
235      * time the plugin is executed.
236      */
237     @MojoParameter(expression = "${localRepository}")
238     protected ArtifactRepository localRepository;
239 
240 
241     /**
242      * The remote Maven repositories used by the artifact resolver to look for JARs.
243      */
244     @MojoParameter(expression = "${project.remoteArtifactRepositories}")
245     protected List repositories;
246 
247     /**
248      * The artifact factory is used to create valid Maven
249      * {@link org.apache.maven.artifact.Artifact} objects. This is used to pass Maven artifacts to
250      * the artifact resolver so that it can download the required JARs to put in the embedded
251      * container's classpaths.
252      */
253     @MojoComponent
254     protected ArtifactFactory artifactFactory;
255 
256     /**
257      * A list of product-specific configurations
258      */
259     @MojoParameter
260     protected List<Product> products = new ArrayList<Product>();
261 
262     /**
263      * File the container logging output will be sent to.
264      */
265     @MojoParameter
266     private String output;
267 
268 
269     private Product createDefaultProductContext() throws MojoExecutionException
270     {
271         Product ctx = new Product();
272         ctx.setId(getProductId());
273         ctx.setContainerId(containerId);
274         ctx.setServer(server);
275         ctx.setContextPath(contextPath);
276         ctx.setJvmArgs(jvmArgs);
277         ctx.setStartupTimeout(startupTimeout);
278         ctx.setShutdownTimeout(shutdownTimeout);
279 
280         setDefaultSystemProperty(systemPropertyVariables, "atlassian.dev.mode", "true");
281         setDefaultSystemProperty(systemPropertyVariables, "java.awt.headless", "true");
282         setDefaultSystemProperty(systemPropertyVariables, "plugin.resource.directories", buildResourcesList());
283 
284         ctx.setSystemPropertyVariables(systemPropertyVariables);
285         ctx.setBundledArtifacts(bundledArtifacts);
286         ctx.setLibArtifacts(libArtifacts);
287         ctx.setPluginArtifacts(pluginArtifacts);
288         ctx.setLog4jProperties(log4jProperties);
289         ctx.setHttpPort(httpPort);
290 
291         ctx.setVersion(productVersion);
292         ctx.setDataVersion(productDataVersion);
293         ctx.setDataPath(productDataPath);
294 
295         // continue to have these work for now
296         ctx.setRestVersion(restVersion);
297         ctx.setSalVersion(salVersion);
298         ctx.setPdkVersion(pdkVersion);
299         ctx.setWebConsoleVersion(webConsoleVersion);
300 
301         ctx.setHttpPort(httpPort);
302         return ctx;
303     }
304 
305     /**
306      * @return a comma-separated list of resource directories.  If a test plugin is detected, the 
307      * test resources directories are included as well.
308      */
309     private String buildResourcesList()
310     {
311         // collect all resource directories and make them available for
312         // on-the-fly reloading
313         StringBuilder resourceProp = new StringBuilder();
314         MavenProject mavenProject = getMavenContext().getProject();
315         @SuppressWarnings("unchecked") List<Resource> resList = mavenProject.getResources();
316         for (int i = 0; i < resList.size(); i++) {
317             resourceProp.append(resList.get(i).getDirectory());
318             if (i + 1 != resList.size()) {
319                 resourceProp.append(",");
320             }
321         }
322 
323         if (ProjectUtils.shouldDeployTestJar(getMavenContext()))
324         {
325             @SuppressWarnings("unchecked") List<Resource> testResList = mavenProject.getTestResources();
326             for (int i = 0; i < testResList.size(); i++) {
327                 if (i == 0 && resourceProp.length() > 0)
328                 {
329                     resourceProp.append(",");
330                 }
331                 resourceProp.append(testResList.get(i).getDirectory());
332                 if (i + 1 != testResList.size()) {
333                     resourceProp.append(",");
334                 }
335             }
336         }
337         return resourceProp.toString();
338     }
339 
340     private static void setDefaultSystemProperty(final Map<String,Object> props, final String key, final String value)
341     {
342         if (!props.containsKey(key))
343         {
344             props.put(key, System.getProperty(key, value));
345         }
346     }
347 
348     private void postProcessProduct(Product product)
349     {
350 
351         String dversion = System.getProperty("product.data.version", product.getDataVersion());
352         String pversion = System.getProperty("product.version", product.getVersion());
353         String dpath = System.getProperty("product.data.path", product.getDataPath());
354 
355         product.setDataPath(dpath);
356         product.setDataVersion(dversion);
357         product.setVersion(pversion);
358         product.setArtifactRetriever(new ArtifactRetriever(artifactResolver, artifactFactory, localRepository, repositories));
359 
360         if (product.getContainerId() == null)
361         {
362             product.setContainerId(DEFAULT_CONTAINER);
363         }
364 
365         if (product.getServer() == null)
366         {
367             product.setServer(DEFAULT_SERVER);
368         }
369 
370         if (product.getDataVersion() == null)
371         {
372             product.setDataVersion(DEFAULT_PRODUCT_DATA_VERSION);
373         }
374 
375         if (product.getPdkVersion() == null)
376         {
377             product.setPdkVersion(DEFAULT_PDK_VERSION);
378         }
379 
380         if (product.getWebConsoleVersion() == null)
381         {
382             product.setWebConsoleVersion(DEFAULT_WEB_CONSOLE_VERSION);
383         }
384         
385         if (product.getOutput() == null)
386         {
387             product.setOutput(output);
388         }
389 
390         if (product.getStartupTimeout() <= 0)
391         {
392             product.setStartupTimeout(DEFAULT_PRODUCT_STARTUP_TIMEOUT);
393         }
394         
395         if (product.getShutdownTimeout() <= 0)
396         {
397             product.setShutdownTimeout(DEFAULT_PRODUCT_SHUTDOWN_TIMEOUT);
398         }
399         
400         product.setInstanceId(getProductInstanceId(product));
401     }
402 
403     private List<ProductArtifact> stringToArtifactList(String val, List<ProductArtifact> artifacts)
404     {
405         if (val == null || val.trim().length() == 0)
406         {
407             return artifacts;
408         }
409 
410         for (String ptn : val.split(","))
411         {
412             String[] items = ptn.split(":");
413             if (items.length < 2 || items.length > 3)
414             {
415                 throw new IllegalArgumentException("Invalid artifact pattern: " + ptn);
416             }
417             String groupId = items[0];
418             String artifactId = items[1];
419             String version = (items.length == 3 ? items[2] : "LATEST");
420             artifacts.add(new ProductArtifact(groupId, artifactId, version));
421         }
422         return artifacts;
423     }
424 
425     public final void execute() throws MojoExecutionException, MojoFailureException
426     {
427         stringToArtifactList(pluginArtifactsString, pluginArtifacts);
428         stringToArtifactList(libArtifactsString, libArtifacts);
429         stringToArtifactList(bundledArtifactsString, bundledArtifacts);
430         systemPropertyVariables.putAll((Map) systemProperties);
431 
432         detectDeprecatedVersionOverrides();
433 
434         doExecute();
435     }
436 
437     private void detectDeprecatedVersionOverrides()
438     {
439         Properties props = getMavenContext().getProject().getProperties();
440         for (String deprecatedProperty : new String[] {"sal.version", "rest.version", "web.console.version", "pdk.version"})
441         {
442             if (props.containsKey(deprecatedProperty))
443             {
444                 getLog().warn("The property '" + deprecatedProperty + "' is no longer usable to override the related bundled plugin." +
445                         "  Use <pluginArtifacts> or <libArtifacts> to explicitly override bundled plugins and libraries, respectively.");
446             }
447         }
448     }
449 
450     protected Map<String, Product> getProductContexts(MavenGoals goals) throws MojoExecutionException
451     {
452         Map<String, Product> productMap = new HashMap<String, Product>();
453 
454 
455         makeProductsInheritDefaultConfiguration(products, productMap);
456 
457         for (Product ctx : productMap.values())
458         {
459             postProcessProduct(ctx);
460             ProductHandler handler = ProductHandlerFactory.create(ctx.getId(), getMavenContext().getProject(), goals, getLog());
461             ctx.setHttpPort(ctx.getHttpPort() == 0 ? handler.getDefaultHttpPort() : ctx.getHttpPort());
462             ctx.setVersion(ctx.getVersion() == null ? "RELEASE" : ctx.getVersion());
463             ctx.setContextPath(ctx.getContextPath() == null ? "/" + handler.getId() : ctx.getContextPath());
464         }
465         return productMap;
466     }
467 
468     void makeProductsInheritDefaultConfiguration(List<Product> products, Map<String, Product> productMap) throws MojoExecutionException
469     {
470         productMap.put(getProductId(), createDefaultProductContext());
471         if (!products.isEmpty())
472         {
473             Product defaultProduct = createDefaultProductContext();
474             for (Product product : products)
475             {
476                 Product processedProduct = product.merge(defaultProduct);
477                 String id = getProductInstanceId(processedProduct);
478                 productMap.put(id, processedProduct);
479             }
480         }
481     }
482 
483     private String getProductInstanceId(Product processedProduct)
484     {
485         return processedProduct.getInstanceId() == null ? processedProduct.getId() : processedProduct.getInstanceId();
486     }
487 
488 
489     protected abstract void doExecute() throws MojoExecutionException, MojoFailureException;
490 }