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}