PageRenderTime 51ms CodeModel.GetById 2ms app.highlight 41ms RepoModel.GetById 2ms app.codeStats 0ms

/maven-amps-plugin/src/main/java/com/atlassian/maven/plugins/amps/AbstractProductHandlerMojo.java

https://bitbucket.org/mmeinhold/amps
Java | 975 lines | 605 code | 131 blank | 239 comment | 91 complexity | 83ab670c1693e91b63672427974b1a13 MD5 | raw file
  1package com.atlassian.maven.plugins.amps;
  2
  3import java.io.File;
  4import java.io.IOException;
  5import java.net.HttpURLConnection;
  6import java.net.URL;
  7import java.util.ArrayList;
  8import java.util.HashMap;
  9import java.util.List;
 10import java.util.Map;
 11import java.util.Properties;
 12import java.util.concurrent.ExecutionException;
 13import java.util.concurrent.ExecutorService;
 14import java.util.concurrent.Executors;
 15import java.util.concurrent.Future;
 16import java.util.concurrent.TimeUnit;
 17import java.util.concurrent.TimeoutException;
 18
 19import com.atlassian.maven.plugins.amps.product.ProductHandler;
 20import com.atlassian.maven.plugins.amps.product.ProductHandlerFactory;
 21import com.atlassian.maven.plugins.amps.product.studio.StudioProductHandler;
 22import com.atlassian.maven.plugins.amps.util.ArtifactRetriever;
 23import com.atlassian.maven.plugins.amps.util.ProjectUtils;
 24
 25import com.google.common.base.Predicate;
 26import com.google.common.collect.Iterables;
 27import com.google.common.collect.Lists;
 28import com.google.common.collect.Maps;
 29
 30import org.apache.commons.lang.StringUtils;
 31import org.apache.maven.artifact.factory.ArtifactFactory;
 32import org.apache.maven.artifact.repository.ArtifactRepository;
 33import org.apache.maven.artifact.resolver.ArtifactResolver;
 34import org.apache.maven.model.Resource;
 35import org.apache.maven.plugin.MojoExecutionException;
 36import org.apache.maven.plugin.MojoFailureException;
 37import org.apache.maven.plugins.annotations.Component;
 38import org.apache.maven.plugins.annotations.Parameter;
 39import org.apache.maven.project.MavenProject;
 40
 41import static com.atlassian.maven.plugins.amps.product.ProductHandlerFactory.STUDIO;
 42import static com.atlassian.maven.plugins.amps.product.AmpsDefaults.*;
 43
 44/**
 45 * Base class for webapp mojos
 46 */
 47public abstract class AbstractProductHandlerMojo extends AbstractProductHandlerAwareMojo {
 48
 49    // ------ start inline product context
 50
 51    protected static final String JUNIT_VERSION = "4.10_1";
 52    protected static final String ATLASSIAN_TEST_RUNNER_VERSION = "1.1";
 53    protected static final String NO_TEST_GROUP = "__no_test_group__";
 54
 55    /**
 56     *  The artifacts to deploy for the test console if needed
 57     */
 58    protected final List<ProductArtifact> testFrameworkPlugins = new ArrayList<ProductArtifact>()
 59    {{
 60            add(new ProductArtifact("org.apache.servicemix.bundles","org.apache.servicemix.bundles.junit",JUNIT_VERSION));
 61            add(new ProductArtifact("com.atlassian.plugins","atlassian-plugins-osgi-testrunner-bundle",ATLASSIAN_TEST_RUNNER_VERSION));
 62        }};
 63    
 64
 65    /**
 66     * Container to run in
 67     */
 68    @Parameter(property = "container")
 69    protected String containerId;
 70
 71    /**
 72     * HTTP port for the servlet containers
 73     */
 74    @Parameter(property = "http.port", defaultValue = "0")
 75    private int httpPort;
 76
 77    /**
 78     * If product should be started with https on port 443
 79     */
 80    @Parameter(property = "use.https", defaultValue = "false")
 81    protected boolean useHttps;
 82
 83    /**
 84     * Application context path
 85     */
 86    @Parameter(property = "context.path")
 87    protected String contextPath;
 88
 89    /**
 90     * Application server
 91     */
 92    @Parameter(property = "server")
 93    protected String server;
 94
 95    /**
 96     * Webapp version
 97     */
 98    @Parameter(property = "product.version")
 99    private String productVersion;
100
101    /**
102     * JVM arguments to pass to cargo
103     */
104    @Parameter(property = "jvmargs")
105    protected String jvmArgs;
106
107    /**
108     * Product startup timeout in milliseconds
109     */
110    @Parameter(property = "product.start.timeout")
111    private int startupTimeout;
112
113    /**
114     * Product shutdown timeout in milliseconds
115     */
116    @Parameter(property = "product.stop.timeout")
117    private int shutdownTimeout;
118
119    /**
120     * System systemProperties to pass to cargo
121     *
122     * @deprecated Since 3.2, use systemPropertyVariables instead
123     */
124    @Parameter
125    @Deprecated
126    protected Properties systemProperties = new Properties();
127
128    /**
129     * System Properties to pass to cargo using a more familiar syntax.
130     *
131     * @since 3.2
132     */
133    @Parameter
134    protected Map<String, Object> systemPropertyVariables = new HashMap<String, Object>();
135
136
137    /**
138     * A log4j systemProperties file
139     */
140    @Parameter
141    protected File log4jProperties;
142
143    /**
144     * The test resources version
145     * @deprecated Since 3.0-beta2
146     */
147    @Deprecated
148    @Parameter(property = "test.resources.version")
149    private String testResourcesVersion;
150
151    /**
152     * The test resources version
153     */
154    @Parameter(property = "product.data.version")
155    private String productDataVersion;
156
157    /**
158     * The path to a custom test resources zip
159     */
160    @Parameter(property = "product.data.path")
161    private String productDataPath;
162
163    /**
164     * If FastDev should be enabled
165     */
166    @Parameter(property = "fastdev.enable", defaultValue = "true")
167    protected boolean enableFastdev;
168
169    /**
170     * The version of FastDev to bundle
171     */
172    @Parameter(property = "fastdev.version", defaultValue = DEFAULT_FASTDEV_VERSION)
173    protected String fastdevVersion;
174
175    /**
176     * If DevToolbox should be enabled
177     */
178    @Parameter(property = "devtoolbox.enable", defaultValue = "true")
179    protected boolean enableDevToolbox;
180
181    /**
182     * The version of DevToolbox to bundle
183     */
184    @Parameter(property = "devtoolbox.version", defaultValue = DEFAULT_DEV_TOOLBOX_VERSION)
185    protected String devToolboxVersion;
186
187    /**
188     * If PDE should be enabled
189     */
190    @Parameter(property = "pde.enable", defaultValue = "true")
191    protected boolean enablePde;
192
193    /**
194     * The version of the PDE to bundle
195     */
196    @Parameter(property = "pde.version", defaultValue = DEFAULT_PDE_VERSION)
197    protected String pdeVersion;
198
199    @Parameter
200    private List<ProductArtifact> pluginArtifacts = new ArrayList<ProductArtifact>();
201
202    /**
203     */
204    @Parameter
205    private List<ProductArtifact> libArtifacts = new ArrayList<ProductArtifact>();
206
207    /**
208     */
209    @Parameter
210    private List<ProductArtifact> bundledArtifacts = new ArrayList<ProductArtifact>();
211
212    /**
213     * SAL version
214     * @deprecated Since 3.2, use {@link #pluginArtifacts} instead
215     */
216    @Deprecated
217    @Parameter
218    private String salVersion;
219
220    /**
221     * Atlassian Plugin Development Kit (PDK) version
222     * @deprecated Since 3.2, use {@link #pluginArtifacts} instead
223     */
224    @Deprecated
225    @Parameter(defaultValue = DEFAULT_PDK_VERSION)
226    private String pdkVersion;
227
228    /**
229     * Atlassian REST module version
230     * @deprecated Since 3.2, use {@link #pluginArtifacts} instead
231     */
232    @Deprecated
233    @Parameter
234    private String restVersion;
235
236
237    /**
238     * Felix OSGi web console version
239     * @deprecated Since 3.2, use {@link #pluginArtifacts} instead
240     */
241    @Deprecated
242    @Parameter(defaultValue =  DEFAULT_WEB_CONSOLE_VERSION)
243    private String webConsoleVersion;
244
245    // ---------------- end product context
246
247    /**
248     * Comma-delimited list of plugin artifacts in GROUP_ID:ARTIFACT_ID:VERSION form, where version can be
249     * ommitted, defaulting to LATEST
250     */
251    @Parameter(property = "plugins")
252    private String pluginArtifactsString;
253
254    /**
255     * Comma-delimited list of lib artifacts in GROUP_ID:ARTIFACT_ID:VERSION form, where version can be
256     * ommitted, defaulting to LATEST
257     */
258    @Parameter(property = "lib.plugins")
259    private String libArtifactsString;
260
261    /**
262     * Comma-delimited list of bundled plugin artifacts in GROUP_ID:ARTIFACT_ID:VERSION form, where version can be
263     * ommitted, defaulting to LATEST
264     */
265    @Parameter(property = "bundled.plugins")
266    private String bundledArtifactsString;
267
268    /**
269     * The build directory
270     */
271    @Parameter(property = "project.build.directory", required = true)
272    protected File targetDirectory;
273
274    /**
275     * The jar name
276     */
277    @Parameter(property = "project.build.finalName", required = true)
278    protected String finalName;
279
280    /**
281     * If the plugin and optionally its test plugin should be installed
282     */
283    @Parameter(property = "install.plugin", defaultValue = "true")
284    protected boolean installPlugin;
285
286    /**
287     * The artifact resolver is used to dynamically resolve JARs that have to be in the embedded
288     * container's classpaths. Another solution would have been to statitically define them a
289     * dependencies in the plugin's POM. Resolving them in a dynamic manner is much better as only
290     * the required JARs for the defined embedded container are downloaded.
291     */
292    @Component
293    protected ArtifactResolver artifactResolver;
294
295    /**
296     * The local Maven repository. This is used by the artifact resolver to download resolved
297     * JARs and put them in the local repository so that they won't have to be fetched again next
298     * time the plugin is executed.
299     */
300    @Parameter(property = "localRepository")
301    protected ArtifactRepository localRepository;
302
303
304    /**
305     * The remote Maven repositories used by the artifact resolver to look for JARs.
306     */
307    @Parameter(property = "project.remoteArtifactRepositories")
308    protected List repositories;
309
310    /**
311     * The artifact factory is used to create valid Maven
312     * {@link org.apache.maven.artifact.Artifact} objects. This is used to pass Maven artifacts to
313     * the artifact resolver so that it can download the required JARs to put in the embedded
314     * container's classpaths.
315     */
316    @Component
317    protected ArtifactFactory artifactFactory;
318
319    /**
320     * A list of product-specific configurations (as literally provided in the pom.xml)
321     */
322    @Parameter
323    protected List<Product> products = new ArrayList<Product>();
324
325    /**
326     * A map of {instanceId -> Product}, initialized by {@link #createProductContexts()}.
327     * Cannot be set by the user.
328     */
329    private Map<String, Product> productMap;
330
331    /**
332     * File the container logging output will be sent to.
333     */
334    @Parameter
335    private String output;
336
337    /**
338     * Comma-delimited list of bundled plugin artifacts in GROUP_ID:ARTIFACT_ID:VERSION form, where version can be
339     * ommitted, defaulting to LATEST
340     */
341    @Parameter(property = "additional.resource.folders")
342    private String additionalResourceFolders;
343
344    /**
345     * Start the products in parallel (TestGroups and Studio).
346     */
347    @Parameter(property = "parallel", defaultValue = "false")
348    protected boolean parallel;
349
350
351    protected Product createDefaultProductContext() throws MojoExecutionException
352    {
353        Product ctx = new Product();
354        ctx.setId(getProductId());
355        ctx.setContainerId(containerId);
356        ctx.setServer(server);
357        ctx.setContextPath(contextPath);
358        ctx.setJvmArgs(jvmArgs);
359        ctx.setStartupTimeout(startupTimeout);
360        ctx.setShutdownTimeout(shutdownTimeout);
361
362        // If they aren't defined, define those system properties. They will override the product
363        // handler's properties.
364        Map<String, Object> properties = new HashMap<String, Object>(systemPropertyVariables);
365        properties.put("atlassian.sdk.version", getSdkVersion());
366        setDefaultSystemProperty(properties, "atlassian.dev.mode", "true");
367        setDefaultSystemProperty(properties, "java.awt.headless", "true");
368        setDefaultSystemProperty(properties, "plugin.resource.directories", buildResourcesList());
369        setDefaultSystemProperty(properties, "plugin.root.directories", buildRootProperty());
370
371        ctx.setSystemPropertyVariables(properties);
372        ctx.setBundledArtifacts(bundledArtifacts);
373        ctx.setLibArtifacts(libArtifacts);
374        ctx.setPluginArtifacts(pluginArtifacts);
375        ctx.setLog4jProperties(log4jProperties);
376        ctx.setHttpPort(httpPort);
377        ctx.setUseHttps(useHttps);
378
379        ctx.setVersion(productVersion);
380        ctx.setDataVersion(productDataVersion);
381        ctx.setDataPath(productDataPath);
382
383        // continue to have these work for now
384        ctx.setRestVersion(restVersion);
385        ctx.setSalVersion(salVersion);
386        ctx.setPdkVersion(pdkVersion);
387        ctx.setWebConsoleVersion(webConsoleVersion);
388
389        ctx.setEnableFastdev(enableFastdev);
390        ctx.setFastdevVersion(fastdevVersion);
391
392        ctx.setEnableDevToolbox(enableDevToolbox);
393        ctx.setDevToolboxVersion(devToolboxVersion);
394
395        ctx.setEnablePde(enablePde);
396        ctx.setPdeVersion(pdeVersion);
397
398        ctx.setHttpPort(httpPort);
399        return ctx;
400    }
401
402    /**
403     * @return a comma-separated list of resource directories.  If a test plugin is detected, the
404     * test resources directories are included as well.
405     */
406    private String buildResourcesList()
407    {
408        // collect all resource directories and make them available for
409        // on-the-fly reloading
410        StringBuilder resourceProp = new StringBuilder();
411        if(StringUtils.isNotBlank(additionalResourceFolders))
412        {
413            String[] dirs = StringUtils.split(additionalResourceFolders,",");
414            for(String rDir : dirs)
415            {
416                resourceProp.append(StringUtils.trim(rDir)).append(",");
417            }
418        }
419        
420        MavenProject mavenProject = getMavenContext().getProject();
421        @SuppressWarnings("unchecked") List<Resource> resList = mavenProject.getResources();
422        for (int i = 0; i < resList.size(); i++) {
423            resourceProp.append(resList.get(i).getDirectory());
424            if (i + 1 != resList.size()) {
425                resourceProp.append(",");
426            }
427        }
428
429        if (ProjectUtils.shouldDeployTestJar(getMavenContext()))
430        {
431            @SuppressWarnings("unchecked") List<Resource> testResList = mavenProject.getTestResources();
432            for (int i = 0; i < testResList.size(); i++) {
433                if (i == 0 && resourceProp.length() > 0)
434                {
435                    resourceProp.append(",");
436                }
437                resourceProp.append(testResList.get(i).getDirectory());
438                if (i + 1 != testResList.size()) {
439                    resourceProp.append(",");
440                }
441            }
442        }
443        return resourceProp.toString();
444    }
445
446    /**
447     * @return the path of the project root, for the <tt>plugin.root.directories</tt> system property.
448     *
449     * @since 3.6
450     */
451    private String buildRootProperty()
452    {
453        MavenProject mavenProject = getMavenContext().getProject();
454        return mavenProject.getBasedir().getPath();
455    }
456
457    private static void setDefaultSystemProperty(final Map<String,Object> props, final String key, final String value)
458    {
459        if (!props.containsKey(key))
460        {
461            props.put(key, System.getProperty(key, value));
462        }
463    }
464
465    /**
466     * Set the default values for the product
467     * @param product the product
468     * @param handler the product handler associated to the product
469     */
470    protected void setDefaultValues(Product product, ProductHandler handler)
471    {
472        product.setInstanceId(getProductInstanceId(product));
473
474        //Apply the common default values
475        String dversion = System.getProperty("product.data.version", product.getDataVersion());
476        String pversion = System.getProperty("product.version", product.getVersion());
477        String dpath = System.getProperty("product.data.path", product.getDataPath());
478
479        // If it's a Studio product, some defaults are different (ex: context path for Confluence is /wiki)
480        if (!StudioProductHandler.setDefaultValues(getMavenContext(), product))
481        {
482            // hacky workaround for AMPS-738:  avoid applying the regular product defaults to a Studio sub-product;
483            // however, do apply them to the main Studio product if and only if we're explicitly running Studio (so
484            // we'll get the right result for command-line options like "-Dproduct=studio -Dproduct.version=108.3").
485            if (!STUDIO.equals(product.getId()) || STUDIO.equals(System.getProperty("product")))
486            {
487                product.setVersion(pversion);
488	            product.setDataVersion(dversion);
489                product.setDataPath(dpath);
490            }
491        }
492
493        product.setArtifactRetriever(new ArtifactRetriever(artifactResolver, artifactFactory, localRepository, repositories));
494
495        if (product.getContainerId() == null)
496        {
497            product.setContainerId(handler.getDefaultContainerId());
498        }
499
500        if (product.getServer() == null)
501        {
502            product.setServer(DEFAULT_SERVER);
503        }
504
505        if (product.getPdkVersion() == null)
506        {
507            product.setPdkVersion(DEFAULT_PDK_VERSION);
508        }
509
510        if (product.getWebConsoleVersion() == null)
511        {
512            product.setWebConsoleVersion(DEFAULT_WEB_CONSOLE_VERSION);
513        }
514
515        if (product.isEnableFastdev() == null)
516        {
517            product.setEnableFastdev(true);
518        }
519
520        if (product.getFastdevVersion() == null)
521        {
522            product.setFastdevVersion(DEFAULT_FASTDEV_VERSION);
523        }
524
525        if (product.isEnableDevToolbox() == null)
526        {
527            product.setEnableDevToolbox(true);
528        }
529
530        if (product.getDevToolboxVersion() == null)
531        {
532            product.setDevToolboxVersion(DEFAULT_DEV_TOOLBOX_VERSION);
533        }
534
535        if (product.getPdeVersion() == null)
536        {
537            product.setPdeVersion(DEFAULT_PDE_VERSION);
538        }
539
540        if (product.getOutput() == null)
541        {
542            product.setOutput(output);
543        }
544
545        if (product.getStartupTimeout() <= 0)
546        {
547            product.setStartupTimeout(DEFAULT_PRODUCT_STARTUP_TIMEOUT);
548        }
549
550        if (product.getShutdownTimeout() <= 0)
551        {
552            product.setShutdownTimeout(DEFAULT_PRODUCT_SHUTDOWN_TIMEOUT);
553        }
554
555        if (product.getHttpPort() == 0)
556        {
557            product.setHttpPort(handler.getDefaultHttpPort());
558        }
559
560        if (product.getUseHttps() == null)
561        {
562            product.setUseHttps(false);
563        }
564
565        if (product.getVersion() == null)
566        {
567            product.setVersion("RELEASE");
568        }
569
570        if (product.getDataVersion() == null)
571        {
572            // Default the productDataVersion to match the productVersion. Defaulting to LATEST
573            // is bad because there is no guarantee that a snapshots let alone a more recent
574            // version of a product's data is compatible with an earlier version of the product or
575            // that a product is required to provide a 'downgrade' task. Developers can still
576            // specify LATEST explicitly
577            product.setDataVersion(product.getVersion());
578        }
579
580        if (product.getContextPath() == null)
581        {
582            product.setContextPath(handler.getDefaultContextPath());
583        }
584        
585        if (product.getDataSources() == null)
586        {
587            product.setDataSources(Lists.<DataSource>newArrayList());
588        }
589    }
590
591    private List<ProductArtifact> stringToArtifactList(String val, List<ProductArtifact> artifacts)
592    {
593        if (val == null || val.trim().length() == 0)
594        {
595            return artifacts;
596        }
597
598        for (String ptn : val.split(","))
599        {
600            String[] items = ptn.split(":");
601            if (items.length < 2 || items.length > 3)
602            {
603                throw new IllegalArgumentException("Invalid artifact pattern: " + ptn);
604            }
605            String groupId = items[0];
606            String artifactId = items[1];
607            String version = (items.length == 3 ? items[2] : "LATEST");
608            artifacts.add(new ProductArtifact(groupId, artifactId, version));
609        }
610        return artifacts;
611    }
612
613    @Override
614    public final void execute() throws MojoExecutionException, MojoFailureException
615    {
616        stringToArtifactList(pluginArtifactsString, pluginArtifacts);
617        stringToArtifactList(libArtifactsString, libArtifacts);
618        stringToArtifactList(bundledArtifactsString, bundledArtifacts);
619        systemPropertyVariables.putAll((Map) systemProperties);
620
621        detectDeprecatedVersionOverrides();
622
623        doExecute();
624    }
625
626    private void detectDeprecatedVersionOverrides()
627    {
628        Properties props = getMavenContext().getProject().getProperties();
629        for (String deprecatedProperty : new String[] {"sal.version", "rest.version", "web.console.version", "pdk.version"})
630        {
631            if (props.containsKey(deprecatedProperty))
632            {
633                getLog().warn("The property '" + deprecatedProperty + "' is no longer usable to override the related bundled plugin." +
634                        "  Use <pluginArtifacts> or <libArtifacts> to explicitly override bundled plugins and libraries, respectively.");
635            }
636        }
637    }
638
639    /**
640     * Builds the map {instanceId -> Product bean}, based on: <ul>
641     * <li>the {@literal <products>} tag</li>
642     * <li>the configuration values inherited from the {@literal <configuration>} tag
643     * </ul>
644     * @throws MojoExecutionException
645     */
646    Map<String, Product> createProductContexts() throws MojoExecutionException
647    {
648        Map<String, Product> productMap = Maps.newHashMap();
649        MavenContext mavenContext = getMavenContext();
650        MavenGoals goals = getMavenGoals();
651
652        // Products in the <products> tag inherit from the upper settings, e.g. when there's a <httpPort> tag for all products
653        makeProductsInheritDefaultConfiguration(products, productMap);
654
655        for (Product ctx : Lists.newArrayList(productMap.values()))
656        {
657            ProductHandler handler = ProductHandlerFactory.create(ctx.getId(), mavenContext, goals);
658            setDefaultValues(ctx, handler);
659
660            // If it's a Studio product, check dependent instance are present
661            for (String instanceId : StudioProductHandler.getDependantInstances(ctx))
662            {
663                if (!productMap.containsKey(instanceId))
664                {
665                    ProductHandler dependantHandler = createProductHandler(instanceId);
666                    productMap.put(instanceId, createProductContext(instanceId, instanceId, dependantHandler));
667                }
668            }
669        }
670
671        // Submit the Studio products for configuration
672        StudioProductHandler studioProductHandler = (StudioProductHandler) ProductHandlerFactory.create(ProductHandlerFactory.STUDIO, mavenContext, goals);
673        studioProductHandler.configureStudioProducts(productMap);
674
675        return productMap;
676    }
677
678    /**
679     * Returns the map { instanceId -> Product } with initialized values.
680     */
681    protected Map<String, Product> getProductContexts() throws MojoExecutionException
682    {
683        if (productMap == null)
684        {
685            productMap = createProductContexts();
686        }
687        return productMap;
688    }
689
690    /**
691     * Puts the list of {@literal <products>} in productMap:
692     * <ul>
693     * <li>The {@literal <product>} from the maven-amps-plugin configuration (if missing, RefApp is used)</li>
694     * <li>The {@literal <products>} from the maven-amps-plugin configuration</li>
695     * </ul>
696     */
697    void makeProductsInheritDefaultConfiguration(List<Product> products, Map<String, Product> productMap) throws MojoExecutionException
698    {
699        Product defaultProduct = createDefaultProductContext();
700        productMap.put(getProductId(), defaultProduct);
701        if (!products.isEmpty())
702        {
703            for (Product product : products)
704            {
705                Product processedProduct = product.merge(defaultProduct);
706                if (ProductHandlerFactory.STUDIO_CROWD.equals(processedProduct.getId()))
707                {
708                    // This is a temporary fix for StudioCrowd - it requires atlassian.dev.mode=false - see AMPS-556
709                    processedProduct.getSystemPropertyVariables().put("atlassian.dev.mode", "false");
710                }
711                String instanceId = getProductInstanceId(processedProduct);
712                productMap.put(instanceId, processedProduct);
713            }
714        }
715    }
716
717    private String getProductInstanceId(Product processedProduct)
718    {
719        return processedProduct.getInstanceId() == null ? processedProduct.getId() : processedProduct.getInstanceId();
720    }
721
722
723    private Product createProductContext(String productNickname, String instanceId, ProductHandler handler) throws MojoExecutionException
724    {
725        getLog().info(String.format("Studio (instanceId=%s): No product with name %s is defined in the pom. Using a default product.", instanceId, productNickname));
726        Product product;
727        product = createDefaultProductContext();
728        product.setId(productNickname);
729        product.setInstanceId(instanceId);
730        setDefaultValues(product, handler);
731        if (ProductHandlerFactory.STUDIO_CROWD.equals(product.getId()))
732        {
733            // This is a temporary fix for StudioCrowd - it requires atlassian.dev.mode=false - see AMPS-556
734            product.getSystemPropertyVariables().put("atlassian.dev.mode", "false");
735        }
736        return product;
737    }
738
739    /**
740     * Attempts to stop all products. Returns after the timeout or as soon as all products
741     * are shut down.
742     */
743    protected void stopProducts(List<ProductExecution> productExecutions) throws MojoExecutionException
744    {
745        ExecutorService executor = Executors.newFixedThreadPool(productExecutions.size());
746        try
747        {
748            long before = System.nanoTime();
749            for (final ProductExecution execution : Iterables.reverse(productExecutions))
750            {
751                final Product product = execution.getProduct();
752                final ProductHandler productHandler = execution.getProductHandler();
753
754                Future<?> task = executor.submit(new Runnable()
755                {
756                    @Override
757                    public void run()
758                    {
759                        getLog().info(product.getInstanceId() + ": Shutting down");
760                        try
761                        {
762                            productHandler.stop(product);
763                        }
764                        catch (MojoExecutionException e)
765                        {
766                            getLog().error("Exception while trying to stop " + product.getInstanceId(), e);
767                        }
768                    }
769                });
770
771                try
772                {
773                    task.get(product.getShutdownTimeout(), TimeUnit.MILLISECONDS);
774                }
775                catch (TimeoutException e)
776                {
777                    getLog().info(product.getInstanceId() + " shutdown: Didn't return in time");
778                    task.cancel(true);
779                }
780            }
781            long after = System.nanoTime();
782            getLog().info("amps:stop in " + TimeUnit.NANOSECONDS.toSeconds(after - before) + "s");
783        }
784        catch (InterruptedException e1)
785        {
786            Thread.currentThread().interrupt();
787        }
788        catch (ExecutionException e)
789        {
790            throw new MojoExecutionException("Exception while stopping the products", e);
791        }
792
793        // If products were launched in parallel, check they are stopped: CodeHaus Cargo returns before
794        // products are down.
795        if (parallel)
796        {
797            waitForProducts(productExecutions, false);
798        }
799    }
800
801
802    /**
803     * Waits until all products are running or stopped
804     * @param startingUp true if starting up the products, false if shutting down.
805     */
806    protected void waitForProducts(List<ProductExecution> productExecutions, boolean startingUp) throws MojoExecutionException
807    {
808        for (ProductExecution productExecution : productExecutions)
809        {
810            pingRepeatedly(productExecution.getProduct(), startingUp);
811        }
812    }
813
814    /**
815     * Ping the product until it's up or stopped
816     * @param startingUp true if applications are expected to be up; false if applications are expected to be brought down
817     * @throws MojoExecutionException if the product didn't have the expected behaviour beofre the timeout
818     */
819    private void pingRepeatedly(Product product, boolean startingUp) throws MojoExecutionException
820    {
821        if (product.getHttpPort() != 0)
822        {
823            String url = "http://" + product.getServer() + ":" + product.getHttpPort();
824            if (StringUtils.isNotBlank(product.getContextPath()))
825            {
826                url = url + product.getContextPath();
827            }
828
829            int timeout = startingUp ? product.getStartupTimeout() : product.getShutdownTimeout();
830            final long end = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeout);
831            boolean interrupted = false;
832            boolean success = false;
833            String lastMessage = "";
834
835            // keep retrieving from the url until a good response is returned, under a time limit.
836            while (!success && !interrupted && System.nanoTime() < end)
837            {
838                HttpURLConnection connection = null;
839                try
840                {
841                    URL urlToPing = new URL(url);
842                    connection = (HttpURLConnection) urlToPing.openConnection();
843                    int response = connection.getResponseCode();
844                    // Tomcat returns 404 until the webapp is up
845                    lastMessage = "Last response code is " + response;
846                    if (startingUp)
847                    {
848                        success = response < 400;
849                    }
850                    else
851                    {
852                        success = response >= 400;
853                    }
854                }
855                catch (IOException e)
856                {
857                    lastMessage = e.getMessage();
858                    success = !startingUp;
859                }
860                finally
861                {
862                    if (connection != null)
863                    {
864                        try
865                        {
866                            connection.getInputStream().close();
867                        }
868                        catch (IOException e)
869                        {
870                            // Don't do anything
871                        }
872                    }
873                }
874
875                if (!success)
876                {
877                    getLog().info("Waiting for " + url + (startingUp ? "" : " to stop"));
878                    try
879                    {
880                        Thread.sleep(1000);
881                    }
882                    catch (InterruptedException e)
883                    {
884                        Thread.currentThread().interrupt();
885                        interrupted = true;
886                        break;
887                    }
888                }
889            }
890
891            if (!success)
892            {
893                throw new MojoExecutionException(String.format("The product %s didn't %s after %ds at %s. %s",
894                        product.getInstanceId(), startingUp ? "start" : "stop", TimeUnit.MILLISECONDS.toSeconds(timeout), url, lastMessage));
895            }
896        }
897    }
898
899    /**
900     * @return the list of instances for the product 'studio'
901     */
902    private Iterable<ProductExecution> getStudioExecutions(final List<ProductExecution> productExecutions)
903    {
904        return Iterables.filter(productExecutions, new Predicate<ProductExecution>(){
905
906            @Override
907            public boolean apply(ProductExecution input)
908            {
909                return input.getProductHandler() instanceof StudioProductHandler;
910            }});
911    }
912
913
914    /**
915     * If there is any Studio instance, returns a list with all products requested by this instance.
916     *
917     * Configures both the Studio instance and its dependent products.
918     *
919     * @param productExecutions the current list of products to run
920     * @param goals
921     * @return the complete list of products to run
922     * @throws MojoExecutionException
923     */
924    protected List<ProductExecution> includeStudioDependentProducts(final List<ProductExecution> productExecutions, final MavenGoals goals)
925            throws MojoExecutionException
926    {
927        // If one of the products is Studio, ask him/her which other products he/she wants to run
928        Iterable<ProductExecution> studioExecutions = getStudioExecutions(productExecutions);
929        if (Iterables.isEmpty(studioExecutions))
930        {
931            return productExecutions;
932        }
933
934        // We have studio execution(s), so we need to add all products requested by Studio
935        List<ProductExecution> productExecutionsIncludingStudio = Lists.newArrayList(productExecutions);
936        Map<String, Product> allContexts = getProductContexts();
937        for(ProductExecution execution : studioExecutions)
938        {
939            for (String dependantProduct : StudioProductHandler.getDependantInstances(execution.getProduct()))
940            {
941                Product product = allContexts.get(dependantProduct);
942                productExecutionsIncludingStudio.add(toProductExecution(product));
943            }
944        }
945
946        return productExecutionsIncludingStudio;
947    }
948
949    protected ProductExecution toProductExecution(Product product)
950    {
951        return new ProductExecution(product, createProductHandler(product.getId()));
952    }
953
954    protected abstract void doExecute() throws MojoExecutionException, MojoFailureException;
955
956    protected void setParallelMode(List<ProductExecution> executions)
957    {
958        // Apply the configuration of the mojo to the products
959        for (ProductExecution execution : executions)
960        {
961            Product product = execution.getProduct();
962            if (parallel)
963            {
964                if (product.getSynchronousStartup() == null)
965                {
966                    product.setSynchronousStartup(Boolean.FALSE);
967                }
968            }
969            else
970            {
971                product.setSynchronousStartup(Boolean.TRUE);
972            }
973        }
974    }
975}