001package org.andromda.maven.plugin.andromdapp;
002
003import java.io.BufferedReader;
004import java.io.File;
005import java.io.IOException;
006import java.io.InputStreamReader;
007import java.lang.reflect.Method;
008import java.util.ArrayList;
009import java.util.Arrays;
010import java.util.Collection;
011import java.util.Collections;
012import java.util.HashMap;
013import java.util.Iterator;
014import java.util.LinkedHashMap;
015import java.util.List;
016import java.util.Map;
017import java.util.Properties;
018import org.andromda.maven.plugin.andromdapp.utils.ProjectUtils;
019import org.andromda.maven.plugin.andromdapp.utils.Projects;
020import org.apache.commons.lang.ObjectUtils;
021import org.apache.commons.lang.StringUtils;
022import org.apache.maven.BuildFailureException;
023import org.apache.maven.execution.MavenSession;
024import org.apache.maven.execution.ReactorManager;
025import org.apache.maven.lifecycle.LifecycleExecutionException;
026import org.apache.maven.lifecycle.LifecycleExecutor;
027import org.apache.maven.plugin.AbstractMojo;
028import org.apache.maven.plugin.MojoExecutionException;
029import org.apache.maven.project.MavenProject;
030import org.apache.maven.project.MavenProjectBuilder;
031import org.apache.maven.project.ProjectBuildingException;
032import org.codehaus.plexus.util.dag.CycleDetectedException;
033
034/**
035 * A Mojo used for executing the build goals from the top level project.
036 *
037 * @goal build
038 * @author Chad Brandon
039 */
040public class BuildMojo
041    extends AbstractMojo
042{
043    /**
044     * @component role="org.apache.maven.lifecycle.LifecycleExecutor"
045     */
046    private LifecycleExecutor lifecycleExecutor;
047
048    /**
049     * @parameter expression="${session}"
050     */
051    private MavenSession session;
052
053    /**
054     * @parameter expression="${project.basedir}"
055     */
056    private File baseDirectory;
057
058    /**
059     * A comma separated list of modules to execute in the form:
060     * <em>-Dmodules=mda,core,common</em> or if you want to specify the goals
061     * to execute as well:
062     * <em>-Dmodules=mda:[goal1+goal2+goal3],core:[goal1]<em>.
063     *
064     * @parameter expression="${modules}"
065     */
066    private String modules;
067
068    /**
069     * @parameter expression="${project}"
070     * @required
071     * @readonly
072     */
073    private MavenProject project;
074
075    /**
076     * If defined starts the build console (i.e. keeps maven loaded and running)
077     *
078     * @parameter expression="${console}"
079     */
080    private String startConsole;
081
082    /**
083     * The default module goals to execute.
084     *
085     * @parameter
086     */
087    private List<String> goals = new ArrayList<String>(Arrays.asList("install"));
088
089    /**
090     * The string used to quite the console;
091     */
092    private static final String EXIT = "exit";
093
094    /**
095     * Used to construct Maven project instances from POMs.
096     *
097     * @component
098     */
099    private MavenProjectBuilder projectBuilder;
100
101    /**
102     * Any execution properties.
103     *
104     * @parameter
105     */
106    private Properties executionProperties = new Properties();
107
108    /**
109     * Identifies system properties when running in console mode.
110     */
111    private static final String EXECUTION_PROPERTY_TOKEN = "-D";
112
113    /**
114     * Lists all execution properties when running in console mode.
115     */
116    private static final String LIST_PROPERTIES = "-list";
117
118    /**
119     * Clears all execution properties.
120     */
121    private static final String CLEAR_PROPERTIES = "-clear";
122
123    /**
124     * Explicitly calls the garbage collector.
125     */
126    private static final String GARBAGE_COLLECT = "-gc";
127
128    /**
129     * The prefix environment variables must have.
130     *
131     * @parameter expression="env."
132     */
133    private String environmentVariablePrefix;
134
135    /**
136     * @see org.apache.maven.plugin.Mojo#execute()
137     */
138    public void execute()
139        throws MojoExecutionException
140    {
141        try
142        {
143            final Map environment = this.getEnvironment();
144            if (this.startConsole != null && !this.startConsole.equals(Boolean.FALSE.toString()))
145            {
146                boolean executed = false;
147                this.printLine();
148                while (true)
149                {
150                    this.printConsolePrompt();
151                    String input = StringUtils.trimToEmpty(this.readLine());
152                    if (EXIT.equals(input))
153                    {
154                        break;
155                    }
156                    if (input.startsWith(EXECUTION_PROPERTY_TOKEN))
157                    {
158                        input = input.replaceFirst(
159                                EXECUTION_PROPERTY_TOKEN,
160                                "");
161                        int index = input.indexOf('=');
162                        String name;
163                        String value;
164                        if (index <= 0)
165                        {
166                            name = input.trim();
167                            value = "true";
168                        }
169                        else
170                        {
171                            name = input.substring(
172                                    0,
173                                    index).trim();
174                            value = input.substring(index + 1).trim();
175                        }
176                        if (value.startsWith(this.environmentVariablePrefix))
177                        {
178                            value = StringUtils.replace(
179                                    value,
180                                    this.environmentVariablePrefix,
181                                    "");
182                            if (environment.containsKey(value))
183                            {
184                                value = ObjectUtils.toString(environment.get(value)).trim();
185                            }
186                        }
187                        this.executionProperties.put(
188                            name,
189                            value);
190                        System.setProperty(
191                            name,
192                            value);
193                        this.printExecutionProperties();
194                    }
195                    else if (LIST_PROPERTIES.equals(input))
196                    {
197                        this.printExecutionProperties();
198                    }
199                    else if (CLEAR_PROPERTIES.equals(input))
200                    {
201                        this.executionProperties.clear();
202                        this.printExecutionProperties();
203                    }
204                    else if (GARBAGE_COLLECT.equals(input))
205                    {
206                        // TODO FindBugs: Except for specific use in benchmarking, this is very dubious.
207                        System.gc();
208                    }
209                    else
210                    {
211                        try
212                        {
213                            executed = this.executeModules(input);
214
215                            // - if nothing was executed, try a goal in the current project
216                            if (this.project != null && !executed && input.trim().length() > 0)
217                            {
218                                executed = true;
219                                final List<String> goals = Arrays.asList(input.split("\\s+"));
220                                this.executeModules(
221                                    StringUtils.join(
222                                        this.project.getModules().iterator(),
223                                        ","),
224                                    goals,
225                                    true);
226                            }
227                        }
228                        catch (final Throwable throwable)
229                        {
230                            throwable.printStackTrace();
231                        }
232                        if (executed)
233                        {
234                            this.printLine();
235                        }
236                        Projects.instance().clear();
237                    }
238                }
239            }
240            else
241            {
242                this.executionProperties.putAll(this.session.getExecutionProperties());
243                this.executeModules(this.modules);
244            }
245        }
246        catch (final Throwable throwable)
247        {
248            throw new MojoExecutionException("Error executing modules", throwable);
249        }
250    }
251
252    private static final String GET_ENVIRONMENT_METHOD = "getenv";
253
254    /**
255     * Retrieves the environment variables (will only work when running jdk5 or above).
256     *
257     * @return the environment variables.
258     */
259    private Map<String, String> getEnvironment()
260    {
261        final Map<String, String> variables = new HashMap<String, String>();
262        try
263        {
264            final Method method = System.class.getMethod(
265                    GET_ENVIRONMENT_METHOD);
266            final Object result = method.invoke(
267                    System.class);
268            if (result instanceof Map)
269            {
270                variables.putAll((Map<String, String>)result);
271            }
272        }
273        catch (Exception exception)
274        {
275            // - ignore (means we can't retrieve the environment with the current JDK).
276        }
277        return variables;
278    }
279
280    private void printExecutionProperties()
281    {
282        this.printLine();
283        this.printTextWithLine("| ------------- execution properties ------------- |");
284        for (final Iterator<Object> iterator = this.executionProperties.keySet().iterator(); iterator.hasNext();)
285        {
286            final String name = (String)iterator.next();
287            System.out.println("    " + name + " = " + this.executionProperties.getProperty(name));
288        }
289        this.printTextWithLine("| ------------------------------------------------ |");
290        this.printLine();
291    }
292
293    /**
294     * Prints the prompt for the console
295     */
296    private void printConsolePrompt()
297    {
298        if (this.project != null)
299        {
300            this.printText("");
301            this.printText(this.project.getArtifactId() + ' ' + this.project.getVersion() + '>');
302        }
303    }
304
305    /**
306     * Prints text to the console.
307     *
308     * @param text the text to print to the console;
309     */
310    private void printText(final String text)
311    {
312        System.out.print(text);
313        System.out.flush();
314    }
315
316    /**
317     * Prints text with a new line to the console.
318     *
319     * @param text the text to print to the console.
320     */
321    private void printTextWithLine(final String text)
322    {
323        System.out.println(text);
324        System.out.flush();
325    }
326
327    /**
328     * Prints a line to standard output.
329     */
330    private void printLine()
331    {
332        System.out.println();
333        System.out.flush();
334    }
335
336    /**
337     * Reads a line from standard input and returns the value.
338     *
339     * @return the value read from standard input.
340     */
341    @SuppressWarnings("null")
342    private String readLine()
343    {
344        final BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
345        String inputString = null;
346        try
347        {
348            inputString = input.readLine();
349        }
350        catch (final IOException exception)
351        {
352            inputString = null;
353        }
354        return StringUtils.trimToNull(inputString);
355    }
356
357    /**
358     * Creates all project modules and executes them.
359     *
360     * @param modules the comma separated list of modules to execute.
361     * @return true if any modules were executed, false otherwise.
362     * @throws MojoExecutionException
363     * @throws CycleDetectedException
364     * @throws LifecycleExecutionException
365     * @throws BuildFailureException
366     */
367    private boolean executeModules(final String modules)
368        throws MojoExecutionException
369    {
370        return this.executeModules(
371            modules,
372            null,
373            false);
374    }
375
376    /**
377     * Creates all project modules and executes them.
378     *
379     * @param modules The list of modules to execute.
380     * @param goals the list of goals to execute (if null, the, goals will be retrieved from project map).
381     * @param sortProjects whether or not projects should be sorted and then executed or whether they should be executed in the
382     *                     order they're in.
383     * @throws CycleDetectedException
384     * @throws LifecycleExecutionException
385     * @throws MojoExecutionException
386     * @throws BuildFailureException
387     * @return true/false on whether or not any modules were executed
388     */
389    private boolean executeModules(
390        final String modules,
391        final List<String> goals,
392        boolean sortProjects)
393        throws MojoExecutionException
394    {
395        final Map<MavenProject, List<String>> projects = this.collectProjects(modules);
396        boolean executed = !projects.isEmpty();
397
398        // - only execute if we have some projects
399        if (executed)
400        {
401            if (!sortProjects)
402            {
403                for (final MavenProject project : projects.keySet())
404                {
405                    List<String> projectGoals;
406                    if (goals == null)
407                    {
408                        projectGoals = projects.get(project);
409                        if (projectGoals.isEmpty())
410                        {
411                            projectGoals.addAll(this.goals);
412                        }
413                    }
414                    else
415                    {
416                        projectGoals = goals;
417                    }
418                    this.executeProjects(
419                        Collections.singletonList(project),
420                        projectGoals);
421                }
422            }
423            else
424            {
425                this.executeProjects(
426                    projects.keySet(),
427                    goals);
428            }
429        }
430        return executed;
431    }
432
433    /**
434     * Executes the given maven <code>project</code>.
435     *
436     * @param projects the projects to execute.
437     * @param goals the goals to execute on the project.
438     * @throws MojoExecutionException
439     * @throws CycleDetectedException
440     * @throws LifecycleExecutionException
441     * @throws BuildFailureException
442     */
443    private void executeProjects(
444        final Collection<MavenProject> projects,
445        final List<String> goals)
446        throws MojoExecutionException
447    {
448        try
449        {
450            if (goals.isEmpty())
451            {
452                goals.addAll(this.goals);
453            }
454            if (projects.size() > 1)
455            {
456                this.getLog().info("Reactor build order:");
457            }
458            final ReactorManager reactorManager = new ReactorManager(new ArrayList<MavenProject>(projects));
459            for (final MavenProject project : reactorManager.getSortedProjects())
460            {
461                this.getLog().info("  " + project.getName());
462            }
463
464            final MavenSession projectSession =
465                new MavenSession(
466                    this.session.getContainer(),
467                    this.session.getSettings(),
468                    this.session.getLocalRepository(),
469                    this.session.getEventDispatcher(),
470                    reactorManager,
471                    goals,
472                    this.baseDirectory.toString(),
473                    this.executionProperties,
474                    this.session.getStartTime());
475
476            projectSession.setUsingPOMsFromFilesystem(true);
477            this.lifecycleExecutor.execute(
478                projectSession,
479                reactorManager,
480                projectSession.getEventDispatcher());
481        }
482        catch (final Throwable throwable)
483        {
484            throw new MojoExecutionException("An error occurred while attempting to execute projects", throwable);
485        }
486    }
487
488    /**
489     * Collects all project modules to execute.
490     *
491     * @param modules The list of modules to execute.
492     * @return the Map of collected projects (the key is the project, the value
493     *         the goals).
494     * @throws MojoExecutionException
495     */
496    private Map<MavenProject, List<String>> collectProjects(final String modules)
497        throws MojoExecutionException
498    {
499        final Map<MavenProject, List<String>> projects = new LinkedHashMap<MavenProject, List<String>>();
500        final Map<File, List<String>> poms = getModulePoms(modules);
501
502        if (!poms.isEmpty())
503        {
504            for (final File pom : poms.keySet())
505            {
506                try
507                {
508                    final MavenProject project = ProjectUtils.getProject(
509                            this.projectBuilder,
510                            this.session,
511                            pom,
512                            this.getLog());
513                    if (project != null)
514                    {
515                        if (this.getLog().isDebugEnabled())
516                        {
517                            this.getLog().debug("Adding project " + project.getId());
518                        }
519                        projects.put(
520                            project,
521                            poms.get(pom));
522                    }
523                    else
524                    {
525                        if (this.getLog().isWarnEnabled())
526                        {
527                            this.getLog().warn("Could not load project from pom: " + pom + " - ignoring");
528                        }
529                    }
530                }
531                catch (ProjectBuildingException exception)
532                {
533                    throw new MojoExecutionException("Error loading POM --> '" + pom + '\'', exception);
534                }
535            }
536        }
537        return projects;
538    }
539
540    /**
541     * Gets all POMs for the modules specified.
542     *
543     * @param moduleList the list of modules to execute.
544     * @return the list of module poms
545     */
546    private Map<File, List<String>> getModulePoms(final String moduleList)
547    {
548        final Map<File, List<String>> poms = new LinkedHashMap<File, List<String>>();
549        final String[] modules = moduleList != null ? moduleList.split(",") : null;
550
551        final String goalPrefix = ":";
552        if (modules != null)
553        {
554            final int numberOfModules = modules.length;
555            for (int ctr = 0; ctr < numberOfModules; ctr++)
556            {
557                String module = modules[ctr].trim();
558                final List<String> goalsList = new ArrayList<String>();
559                if (module.contains(goalPrefix))
560                {
561                    final String[] goals = module.replaceAll(
562                            ".*(:\\[)|(\\])",
563                            "").split("\\+");
564                    if (goals != null)
565                    {
566                        final int numberOfGoals = goals.length;
567                        for (int ctr2 = 0; ctr2 < numberOfGoals; ctr2++)
568                        {
569                            final String goal = goals[ctr2].trim();
570                            goalsList.add(goal);
571                        }
572                    }
573                }
574                module = module.replaceAll(
575                        goalPrefix + "\\[.*\\]",
576                        "");
577                final File pom = new File(this.baseDirectory, module + "/pom.xml");
578                if (pom.isFile())
579                {
580                    poms.put(
581                        pom,
582                        goalsList);
583                }
584            }
585        }
586        return poms;
587    }
588}