diff --git a/releaser-spring/src/main/java/releaser/internal/spring/BatchConfiguration.java b/releaser-spring/src/main/java/releaser/internal/spring/BatchConfiguration.java index a1cf7c13..65ec95d0 100644 --- a/releaser-spring/src/main/java/releaser/internal/spring/BatchConfiguration.java +++ b/releaser-spring/src/main/java/releaser/internal/spring/BatchConfiguration.java @@ -55,17 +55,17 @@ class BatchConfiguration { } @Bean - @ConditionalOnMissingBean - BuildReportHandler springBuildReportHandler(JobExplorer jobExplorer, - ConfigurableApplicationContext context) { - return new SpringBatchExecutionResultHandler(jobExplorer, context); + @ConditionalOnMissingBean(BuildReportHandler.class) + SpringBatchBuildReportHandler springBatchBuildReportHandler(JobExplorer jobExplorer) { + return new SpringBatchBuildReportHandler(jobExplorer); } @Bean - @ConditionalOnMissingBean - ExecutionResultHandler springBatchExecutionResultHandler(JobExplorer jobExplorer, + @ConditionalOnMissingBean(ExecutionResultHandler.class) + SpringBatchExecutionResultHandler springBatchExecutionResultHandler( + BuildReportHandler buildReportHandler, ConfigurableApplicationContext context) { - return new SpringBatchExecutionResultHandler(jobExplorer, context); + return new SpringBatchExecutionResultHandler(buildReportHandler, context); } @Bean diff --git a/releaser-spring/src/main/java/releaser/internal/spring/SpringBatchBuildReportHandler.java b/releaser-spring/src/main/java/releaser/internal/spring/SpringBatchBuildReportHandler.java new file mode 100644 index 00000000..1b2d4b5f --- /dev/null +++ b/releaser-spring/src/main/java/releaser/internal/spring/SpringBatchBuildReportHandler.java @@ -0,0 +1,187 @@ +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package releaser.internal.spring; + +import java.text.SimpleDateFormat; +import java.time.temporal.ChronoUnit; +import java.util.Arrays; +import java.util.Comparator; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import com.jakewharton.fliptables.FlipTableConverters; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import releaser.internal.tasks.TrainPostReleaseReleaserTask; + +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.explore.JobExplorer; +import org.springframework.batch.item.ExecutionContext; +import org.springframework.core.NestedExceptionUtils; +import org.springframework.util.StringUtils; + +class SpringBatchBuildReportHandler implements BuildReportHandler { + + private static final Logger log = LoggerFactory + .getLogger(SpringBatchExecutionResultHandler.class); + + private final JobExplorer jobExplorer; + + SpringBatchBuildReportHandler(JobExplorer jobExplorer) { + this.jobExplorer = jobExplorer; + } + + @Override + public void reportBuildSummary() { + List jobNames = this.jobExplorer.getJobNames(); + List sortedJobExecutions = jobNames.stream() + .flatMap(name -> this.jobExplorer.findJobInstancesByJobName(name, 0, 100) + .stream()) + .flatMap(instance -> this.jobExplorer.getJobExecutions(instance).stream()) + .filter(j -> !j.isRunning()) + .sorted(Comparator.comparing(JobExecution::getCreateTime)) + .collect(Collectors.toList()); + List stepContexts = sortedJobExecutions.stream() + .flatMap(j -> j.getStepExecutions().stream()) + .collect(Collectors.toCollection(LinkedList::new)); + printTable(buildTable(stepContexts)); + } + + private List buildTable(List stepContexts) { + return stepContexts.stream().map(step -> { + String date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS") + .format(step.getStartTime()); + long millis = ChronoUnit.MILLIS.between(step.getStartTime().toInstant(), + step.getEndTime().toInstant()); + ExecutionContext context = step.getExecutionContext(); + ExecutionResultReport entity = (ExecutionResultReport) context.get("entity"); + if (entity == null) { + return null; + } + String projectName = TrainPostReleaseReleaserTask.class + .isAssignableFrom(entity.getReleaserTaskType()) ? "postRelease" + : entity.getProjectName(); + return new Table(date, time(millis), projectName, entity.getShortName(), + entity.getDescription(), entity.getState(), entity.getExceptions()); + }).filter(Objects::nonNull).collect(Collectors.toCollection(LinkedList::new)); + } + + private String time(long millis) { + long minutes = (millis / 1000) / 60; + long seconds = (millis / 1000) % 60; + if (minutes == 0 && seconds == 0) { + return millis + " ms"; + } + else if (minutes == 0) { + return seconds + " s"; + } + return minutes + " min " + seconds + " s"; + } + + private void printTable(List
table) { + String string = "\n\n***** BUILD REPORT *****\n\n" + + FlipTableConverters.fromIterable(table, Table.class) + + "\n\n***** BUILD REPORT *****\n\n"; + List
brokenTasks = table.stream() + .filter(table1 -> StringUtils.hasText(table1.thrownException)) + .collect(Collectors.toList()); + if (!brokenTasks.isEmpty()) { + String brokenBuilds = "\n\n[BUILD UNSTABLE] The following release tasks are failing!\n\n" + + brokenTasks.stream().map(table1 -> String.format( + "***** Project / Task : <%s/%s> ***** \nTask Description <%s>\nException Stacktrace \n\n%s", + table1.projectName, table1.taskCaption, + table1.taskDescription, + table1.exceptions + "\n" + table1.exceptions.stream() + .map(Throwable::getStackTrace).flatMap(Arrays::stream) + .map(StackTraceElement::toString) + .collect(Collectors.joining("\n")))) + .collect(Collectors.joining("\n\n")); + log.warn(string + brokenBuilds); + } + else { + log.info(string); + } + } + + class Table { + + final String creationTime; + + final String executionTime; + + final String projectName; + + final String taskCaption; + + final String taskDescription; + + final String taskState; + + final String thrownException; + + List exceptions; + + Table(String creationTime, String executionTime, String projectName, + String taskCaption, String taskDescription, String taskState, + List exceptions) { + this.creationTime = creationTime; + this.executionTime = executionTime; + this.projectName = projectName; + this.taskCaption = taskCaption; + this.taskDescription = taskDescription; + this.taskState = taskState; + this.thrownException = exceptions == null ? "" : exceptions.stream() + // TODO: Last but most specific + .map(t -> NestedExceptionUtils.getMostSpecificCause(t).toString()) + .collect(Collectors.joining("\n")); + this.exceptions = exceptions; + } + + public String getExecutionTime() { + return this.executionTime; + } + + public String getCreationTime() { + return this.creationTime; + } + + public String getProjectName() { + return this.projectName; + } + + public String getTaskCaption() { + return this.taskCaption; + } + + public String getTaskDescription() { + return this.taskDescription; + } + + public String getTaskState() { + return this.taskState; + } + + public String getThrownException() { + return this.thrownException; + } + + } + +} diff --git a/releaser-spring/src/main/java/releaser/internal/spring/SpringBatchExecutionResultHandler.java b/releaser-spring/src/main/java/releaser/internal/spring/SpringBatchExecutionResultHandler.java index 060cfa2b..270b6d02 100644 --- a/releaser-spring/src/main/java/releaser/internal/spring/SpringBatchExecutionResultHandler.java +++ b/releaser-spring/src/main/java/releaser/internal/spring/SpringBatchExecutionResultHandler.java @@ -19,49 +19,32 @@ package releaser.internal.spring; import java.io.File; import java.io.IOException; import java.nio.file.Files; -import java.text.SimpleDateFormat; -import java.time.temporal.ChronoUnit; -import java.util.Arrays; -import java.util.Comparator; -import java.util.LinkedList; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; -import com.jakewharton.fliptables.FlipTableConverters; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import releaser.internal.tasks.TrainPostReleaseReleaserTask; import releaser.internal.tech.ExecutionResult; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.explore.JobExplorer; -import org.springframework.batch.item.ExecutionContext; import org.springframework.boot.SpringApplication; import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.core.NestedExceptionUtils; -import org.springframework.util.StringUtils; -class SpringBatchExecutionResultHandler - implements ExecutionResultHandler, BuildReportHandler { +class SpringBatchExecutionResultHandler implements ExecutionResultHandler { private static final Logger log = LoggerFactory .getLogger(SpringBatchExecutionResultHandler.class); - private final JobExplorer jobExplorer; - private final ConfigurableApplicationContext context; - SpringBatchExecutionResultHandler(JobExplorer jobExplorer, + private final BuildReportHandler buildReportHandler; + + SpringBatchExecutionResultHandler(BuildReportHandler buildReportHandler, ConfigurableApplicationContext context) { - this.jobExplorer = jobExplorer; + this.buildReportHandler = buildReportHandler; this.context = context; } @Override public void accept(ExecutionResult executionResult) { - reportBuildSummary(); + this.buildReportHandler.reportBuildSummary(); if (executionResult.isFailure()) { log.error("At least one failure occurred while running the release process", executionResult.foundExceptions()); @@ -87,78 +70,6 @@ class SpringBatchExecutionResultHandler System.exit(SpringApplication.exit(this.context, () -> 1)); } - @Override - public void reportBuildSummary() { - List jobNames = this.jobExplorer.getJobNames(); - List sortedJobExecutions = jobNames.stream() - .flatMap(name -> this.jobExplorer.findJobInstancesByJobName(name, 0, 100) - .stream()) - .flatMap(instance -> this.jobExplorer.getJobExecutions(instance).stream()) - .filter(j -> !j.isRunning()) - .sorted(Comparator.comparing(JobExecution::getCreateTime)) - .collect(Collectors.toList()); - List stepContexts = sortedJobExecutions.stream() - .flatMap(j -> j.getStepExecutions().stream()) - .collect(Collectors.toCollection(LinkedList::new)); - printTable(buildTable(stepContexts)); - } - - private List
buildTable(List stepContexts) { - return stepContexts.stream().map(step -> { - String date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS") - .format(step.getStartTime()); - long millis = ChronoUnit.MILLIS.between(step.getStartTime().toInstant(), - step.getEndTime().toInstant()); - ExecutionContext context = step.getExecutionContext(); - ExecutionResultReport entity = (ExecutionResultReport) context.get("entity"); - if (entity == null) { - return null; - } - String projectName = TrainPostReleaseReleaserTask.class - .isAssignableFrom(entity.getReleaserTaskType()) ? "postRelease" - : entity.getProjectName(); - return new Table(date, time(millis), projectName, entity.getShortName(), - entity.getDescription(), entity.getState(), entity.getExceptions()); - }).filter(Objects::nonNull).collect(Collectors.toCollection(LinkedList::new)); - } - - private String time(long millis) { - long minutes = (millis / 1000) / 60; - long seconds = (millis / 1000) % 60; - if (minutes == 0 && seconds == 0) { - return millis + " ms"; - } - else if (minutes == 0) { - return seconds + " s"; - } - return minutes + " min " + seconds + " s"; - } - - private void printTable(List
table) { - String string = "\n\n***** BUILD REPORT *****\n\n" - + FlipTableConverters.fromIterable(table, Table.class) - + "\n\n***** BUILD REPORT *****\n\n"; - List
brokenTasks = table.stream() - .filter(table1 -> StringUtils.hasText(table1.thrownException)) - .collect(Collectors.toList()); - if (!brokenTasks.isEmpty()) { - String brokenBuilds = "\n\n[BUILD UNSTABLE] The following release tasks are failing!\n\n" - + brokenTasks.stream().map(table1 -> String.format( - "***** Project / Task : <%s/%s> ***** \nTask Description <%s>\nException Stacktrace \n\n%s", - table1.projectName, table1.taskCaption, - table1.taskDescription, - table1.exceptions + "\n" + table1.exceptions.stream() - .map(Throwable::getStackTrace).flatMap(Arrays::stream) - .map(StackTraceElement::toString) - .collect(Collectors.joining("\n")))) - .collect(Collectors.joining("\n\n")); - log.warn(string + brokenBuilds); - } - else { - log.info(string); - } - } - // File creation required by Jenkins private void handleUnstableException() { File buildStatus = new File("build_status"); @@ -226,67 +137,3 @@ class SpringBatchExecutionResultHandler } } - -class Table { - - final String creationTime; - - final String executionTime; - - final String projectName; - - final String taskCaption; - - final String taskDescription; - - final String taskState; - - final String thrownException; - - List exceptions; - - Table(String creationTime, String executionTime, String projectName, - String taskCaption, String taskDescription, String taskState, - List exceptions) { - this.creationTime = creationTime; - this.executionTime = executionTime; - this.projectName = projectName; - this.taskCaption = taskCaption; - this.taskDescription = taskDescription; - this.taskState = taskState; - this.thrownException = exceptions == null ? "" : exceptions.stream() - // TODO: Last but most specific - .map(t -> NestedExceptionUtils.getMostSpecificCause(t).toString()) - .collect(Collectors.joining("\n")); - this.exceptions = exceptions; - } - - public String getExecutionTime() { - return this.executionTime; - } - - public String getCreationTime() { - return this.creationTime; - } - - public String getProjectName() { - return this.projectName; - } - - public String getTaskCaption() { - return this.taskCaption; - } - - public String getTaskDescription() { - return this.taskDescription; - } - - public String getTaskState() { - return this.taskState; - } - - public String getThrownException() { - return this.thrownException; - } - -} diff --git a/releaser-test/src/main/java/releaser/internal/spring/AbstractSpringAcceptanceTests.java b/releaser-test/src/main/java/releaser/internal/spring/AbstractSpringAcceptanceTests.java index 13d297da..fc4ddef3 100644 --- a/releaser-test/src/main/java/releaser/internal/spring/AbstractSpringAcceptanceTests.java +++ b/releaser-test/src/main/java/releaser/internal/spring/AbstractSpringAcceptanceTests.java @@ -57,7 +57,6 @@ import releaser.internal.template.TemplateGenerator; import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; -import org.springframework.batch.core.explore.JobExplorer; import org.springframework.batch.core.launch.JobLauncher; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.builder.SpringApplicationBuilder; @@ -352,9 +351,9 @@ public abstract class AbstractSpringAcceptanceTests { public boolean exitedWithException; - public TestExecutionResultHandler(JobExplorer jobExplorer, + public TestExecutionResultHandler(BuildReportHandler buildReportHandler, ConfigurableApplicationContext context) { - super(jobExplorer, context); + super(buildReportHandler, context); } @Override @@ -401,9 +400,10 @@ public abstract class AbstractSpringAcceptanceTests { } @Bean - TestExecutionResultHandler testExecutionResultHandler(JobExplorer explorer, + TestExecutionResultHandler testExecutionResultHandler( + BuildReportHandler buildReportHandler, ConfigurableApplicationContext context) { - return new TestExecutionResultHandler(explorer, context); + return new TestExecutionResultHandler(buildReportHandler, context); } }