1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.cling.invoker.mvn;
20
21 import java.io.FileNotFoundException;
22 import java.nio.file.Files;
23 import java.nio.file.Path;
24 import java.util.ArrayList;
25 import java.util.HashMap;
26 import java.util.LinkedHashMap;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.regex.Matcher;
30 import java.util.regex.Pattern;
31
32 import org.apache.maven.InternalErrorException;
33 import org.apache.maven.Maven;
34 import org.apache.maven.api.Constants;
35 import org.apache.maven.api.MonotonicClock;
36 import org.apache.maven.api.cli.InvokerRequest;
37 import org.apache.maven.api.cli.Logger;
38 import org.apache.maven.api.cli.mvn.MavenOptions;
39 import org.apache.maven.api.services.BuilderProblem;
40 import org.apache.maven.api.services.Lookup;
41 import org.apache.maven.api.services.SettingsBuilderRequest;
42 import org.apache.maven.api.services.SettingsBuilderResult;
43 import org.apache.maven.api.services.Source;
44 import org.apache.maven.api.services.ToolchainsBuilder;
45 import org.apache.maven.api.services.ToolchainsBuilderRequest;
46 import org.apache.maven.api.services.ToolchainsBuilderResult;
47 import org.apache.maven.api.services.model.ModelProcessor;
48 import org.apache.maven.cling.event.ExecutionEventLogger;
49 import org.apache.maven.cling.invoker.LookupInvoker;
50 import org.apache.maven.cling.invoker.ProtoLookup;
51 import org.apache.maven.cling.invoker.Utils;
52 import org.apache.maven.cling.transfer.ConsoleMavenTransferListener;
53 import org.apache.maven.cling.transfer.QuietMavenTransferListener;
54 import org.apache.maven.cling.transfer.SimplexTransferListener;
55 import org.apache.maven.cling.transfer.Slf4jMavenTransferListener;
56 import org.apache.maven.cling.utils.CLIReportingUtils;
57 import org.apache.maven.eventspy.internal.EventSpyDispatcher;
58 import org.apache.maven.exception.DefaultExceptionHandler;
59 import org.apache.maven.exception.ExceptionHandler;
60 import org.apache.maven.exception.ExceptionSummary;
61 import org.apache.maven.execution.DefaultMavenExecutionRequest;
62 import org.apache.maven.execution.ExecutionListener;
63 import org.apache.maven.execution.MavenExecutionRequest;
64 import org.apache.maven.execution.MavenExecutionRequestPopulator;
65 import org.apache.maven.execution.MavenExecutionResult;
66 import org.apache.maven.execution.ProfileActivation;
67 import org.apache.maven.execution.ProjectActivation;
68 import org.apache.maven.jline.MessageUtils;
69 import org.apache.maven.lifecycle.LifecycleExecutionException;
70 import org.apache.maven.logging.LoggingExecutionListener;
71 import org.apache.maven.logging.MavenTransferListener;
72 import org.apache.maven.project.MavenProject;
73 import org.codehaus.plexus.PlexusContainer;
74 import org.eclipse.aether.DefaultRepositoryCache;
75 import org.eclipse.aether.transfer.TransferListener;
76
77 import static java.util.Comparator.comparing;
78 import static org.apache.maven.cling.invoker.Utils.toProperties;
79
80
81
82
83
84
85 public abstract class MavenInvoker<C extends MavenContext> extends LookupInvoker<C> {
86 public MavenInvoker(ProtoLookup protoLookup) {
87 super(protoLookup);
88 }
89
90 @Override
91 protected int execute(C context) throws Exception {
92 MavenExecutionRequest request = prepareMavenExecutionRequest();
93 toolchains(context, request);
94 populateRequest(context, context.lookup, request);
95 return doExecute(context, request);
96 }
97
98 protected MavenExecutionRequest prepareMavenExecutionRequest() throws Exception {
99
100 DefaultMavenExecutionRequest mavenExecutionRequest = new DefaultMavenExecutionRequest();
101 mavenExecutionRequest.setRepositoryCache(new DefaultRepositoryCache());
102 mavenExecutionRequest.setInteractiveMode(true);
103 mavenExecutionRequest.setCacheTransferError(false);
104 mavenExecutionRequest.setIgnoreInvalidArtifactDescriptor(true);
105 mavenExecutionRequest.setIgnoreMissingArtifactDescriptor(true);
106 mavenExecutionRequest.setRecursive(true);
107 mavenExecutionRequest.setReactorFailureBehavior(MavenExecutionRequest.REACTOR_FAIL_FAST);
108 mavenExecutionRequest.setStartInstant(MonotonicClock.now());
109 mavenExecutionRequest.setLoggingLevel(MavenExecutionRequest.LOGGING_LEVEL_INFO);
110 mavenExecutionRequest.setDegreeOfConcurrency(1);
111 mavenExecutionRequest.setBuilderId("singlethreaded");
112 return mavenExecutionRequest;
113 }
114
115 @Override
116 protected void lookup(C context) throws Exception {
117 context.eventSpyDispatcher = context.lookup.lookup(EventSpyDispatcher.class);
118 context.maven = context.lookup.lookup(Maven.class);
119 }
120
121 @Override
122 protected void init(C context) throws Exception {
123 super.init(context);
124 InvokerRequest invokerRequest = context.invokerRequest;
125 Map<String, Object> data = new HashMap<>();
126 data.put("plexus", context.lookup.lookup(PlexusContainer.class));
127 data.put("workingDirectory", invokerRequest.cwd().toString());
128 data.put("systemProperties", toProperties(context.protoSession.getSystemProperties()));
129 data.put("userProperties", toProperties(context.protoSession.getUserProperties()));
130 data.put("versionProperties", CLIReportingUtils.getBuildProperties());
131 context.eventSpyDispatcher.init(() -> data);
132 }
133
134 @Override
135 protected void postCommands(C context) throws Exception {
136 super.postCommands(context);
137
138 InvokerRequest invokerRequest = context.invokerRequest;
139 MavenOptions options = (MavenOptions) invokerRequest.options();
140 Logger logger = context.logger;
141 if (options.relaxedChecksums().orElse(false)) {
142 logger.info("Disabling strict checksum verification on all artifact downloads.");
143 } else if (options.strictChecksums().orElse(false)) {
144 logger.info("Enabling strict checksum verification on all artifact downloads.");
145 }
146 }
147
148 @Override
149 protected void customizeSettingsRequest(C context, SettingsBuilderRequest settingsBuilderRequest) throws Exception {
150 if (context.eventSpyDispatcher != null) {
151 context.eventSpyDispatcher.onEvent(settingsBuilderRequest);
152 }
153 }
154
155 @Override
156 protected void customizeSettingsResult(C context, SettingsBuilderResult settingsBuilderResult) throws Exception {
157 if (context.eventSpyDispatcher != null) {
158 context.eventSpyDispatcher.onEvent(settingsBuilderResult);
159 }
160 }
161
162 protected void toolchains(C context, MavenExecutionRequest request) throws Exception {
163 Path userToolchainsFile = null;
164 if (context.invokerRequest.options().altUserToolchains().isPresent()) {
165 userToolchainsFile = context.cwdResolver.apply(
166 context.invokerRequest.options().altUserToolchains().get());
167
168 if (!Files.isRegularFile(userToolchainsFile)) {
169 throw new FileNotFoundException(
170 "The specified user toolchains file does not exist: " + userToolchainsFile);
171 }
172 } else {
173 String userToolchainsFileStr =
174 context.protoSession.getUserProperties().get(Constants.MAVEN_USER_TOOLCHAINS);
175 if (userToolchainsFileStr != null) {
176 userToolchainsFile = context.cwdResolver.apply(userToolchainsFileStr);
177 }
178 }
179
180 Path installationToolchainsFile = null;
181 if (context.invokerRequest.options().altInstallationToolchains().isPresent()) {
182 installationToolchainsFile = context.cwdResolver.apply(
183 context.invokerRequest.options().altInstallationToolchains().get());
184
185 if (!Files.isRegularFile(installationToolchainsFile)) {
186 throw new FileNotFoundException(
187 "The specified installation toolchains file does not exist: " + installationToolchainsFile);
188 }
189 } else {
190 String installationToolchainsFileStr =
191 context.protoSession.getUserProperties().get(Constants.MAVEN_INSTALLATION_TOOLCHAINS);
192 if (installationToolchainsFileStr != null) {
193 installationToolchainsFile = context.cwdResolver.apply(installationToolchainsFileStr);
194 }
195 }
196
197 request.setInstallationToolchainsFile(
198 installationToolchainsFile != null ? installationToolchainsFile.toFile() : null);
199 request.setUserToolchainsFile(userToolchainsFile != null ? userToolchainsFile.toFile() : null);
200
201 ToolchainsBuilderRequest toolchainsRequest = ToolchainsBuilderRequest.builder()
202 .session(context.protoSession)
203 .installationToolchainsSource(
204 installationToolchainsFile != null && Files.isRegularFile(installationToolchainsFile)
205 ? Source.fromPath(installationToolchainsFile)
206 : null)
207 .userToolchainsSource(
208 userToolchainsFile != null && Files.isRegularFile(userToolchainsFile)
209 ? Source.fromPath(userToolchainsFile)
210 : null)
211 .build();
212
213 context.eventSpyDispatcher.onEvent(toolchainsRequest);
214
215 context.logger.debug("Reading installation toolchains from '" + installationToolchainsFile + "'");
216 context.logger.debug("Reading user toolchains from '" + userToolchainsFile + "'");
217
218 ToolchainsBuilderResult toolchainsResult =
219 context.lookup.lookup(ToolchainsBuilder.class).build(toolchainsRequest);
220
221 context.eventSpyDispatcher.onEvent(toolchainsResult);
222
223 context.lookup
224 .lookup(MavenExecutionRequestPopulator.class)
225 .populateFromToolchains(
226 request,
227 new org.apache.maven.toolchain.model.PersistedToolchains(
228 toolchainsResult.getEffectiveToolchains()));
229
230 if (!toolchainsResult.getProblems().isEmpty()) {
231 context.logger.warn("");
232 context.logger.warn("Some problems were encountered while building the effective toolchains");
233
234 for (BuilderProblem problem : toolchainsResult.getProblems()) {
235 context.logger.warn(problem.getMessage() + " @ " + problem.getLocation());
236 }
237
238 context.logger.warn("");
239 }
240 }
241
242 @Override
243 protected void populateRequest(C context, Lookup lookup, MavenExecutionRequest request) throws Exception {
244 super.populateRequest(context, lookup, request);
245 if (context.invokerRequest.rootDirectory().isEmpty()) {
246
247 request.setMultiModuleProjectDirectory(
248 context.invokerRequest.topDirectory().toFile());
249 request.setRootDirectory(context.invokerRequest.topDirectory());
250 }
251
252 MavenOptions options = (MavenOptions) context.invokerRequest.options();
253 request.setNoSnapshotUpdates(options.suppressSnapshotUpdates().orElse(false));
254 request.setGoals(options.goals().orElse(List.of()));
255 request.setReactorFailureBehavior(determineReactorFailureBehaviour(context));
256 request.setRecursive(!options.nonRecursive().orElse(!request.isRecursive()));
257 request.setOffline(options.offline().orElse(request.isOffline()));
258 request.setUpdateSnapshots(options.updateSnapshots().orElse(false));
259 request.setGlobalChecksumPolicy(determineGlobalChecksumPolicy(context));
260
261 Path pom = determinePom(context, lookup);
262 if (pom != null) {
263 request.setPom(pom.toFile());
264 if (pom.getParent() != null) {
265 request.setBaseDirectory(pom.getParent().toFile());
266 }
267
268
269 if (context.invokerRequest.rootDirectory().isEmpty()) {
270 Path rootDirectory = Utils.findMandatoryRoot(context.invokerRequest.topDirectory());
271 request.setMultiModuleProjectDirectory(rootDirectory.toFile());
272 request.setRootDirectory(rootDirectory);
273 }
274 }
275
276 request.setTransferListener(
277 determineTransferListener(context, options.noTransferProgress().orElse(false)));
278 request.setExecutionListener(determineExecutionListener(context));
279
280 request.setResumeFrom(options.resumeFrom().orElse(null));
281 request.setResume(options.resume().orElse(false));
282 request.setMakeBehavior(determineMakeBehavior(context));
283 request.setCacheNotFound(options.cacheArtifactNotFound().orElse(true));
284 request.setCacheTransferError(false);
285
286 if (options.strictArtifactDescriptorPolicy().orElse(false)) {
287 request.setIgnoreMissingArtifactDescriptor(false);
288 request.setIgnoreInvalidArtifactDescriptor(false);
289 } else {
290 request.setIgnoreMissingArtifactDescriptor(true);
291 request.setIgnoreInvalidArtifactDescriptor(true);
292 }
293
294 request.setIgnoreTransitiveRepositories(
295 options.ignoreTransitiveRepositories().orElse(false));
296
297 performProjectActivation(context, request.getProjectActivation());
298 performProfileActivation(context, request.getProfileActivation());
299
300
301
302
303
304
305
306
307
308 if (options.threads().isPresent()) {
309 int degreeOfConcurrency =
310 calculateDegreeOfConcurrency(options.threads().get());
311 if (degreeOfConcurrency > 1) {
312 request.setBuilderId("multithreaded");
313 request.setDegreeOfConcurrency(degreeOfConcurrency);
314 }
315 }
316
317
318
319
320 if (options.builder().isPresent()) {
321 request.setBuilderId(options.builder().get());
322 }
323 }
324
325 protected Path determinePom(C context, Lookup lookup) {
326 Path current = context.invokerRequest.cwd();
327 MavenOptions options = (MavenOptions) context.invokerRequest.options();
328 if (options.alternatePomFile().isPresent()) {
329 current = context.cwdResolver.apply(options.alternatePomFile().get());
330 }
331 ModelProcessor modelProcessor =
332 lookup.lookupOptional(ModelProcessor.class).orElse(null);
333 if (modelProcessor != null) {
334 return modelProcessor.locateExistingPom(current);
335 } else {
336 return Files.isRegularFile(current) ? current : null;
337 }
338 }
339
340 protected String determineReactorFailureBehaviour(C context) {
341 MavenOptions mavenOptions = (MavenOptions) context.invokerRequest.options();
342 if (mavenOptions.failFast().isPresent()) {
343 return MavenExecutionRequest.REACTOR_FAIL_FAST;
344 } else if (mavenOptions.failAtEnd().isPresent()) {
345 return MavenExecutionRequest.REACTOR_FAIL_AT_END;
346 } else if (mavenOptions.failNever().isPresent()) {
347 return MavenExecutionRequest.REACTOR_FAIL_NEVER;
348 } else {
349 return MavenExecutionRequest.REACTOR_FAIL_FAST;
350 }
351 }
352
353 protected String determineGlobalChecksumPolicy(C context) {
354 MavenOptions mavenOptions = (MavenOptions) context.invokerRequest.options();
355 if (mavenOptions.strictChecksums().orElse(false)) {
356 return MavenExecutionRequest.CHECKSUM_POLICY_FAIL;
357 } else if (mavenOptions.relaxedChecksums().orElse(false)) {
358 return MavenExecutionRequest.CHECKSUM_POLICY_WARN;
359 } else {
360 return null;
361 }
362 }
363
364 protected ExecutionListener determineExecutionListener(C context) {
365 ExecutionListener listener = new ExecutionEventLogger(context.invokerRequest.messageBuilderFactory());
366 if (context.eventSpyDispatcher != null) {
367 listener = context.eventSpyDispatcher.chainListener(listener);
368 }
369 return new LoggingExecutionListener(listener, determineBuildEventListener(context));
370 }
371
372 protected TransferListener determineTransferListener(C context, boolean noTransferProgress) {
373 boolean quiet = context.invokerRequest.options().quiet().orElse(false);
374 boolean logFile = context.invokerRequest.options().logFile().isPresent();
375 boolean runningOnCI = isRunningOnCI(context);
376 boolean quietCI = runningOnCI
377 && !context.invokerRequest.options().forceInteractive().orElse(false);
378
379 TransferListener delegate;
380 if (quiet || noTransferProgress || quietCI) {
381 delegate = new QuietMavenTransferListener();
382 } else if (context.interactive && !logFile) {
383 delegate = new SimplexTransferListener(new ConsoleMavenTransferListener(
384 context.invokerRequest.messageBuilderFactory(),
385 context.terminal.writer(),
386 context.invokerRequest.options().verbose().orElse(false)));
387 } else {
388 delegate = new Slf4jMavenTransferListener();
389 }
390 return new MavenTransferListener(delegate, determineBuildEventListener(context));
391 }
392
393 protected String determineMakeBehavior(C context) {
394 MavenOptions mavenOptions = (MavenOptions) context.invokerRequest.options();
395 if (mavenOptions.alsoMake().isPresent()
396 && mavenOptions.alsoMakeDependents().isEmpty()) {
397 return MavenExecutionRequest.REACTOR_MAKE_UPSTREAM;
398 } else if (mavenOptions.alsoMake().isEmpty()
399 && mavenOptions.alsoMakeDependents().isPresent()) {
400 return MavenExecutionRequest.REACTOR_MAKE_DOWNSTREAM;
401 } else if (mavenOptions.alsoMake().isPresent()
402 && mavenOptions.alsoMakeDependents().isPresent()) {
403 return MavenExecutionRequest.REACTOR_MAKE_BOTH;
404 } else {
405 return null;
406 }
407 }
408
409 protected void performProjectActivation(C context, ProjectActivation projectActivation) {
410 MavenOptions mavenOptions = (MavenOptions) context.invokerRequest.options();
411 if (mavenOptions.projects().isPresent()
412 && !mavenOptions.projects().get().isEmpty()) {
413 List<String> optionValues = mavenOptions.projects().get();
414 for (final String optionValue : optionValues) {
415 for (String token : optionValue.split(",")) {
416 String selector = token.trim();
417 boolean active = true;
418 if (!selector.isEmpty()) {
419 if (selector.charAt(0) == '-' || selector.charAt(0) == '!') {
420 active = false;
421 selector = selector.substring(1);
422 } else if (token.charAt(0) == '+') {
423 selector = selector.substring(1);
424 }
425 }
426 boolean optional = false;
427 if (!selector.isEmpty() && selector.charAt(0) == '?') {
428 optional = true;
429 selector = selector.substring(1);
430 }
431 projectActivation.addProjectActivation(selector, active, optional);
432 }
433 }
434 }
435 }
436
437 protected void performProfileActivation(C context, ProfileActivation profileActivation) {
438 MavenOptions mavenOptions = (MavenOptions) context.invokerRequest.options();
439 if (mavenOptions.activatedProfiles().isPresent()
440 && !mavenOptions.activatedProfiles().get().isEmpty()) {
441 List<String> optionValues = mavenOptions.activatedProfiles().get();
442 for (final String optionValue : optionValues) {
443 for (String token : optionValue.split(",")) {
444 String profileId = token.trim();
445 boolean active = true;
446 if (!profileId.isEmpty()) {
447 if (profileId.charAt(0) == '-' || profileId.charAt(0) == '!') {
448 active = false;
449 profileId = profileId.substring(1);
450 } else if (token.charAt(0) == '+') {
451 profileId = profileId.substring(1);
452 }
453 }
454 boolean optional = false;
455 if (!profileId.isEmpty() && profileId.charAt(0) == '?') {
456 optional = true;
457 profileId = profileId.substring(1);
458 }
459 profileActivation.addProfileActivation(profileId, active, optional);
460 }
461 }
462 }
463 }
464
465 protected int doExecute(C context, MavenExecutionRequest request) throws Exception {
466 context.eventSpyDispatcher.onEvent(request);
467
468 MavenExecutionResult result;
469 try {
470 result = context.maven.execute(request);
471 context.eventSpyDispatcher.onEvent(result);
472 } finally {
473 context.eventSpyDispatcher.close();
474 }
475
476 if (result.hasExceptions()) {
477 ExceptionHandler handler = new DefaultExceptionHandler();
478 Map<String, String> references = new LinkedHashMap<>();
479 List<MavenProject> failedProjects = new ArrayList<>();
480
481 for (Throwable exception : result.getExceptions()) {
482 ExceptionSummary summary = handler.handleException(exception);
483 logSummary(context, summary, references, "");
484
485 if (exception instanceof LifecycleExecutionException) {
486 failedProjects.add(((LifecycleExecutionException) exception).getProject());
487 }
488 }
489
490 context.logger.error("");
491
492 if (!context.invokerRequest.options().showErrors().orElse(false)) {
493 context.logger.error("To see the full stack trace of the errors, re-run Maven with the '"
494 + MessageUtils.builder().strong("-e") + "' switch");
495 }
496 if (!context.invokerRequest.options().verbose().orElse(false)) {
497 context.logger.error("Re-run Maven using the '"
498 + MessageUtils.builder().strong("-X") + "' switch to enable verbose output");
499 }
500
501 if (!references.isEmpty()) {
502 context.logger.error("");
503 context.logger.error("For more information about the errors and possible solutions"
504 + ", please read the following articles:");
505
506 for (Map.Entry<String, String> entry : references.entrySet()) {
507 context.logger.error(MessageUtils.builder().strong(entry.getValue()) + " " + entry.getKey());
508 }
509 }
510
511 if (result.canResume()) {
512 logBuildResumeHint(context, "mvn [args] -r");
513 } else if (!failedProjects.isEmpty()) {
514 List<MavenProject> sortedProjects = result.getTopologicallySortedProjects();
515
516
517 failedProjects.sort(comparing(sortedProjects::indexOf));
518
519 MavenProject firstFailedProject = failedProjects.get(0);
520 if (!firstFailedProject.equals(sortedProjects.get(0))) {
521 String resumeFromSelector = getResumeFromSelector(sortedProjects, firstFailedProject);
522 logBuildResumeHint(context, "mvn [args] -rf " + resumeFromSelector);
523 }
524 }
525
526 if (((MavenOptions) context.invokerRequest.options()).failNever().orElse(false)) {
527 context.logger.info("Build failures were ignored.");
528 return 0;
529 } else {
530 return 1;
531 }
532 } else {
533 return 0;
534 }
535 }
536
537 protected void logBuildResumeHint(C context, String resumeBuildHint) {
538 context.logger.error("");
539 context.logger.error("After correcting the problems, you can resume the build with the command");
540 context.logger.error(
541 MessageUtils.builder().a(" ").strong(resumeBuildHint).toString());
542 }
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561 protected String getResumeFromSelector(List<MavenProject> mavenProjects, MavenProject firstFailedProject) {
562 boolean hasOverlappingArtifactId = mavenProjects.stream()
563 .filter(project -> firstFailedProject.getArtifactId().equals(project.getArtifactId()))
564 .count()
565 > 1;
566
567 if (hasOverlappingArtifactId) {
568 return firstFailedProject.getGroupId() + ":" + firstFailedProject.getArtifactId();
569 }
570
571 return ":" + firstFailedProject.getArtifactId();
572 }
573
574 protected static final Pattern NEXT_LINE = Pattern.compile("\r?\n");
575
576 protected static final Pattern LAST_ANSI_SEQUENCE = Pattern.compile("(\u001B\\[[;\\d]*[ -/]*[@-~])[^\u001B]*$");
577
578 protected static final String ANSI_RESET = "\u001B\u005Bm";
579
580 protected void logSummary(C context, ExceptionSummary summary, Map<String, String> references, String indent) {
581 String referenceKey = "";
582
583 if (summary.getReference() != null && !summary.getReference().isEmpty()) {
584 referenceKey =
585 references.computeIfAbsent(summary.getReference(), k -> "[Help " + (references.size() + 1) + "]");
586 }
587
588 String msg = summary.getMessage();
589
590 if (!referenceKey.isEmpty()) {
591 if (msg.indexOf('\n') < 0) {
592 msg += " -> " + MessageUtils.builder().strong(referenceKey);
593 } else {
594 msg += "\n-> " + MessageUtils.builder().strong(referenceKey);
595 }
596 }
597
598 String[] lines = NEXT_LINE.split(msg);
599 String currentColor = "";
600
601 for (int i = 0; i < lines.length; i++) {
602
603 String line = currentColor + lines[i];
604
605
606 Matcher matcher = LAST_ANSI_SEQUENCE.matcher(line);
607 String nextColor = "";
608 if (matcher.find()) {
609 nextColor = matcher.group(1);
610 if (ANSI_RESET.equals(nextColor)) {
611
612 nextColor = "";
613 }
614 }
615
616
617 line = indent + line + ("".equals(nextColor) ? "" : ANSI_RESET);
618
619 if ((i == lines.length - 1)
620 && (context.invokerRequest.options().showErrors().orElse(false)
621 || (summary.getException() instanceof InternalErrorException))) {
622 context.logger.error(line, summary.getException());
623 } else {
624 context.logger.error(line);
625 }
626
627 currentColor = nextColor;
628 }
629
630 indent += " ";
631
632 for (ExceptionSummary child : summary.getChildren()) {
633 logSummary(context, child, references, indent);
634 }
635 }
636 }