#99 - Polishing.

Avoid CGlib subclasses for configuration classes. Move ExecutorService to top-level config. Use lazy HTTP client creation to speed up test bootstrap.
This commit is contained in:
Mark Paluch
2020-11-12 10:02:47 +01:00
parent acca880b0f
commit 352e155e9f
13 changed files with 267 additions and 167 deletions

View File

@@ -30,10 +30,10 @@ import org.xmlbeam.config.DefaultXMLFactoriesConfig.NamespacePhilosophy;
/**
* Spring configuration for build related components.
*
*
* @author Oliver Gierke
*/
@Configuration
@Configuration(proxyBeanMethods = false)
class BuildConfiguration {
@Bean

View File

@@ -18,19 +18,14 @@ package org.springframework.data.release.build;
import lombok.NonNull;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.function.Supplier;
import java.util.stream.Collector;
@@ -66,12 +61,7 @@ class BuildExecutor {
this.buildSystems = buildSystems;
this.mavenProperties = mavenProperties;
if (this.mavenProperties.isParallelize()) {
this.executor = buildExecutor;
} else {
this.executor = ImmediateExecutorService.INSTANCE;
}
this.executor = buildExecutor;
}
@PreDestroy
@@ -270,82 +260,4 @@ class BuildExecutor {
}
}
enum ImmediateExecutorService implements ExecutorService {
INSTANCE;
@Override
public void shutdown() {
}
@Override
public List<Runnable> shutdownNow() {
return Collections.emptyList();
}
@Override
public boolean isShutdown() {
return false;
}
@Override
public boolean isTerminated() {
return false;
}
@Override
public boolean awaitTermination(long timeout, TimeUnit unit) {
return false;
}
@Override
public <T> Future<T> submit(Callable<T> task) {
try {
return CompletableFuture.completedFuture(task.call());
} catch (Exception e) {
CompletableFuture<T> f = new CompletableFuture<>();
f.completeExceptionally(e);
return f;
}
}
@Override
public <T> Future<T> submit(Runnable task, T result) {
return submit(() -> {
task.run();
return result;
});
}
@Override
public Future<?> submit(Runnable task) {
return submit(task, null);
}
@Override
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) {
throw new UnsupportedOperationException();
}
@Override
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) {
throw new UnsupportedOperationException();
}
@Override
public <T> T invokeAny(Collection<? extends Callable<T>> tasks) {
throw new UnsupportedOperationException();
}
@Override
public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) {
throw new UnsupportedOperationException();
}
@Override
public void execute(Runnable command) {
submit(command);
}
}
}

View File

@@ -73,7 +73,7 @@ class ReleaseCommands extends TimedCommand {
git.prepare(iteration);
// rebuild.runPreReleaseChecks(iteration);
build.runPreReleaseChecks(iteration);
misc.prepareChangelogs(iteration);
misc.updateResources(iteration);

View File

@@ -33,17 +33,17 @@ import org.springframework.web.client.RestTemplate;
/**
* Configuration to set up deployment components.
*
*
* @author Oliver Gierke
*/
@Configuration
@Configuration(proxyBeanMethods = false)
class DeploymentConfiguration {
@Autowired DeploymentProperties properties;
@Bean
public ArtifactoryClient client(Logger logger) {
return new ArtifactoryClient(artifactoryRestTemplate(), logger, properties);
public ArtifactoryClient client(Logger logger, RestTemplate artifactoryRestTemplate) {
return new ArtifactoryClient(artifactoryRestTemplate, logger, properties);
}
@Bean
@@ -60,7 +60,7 @@ class DeploymentConfiguration {
private final @NonNull DeploymentProperties properties;
/*
/*
* (non-Javadoc)
* @see org.springframework.http.client.ClientHttpRequestInterceptor#intercept(org.springframework.http.HttpRequest, byte[], org.springframework.http.client.ClientHttpRequestExecution)
*/

View File

@@ -27,8 +27,14 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.data.release.issues.TicketReference;
import org.springframework.lang.Nullable;
/**
* Value object representing a parsed commit message. The {@link #parse(String)} method inspects a commit message to
* extract a {@link TicketReference}, related tickets, a pull request reference and summary/body from the commit. Commit
* messages may used {@code &lt;ticket&gt; - summary} syntax for Jira and GitHub tickets (gh- and # notation). This
* parser also supports {@code Original pull request}, {@code Related ticket} and GitHub close keywords.
*
* @author Mark Paluch
*/
@Getter
@@ -54,38 +60,103 @@ class ParsedCommitMessage {
"Related (?>tickets|ticket):(?>\\s+)?((" + A_TICKET.pattern() + "(?>[\\s,]*))+)", Pattern.CASE_INSENSITIVE);
private final String summary;
private final String body;
private final @Nullable String body;
private final TicketReference ticketReference;
private final TicketReference pullRequestReference;
private final List<TicketReference> relatedTickets;
private ParsedCommitMessage(String summary, String body, TicketReference ticketReference,
TicketReference pullRequestReference, List<TicketReference> relatedTickets) {
private ParsedCommitMessage(String summary, @Nullable String body) {
this.summary = summary;
this.body = body;
TicketReference ticketReference = null;
TicketReference pullRequestReference = null;
// DATACASS-nnn - syntax
Optional<TicketReference> jiraTicket = tryParseJiraTicketReference(summary);
if (jiraTicket.isPresent()) {
ticketReference = jiraTicket.get();
}
// Closes (gh-nnn|#nnn) syntax
Matcher gitHubMatcher = GITHUB_CLOSE_SYNTAX.matcher(summary + "\n" + body);
// #nnn syntax
Optional<TicketReference> gitHubTicket = tryParseGitHubTicketReference(summary);
if (gitHubTicket.isPresent()) {
ticketReference = gitHubTicket.get();
} else {
if (gitHubMatcher.find()) {
ticketReference = new TicketReference(gitHubMatcher.group(1), summary, TicketReference.Style.GitHub);
}
}
List<TicketReference> relatedTickets = parseRelatedTickets(body, gitHubMatcher);
Optional<TicketReference> optionalOriginalPr = parsePullRequestReference(body);
if (optionalOriginalPr.isPresent()) {
pullRequestReference = optionalOriginalPr.get();
if (ticketReference == null) {
ticketReference = pullRequestReference;
pullRequestReference = null;
}
}
this.ticketReference = ticketReference;
this.pullRequestReference = pullRequestReference;
this.relatedTickets = relatedTickets;
}
/**
* Parse a commit message into {@link ParsedCommitMessage}.
*
* @param message
* @return
*/
public static ParsedCommitMessage parse(String message) {
int lineBreak = message.indexOf('\n');
String summary = null;
String body = null;
TicketReference ticketReference = null;
TicketReference pullRequestReference = null;
String summary;
String body;
if (lineBreak > -1) {
summary = message.substring(0, lineBreak).trim();
body = message.substring(lineBreak + 1).trim();
} else {
summary = message.trim();
body = null;
}
// DATACASS-nnn - syntax
return new ParsedCommitMessage(summary, body);
}
protected static Optional<TicketReference> tryParseGitHubTicketReference(String summary) {
Matcher gitHubPrefixMatcher = GITHUB_PREFIX_SYNTAX.matcher(summary);
if (gitHubPrefixMatcher.find()) {
MatchResult mr = gitHubPrefixMatcher.toMatchResult();
if (mr.start(1) == 0) {
int summaryStart = findSummaryIndex(summary, mr.end(1));
return Optional.of(new TicketReference(gitHubPrefixMatcher.group(1).toUpperCase(Locale.ROOT),
summaryStart > -1 ? summary.substring(summaryStart) : summary, TicketReference.Style.GitHub));
}
}
return Optional.empty();
}
protected static Optional<TicketReference> tryParseJiraTicketReference(String summary) {
Matcher jiraMatcher = JIRA_TICKET.matcher(summary);
if (jiraMatcher.find()) {
@@ -96,33 +167,30 @@ class ParsedCommitMessage {
if (mr.start(1) < 2) {
int summaryStart = findSummaryIndex(summary, mr.end(1));
ticketReference = new TicketReference(jiraMatcher.group(1).toUpperCase(Locale.ROOT),
summaryStart > -1 ? summary.substring(summaryStart) : summary, TicketReference.Style.Jira);
return Optional.of(new TicketReference(jiraMatcher.group(1).toUpperCase(Locale.ROOT),
summaryStart > -1 ? summary.substring(summaryStart) : summary, TicketReference.Style.Jira));
}
}
// Closes (gh-nnn|#nnn) syntax
Matcher gitHubMatcher = GITHUB_CLOSE_SYNTAX.matcher(message);
return Optional.empty();
}
// #nnn syntax
Matcher gitHubPrefixMatcher = GITHUB_PREFIX_SYNTAX.matcher(summary);
protected static Optional<TicketReference> parsePullRequestReference(String body) {
if (gitHubPrefixMatcher.find()) {
if (body != null) {
MatchResult mr = gitHubPrefixMatcher.toMatchResult();
if (mr.start(1) == 0) {
int summaryStart = findSummaryIndex(summary, mr.end(1));
Matcher prMatcher = ORIGINAL_PULL_REQUEST.matcher(body);
ticketReference = new TicketReference(gitHubPrefixMatcher.group(1).toUpperCase(Locale.ROOT),
summaryStart > -1 ? summary.substring(summaryStart) : summary, TicketReference.Style.GitHub);
}
} else {
if (gitHubMatcher.find()) {
ticketReference = new TicketReference(gitHubMatcher.group(1), summary, TicketReference.Style.GitHub);
if (prMatcher.find()) {
return extractTicket(prMatcher.group(1));
}
}
return Optional.empty();
}
protected static List<TicketReference> parseRelatedTickets(String body, Matcher gitHubMatcher) {
List<TicketReference> relatedTickets = new ArrayList<>();
if (body != null) {
Matcher relatedTicketsMatcher = RELATED_TICKET.matcher(body);
@@ -141,26 +209,7 @@ class ParsedCommitMessage {
}
}
if (body != null) {
Matcher prMatcher = ORIGINAL_PULL_REQUEST.matcher(body);
if (prMatcher.find()) {
Optional<TicketReference> pullRequest = extractTicket(prMatcher.group(1));
if (pullRequest.isPresent()) {
pullRequestReference = pullRequest.get();
}
if (ticketReference == null && pullRequestReference != null) {
ticketReference = pullRequestReference;
pullRequestReference = null;
}
}
}
return new ParsedCommitMessage(summary, body, ticketReference, pullRequestReference, relatedTickets);
return relatedTickets;
}
protected static Optional<TicketReference> extractTicket(String ticketId) {

View File

@@ -63,9 +63,6 @@ public class Changelog {
builder.append("* ").append(ticket.getId()).append(" - ").append(summary != null ? summary.trim() : "");
if (summary == null) {
System.out.println();
}
if (!summary.endsWith(".")) {
builder.append(".");
}

View File

@@ -136,6 +136,18 @@ public interface IssueTracker extends Plugin<Project> {
*/
Changelog getChangelogFor(ModuleIteration module);
/**
* Returns the {@link Changelog} for the given {@link ModuleIteration} using {@link TicketReference}s.
*
* @param module must not be {@literal null}.
* @return
*/
default Changelog getChangelogFor(ModuleIteration module, List<TicketReference> ticketReferences) {
Tickets tickets = resolve(module, ticketReferences);
return Changelog.of(module, tickets);
}
/**
* Closes the given {@link ModuleIteration}.
*

View File

@@ -15,6 +15,8 @@
*/
package org.springframework.data.release.issues;
import java.io.IOException;
import java.net.URI;
import java.util.List;
import org.apache.http.HttpHost;
@@ -40,6 +42,9 @@ import org.springframework.data.release.issues.github.GitHubProperties;
import org.springframework.data.release.issues.jira.JiraProperties;
import org.springframework.data.release.model.Project;
import org.springframework.data.release.utils.HttpBasicCredentials;
import org.springframework.data.util.Lazy;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
@@ -60,7 +65,7 @@ import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
* @author Oliver Gierke
* @author Mark Paluch
*/
@Configuration
@Configuration(proxyBeanMethods = false)
@EnableCaching(proxyTargetClass = true)
class IssueTrackerConfiguration {
@@ -93,9 +98,17 @@ class IssueTrackerConfiguration {
addPreemptiveAuth(credsProvider, authCache, jiraProperties.getApiUrl(), jiraProperties.getCredentials());
addPreemptiveAuth(credsProvider, authCache, gitHubProperties.getApiUrl(), gitHubProperties.getHttpCredentials());
CloseableHttpClient httpClient = HttpClientBuilder.create().setDefaultCredentialsProvider(credsProvider).build();
Lazy<CloseableHttpClient> lazy = Lazy
.of(() -> HttpClientBuilder.create().setDefaultCredentialsProvider(credsProvider).build());
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory() {
@Override
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
setHttpClient(lazy.get());
return super.createRequest(uri, httpMethod);
}
};
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
factory.setHttpContextFactory((httpMethod, uri) -> {
HttpClientContext context = HttpClientContext.create();
context.setAuthCache(authCache);
@@ -107,13 +120,13 @@ class IssueTrackerConfiguration {
@Bean
@Qualifier("tracker")
RestTemplateBuilder restTemplate(ClientHttpRequestFactory clientHttpRequestFactory) {
RestTemplateBuilder restTemplate(ClientHttpRequestFactory clientHttpRequestFactory,
ObjectMapper jacksonObjectMapper) {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(jacksonObjectMapper());
converter.setObjectMapper(jacksonObjectMapper);
return new RestTemplateBuilder().messageConverters(converter)
.requestFactory(() -> clientHttpRequestFactory);
return new RestTemplateBuilder().messageConverters(converter).requestFactory(() -> clientHttpRequestFactory);
}
@Bean

View File

@@ -17,6 +17,7 @@ package org.springframework.data.release.misc;
import lombok.RequiredArgsConstructor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@@ -28,6 +29,7 @@ import org.springframework.data.release.git.GitOperations;
import org.springframework.data.release.io.Workspace;
import org.springframework.data.release.issues.Changelog;
import org.springframework.data.release.issues.IssueTracker;
import org.springframework.data.release.issues.Ticket;
import org.springframework.data.release.issues.TicketReference;
import org.springframework.data.release.issues.Tickets;
import org.springframework.data.release.model.Iteration;
@@ -84,6 +86,7 @@ public class ReleaseOperations {
}
protected void prepareChangelog(TrainIteration iteration, TrainIteration previousIteration, ModuleIteration module) {
IssueTracker issueTracker = trackers.getRequiredPluginFor(module.getProject(),
() -> String.format("No issue tracker found for project %s!", module.getProject()));
@@ -116,14 +119,22 @@ public class ReleaseOperations {
protected Changelog getChangelog(TrainIteration iteration, TrainIteration previousIteration, ModuleIteration module,
IssueTracker issueTracker) {
Changelog changelog;
if (COMMIT_BASED_CHANGELOG) {
List<TicketReference> ticketReferences = git.getTicketReferencesBetween(module.getProject(), previousIteration,
iteration);
Tickets resolvedTickets = issueTracker.resolve(module, ticketReferences);
changelog = Changelog.of(module, resolvedTickets);
// TODO: Remove once all tickets are migrated to GitHub
List<Ticket> tickets = new ArrayList<>();
for (IssueTracker tracker : trackers) {
tickets.addAll(tracker.resolve(module, ticketReferences).getTickets());
}
changelog = Changelog.of(module, new Tickets(tickets));
} else {
changelog = issueTracker.getChangelogFor(module);
}

View File

@@ -22,6 +22,7 @@ import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.release.git.GitOperations;
import org.springframework.data.release.git.GitProperties;
import org.springframework.data.release.utils.Logger;
import org.springframework.web.client.RestTemplate;
@@ -31,26 +32,24 @@ import org.springframework.web.client.RestTemplate;
* @author Oliver Gierke
* @author Mark Paluch
*/
@Configuration
@Configuration(proxyBeanMethods = false)
class SaganConfiguration {
@Autowired GitProperties gitProperties;
@Autowired SaganProperties properties;
@Autowired Logger logger;
@Bean
public SaganOperations saganOperations(GitOperations operations, Executor executor) {
return new SaganOperations(operations, executor, saganClient(), logger);
public SaganOperations saganOperations(GitOperations operations, SaganClient saganClient, Executor executor) {
return new SaganOperations(operations, executor, saganClient, logger);
}
@Bean
SaganClient saganClient() {
return new DefaultSaganClient(saganRestTemplate(), properties, logger);
// return new DummySaganClient(logger, new ObjectMapper().writerWithDefaultPrettyPrinter());
}
RestTemplate restTemplate = new RestTemplateBuilder()
.basicAuthentication(gitProperties.getUsername(), properties.key).build();
@Bean
RestTemplate saganRestTemplate() {
return new RestTemplateBuilder().basicAuthentication("mp911de", properties.key).build();
return new DefaultSaganClient(restTemplate, properties, logger);
}
}

View File

@@ -17,6 +17,16 @@ package org.springframework.data.release.utils;
import lombok.extern.slf4j.Slf4j;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolExecutorFactoryBean;
@@ -29,7 +39,8 @@ import org.springframework.scheduling.concurrent.ThreadPoolExecutorFactoryBean;
class ExecutorConfiguration {
@Bean
public ThreadPoolExecutorFactoryBean executorService() {
@ConditionalOnProperty(prefix = "maven", name = "parallelize")
public ThreadPoolExecutorFactoryBean threadPoolExecutorFactoryBean() {
int processors = Runtime.getRuntime().availableProcessors();
int threadCount = Math.max(2, processors - 4);
@@ -41,4 +52,88 @@ class ExecutorConfiguration {
return scheduler;
}
@Bean
@ConditionalOnProperty(prefix = "maven", name = "parallelize", matchIfMissing = true, havingValue = "false")
public ExecutorService executorService() {
return ImmediateExecutorService.INSTANCE;
}
enum ImmediateExecutorService implements ExecutorService {
INSTANCE;
@Override
public void shutdown() {
}
@Override
public List<Runnable> shutdownNow() {
return Collections.emptyList();
}
@Override
public boolean isShutdown() {
return false;
}
@Override
public boolean isTerminated() {
return false;
}
@Override
public boolean awaitTermination(long timeout, TimeUnit unit) {
return false;
}
@Override
public <T> Future<T> submit(Callable<T> task) {
try {
return CompletableFuture.completedFuture(task.call());
} catch (Exception e) {
CompletableFuture<T> f = new CompletableFuture<>();
f.completeExceptionally(e);
return f;
}
}
@Override
public <T> Future<T> submit(Runnable task, T result) {
return submit(() -> {
task.run();
return result;
});
}
@Override
public Future<?> submit(Runnable task) {
return submit(task, null);
}
@Override
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) {
throw new UnsupportedOperationException();
}
@Override
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) {
throw new UnsupportedOperationException();
}
@Override
public <T> T invokeAny(Collection<? extends Callable<T>> tasks) {
throw new UnsupportedOperationException();
}
@Override
public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) {
throw new UnsupportedOperationException();
}
@Override
public void execute(Runnable command) {
submit(command);
}
}
}

View File

@@ -3,14 +3,12 @@ logging.level.org.springframework=WARN
logging.level.org.springframework.data.release=INFO
logging.level.org.springframework.web.client=TRACE
logging.level.org.springframework.http=DEBUG
# Deployment
deployment.repository-prefix=test-
maven.parallelize=false
jira.username=dummy
jira.password=dummy
jira.api-url=http://localhost:8888
git.username=dummy
git.password=dummy
git.email=dummy@dummy.com

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d %5p %40.40c:%4L - %m%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="console"/>
</root>
</configuration>