#5 - Polishing.

Refactored issue tracker implementation into dedicated packages. Removed package tangles by moving some types around. Extracted GitHub specific properties into its own properties type reusing the Git credentials as we're talking to GitHub anyway.

Added Jackson parameter name module and enabled Java 8 parameter names to be able to get rid of the manually declared constructors for payload classes. Turned them into value objects where possible.

Original pull request: #14.
This commit is contained in:
Oliver Gierke
2016-04-05 18:35:31 +02:00
parent 7f1ae27d3e
commit 5451682c64
53 changed files with 966 additions and 679 deletions

View File

@@ -39,6 +39,11 @@
<artifactId>jackson-databind</artifactId> <artifactId>jackson-databind</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-parameter-names</artifactId>
</dependency>
<dependency> <dependency>
<groupId>joda-time</groupId> <groupId>joda-time</groupId>
<artifactId>joda-time</artifactId> <artifactId>joda-time</artifactId>
@@ -137,6 +142,16 @@
</configuration> </configuration>
</plugin> </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<compilerArgument>-parameters</compilerArgument>
</configuration>
</plugin>
</plugins> </plugins>
</build> </build>

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2016 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
*
* http://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 org.springframework.data.release.build;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import java.io.IOException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.CliComponent;
import org.springframework.data.release.io.Workspace;
import org.springframework.data.release.utils.Logger;
import org.springframework.shell.core.annotation.CliCommand;
/**
* @author Oliver Gierke
*/
@CliComponent
@RequiredArgsConstructor(onConstructor = @__(@Autowired) )
public class BuildCommands {
private final @NonNull BuildOperations build;
private final @NonNull Workspace workspace;
private final @NonNull Logger logger;
/**
* Removes all Spring Data artifacts from the local repository.
*
* @throws IOException
*/
@CliCommand("workspace purge artifacts")
public void purge() throws IOException {
logger.log("Workspace", "Cleaning up workspace directory at %s.",
workspace.getWorkingDirectory().getAbsolutePath());
workspace.purge(build.getLocalRepository(),
path -> build.getLocalRepository().relativize(path).startsWith("org/springframework/data"));
}
}

View File

@@ -28,7 +28,6 @@ import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.Phase; import org.springframework.data.release.model.Phase;
import org.springframework.data.release.model.Project; import org.springframework.data.release.model.Project;
import org.springframework.data.release.model.TrainIteration; import org.springframework.data.release.model.TrainIteration;
import org.springframework.data.release.model.UpdateInformation;
import org.springframework.plugin.core.PluginRegistry; import org.springframework.plugin.core.PluginRegistry;
import org.springframework.plugin.core.PluginRegistry.Supplier; import org.springframework.plugin.core.PluginRegistry.Supplier;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -56,13 +55,13 @@ public class BuildOperations {
Assert.notNull(iteration, "Train iteration must not be null!"); Assert.notNull(iteration, "Train iteration must not be null!");
Assert.notNull(phase, "Phase must not be null!"); Assert.notNull(phase, "Phase must not be null!");
UpdateInformation updateInformation = new UpdateInformation(iteration, phase); UpdateInformation updateInformation = UpdateInformation.of(iteration, phase);
doWithBuildSystem(iteration, (system, it) -> system.updateProjectDescriptors(it, updateInformation)); doWithBuildSystem(iteration, (system, it) -> system.updateProjectDescriptors(it, updateInformation));
} }
/** /**
* Triggers the distribution builds for all modules particitpating in the given {@link TrainIteration}. * Triggers the distribution builds for all modules participating in the given {@link TrainIteration}.
* *
* @param iteration must not be {@literal null}. * @param iteration must not be {@literal null}.
*/ */

View File

@@ -19,7 +19,6 @@ import org.springframework.data.release.deployment.DeploymentInformation;
import org.springframework.data.release.model.ModuleIteration; import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.Phase; import org.springframework.data.release.model.Phase;
import org.springframework.data.release.model.Project; import org.springframework.data.release.model.Project;
import org.springframework.data.release.model.UpdateInformation;
import org.springframework.plugin.core.Plugin; import org.springframework.plugin.core.Plugin;
/** /**
@@ -30,16 +29,35 @@ import org.springframework.plugin.core.Plugin;
interface BuildSystem extends Plugin<Project> { interface BuildSystem extends Plugin<Project> {
/** /**
* @param iteration * Updates the project descriptors for the given {@link ModuleIteration} using the given {@link UpdateInformation}.
* @param train *
* @param phase * @param iteration must not be {@literal null}.
* @throws Exception * @param updateInformation must not be {@literal null}.
*/ */
ModuleIteration updateProjectDescriptors(ModuleIteration iteration, UpdateInformation updateInformation); ModuleIteration updateProjectDescriptors(ModuleIteration iteration, UpdateInformation updateInformation);
/**
* Prepares the project descriptor of the {@link ModuleIteration} for the given release {@link Phase}.
*
* @param module must not be {@literal null}.
* @param phase must not be {@literal null}.
* @return
*/
ModuleIteration prepareVersion(ModuleIteration module, Phase phase); ModuleIteration prepareVersion(ModuleIteration module, Phase phase);
ModuleIteration triggerDistributionBuild(ModuleIteration module); /**
* Deploy artifacts for the given {@link ModuleIteration} and return the {@link DeploymentInformation}.
*
* @param module must not be {@literal null}.
* @return
*/
DeploymentInformation deploy(ModuleIteration module); DeploymentInformation deploy(ModuleIteration module);
/**
* Runs the distribution build.
*
* @param module must not be {@literal null}.
* @return
*/
ModuleIteration triggerDistributionBuild(ModuleIteration module);
} }

View File

@@ -29,7 +29,6 @@ import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.Phase; import org.springframework.data.release.model.Phase;
import org.springframework.data.release.model.Project; import org.springframework.data.release.model.Project;
import org.springframework.data.release.model.TrainIteration; import org.springframework.data.release.model.TrainIteration;
import org.springframework.data.release.model.UpdateInformation;
import org.springframework.data.release.utils.Logger; import org.springframework.data.release.utils.Logger;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -61,7 +60,7 @@ class GradleBuildSystem implements BuildSystem {
*/ */
public void updateProject(TrainIteration iteration, final Phase phase) throws Exception { public void updateProject(TrainIteration iteration, final Phase phase) throws Exception {
UpdateInformation updateInformation = new UpdateInformation(iteration, phase); UpdateInformation updateInformation = UpdateInformation.of(iteration, phase);
for (ModuleIteration module : iteration.getModulesExcept(BUILD)) { for (ModuleIteration module : iteration.getModulesExcept(BUILD)) {
updateProjectDescriptors(module, updateInformation); updateProjectDescriptors(module, updateInformation);

View File

@@ -36,7 +36,6 @@ import org.springframework.data.release.model.Phase;
import org.springframework.data.release.model.Project; import org.springframework.data.release.model.Project;
import org.springframework.data.release.model.Train; import org.springframework.data.release.model.Train;
import org.springframework.data.release.model.TrainIteration; import org.springframework.data.release.model.TrainIteration;
import org.springframework.data.release.model.UpdateInformation;
import org.springframework.data.release.utils.ExecutionUtils; import org.springframework.data.release.utils.ExecutionUtils;
import org.springframework.data.release.utils.Logger; import org.springframework.data.release.utils.Logger;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -218,7 +217,7 @@ class MavenBuildSystem implements BuildSystem {
public ModuleIteration prepareVersion(ModuleIteration module, Phase phase) { public ModuleIteration prepareVersion(ModuleIteration module, Phase phase) {
Project project = module.getProject(); Project project = module.getProject();
UpdateInformation information = new UpdateInformation(module.getTrainIteration(), phase); UpdateInformation information = UpdateInformation.of(module.getTrainIteration(), phase);
mvn.execute(project, "versions:set", "versions:commit", mvn.execute(project, "versions:set", "versions:commit",
"-DnewVersion=".concat(information.getProjectVersionToSet(project).toString())); "-DnewVersion=".concat(information.getProjectVersionToSet(project).toString()));

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2015 the original author or authors. * Copyright 2015-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -23,7 +23,6 @@ import lombok.RequiredArgsConstructor;
import org.springframework.data.release.model.ArtifactVersion; import org.springframework.data.release.model.ArtifactVersion;
import org.springframework.data.release.model.Project; import org.springframework.data.release.model.Project;
import org.springframework.data.release.model.UpdateInformation;
import org.springframework.data.release.utils.Logger; import org.springframework.data.release.utils.Logger;
import org.springframework.util.Assert; import org.springframework.util.Assert;

View File

@@ -1,4 +1,19 @@
package org.springframework.data.release.model; /*
* Copyright 2015-2016 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
*
* http://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 org.springframework.data.release.build;
import static org.springframework.data.release.model.Projects.*; import static org.springframework.data.release.model.Projects.*;
@@ -6,7 +21,10 @@ import lombok.Getter;
import lombok.NonNull; import lombok.NonNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.data.release.build.Repository; import org.springframework.data.release.model.ArtifactVersion;
import org.springframework.data.release.model.Phase;
import org.springframework.data.release.model.Project;
import org.springframework.data.release.model.TrainIteration;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
@@ -14,7 +32,7 @@ import org.springframework.util.Assert;
* *
* @author Oliver Gierke * @author Oliver Gierke
*/ */
@RequiredArgsConstructor @RequiredArgsConstructor(staticName = "of")
public class UpdateInformation { public class UpdateInformation {
private final @NonNull @Getter TrainIteration iteration; private final @NonNull @Getter TrainIteration iteration;

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2014 the original author or authors. * Copyright 2014-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -20,7 +20,7 @@ import lombok.RequiredArgsConstructor;
import java.util.Optional; import java.util.Optional;
import org.springframework.data.release.jira.Ticket; import org.springframework.data.release.issues.Ticket;
/** /**
* @author Oliver Gierke * @author Oliver Gierke

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2014 the original author or authors. * Copyright 2014-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -24,8 +24,7 @@ import java.util.stream.Stream;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.CliComponent; import org.springframework.data.release.CliComponent;
import org.springframework.data.release.jira.Ticket; import org.springframework.data.release.issues.Ticket;
import org.springframework.data.release.jira.TicketBranches;
import org.springframework.data.release.model.Project; import org.springframework.data.release.model.Project;
import org.springframework.data.release.model.ReleaseTrains; import org.springframework.data.release.model.ReleaseTrains;
import org.springframework.data.release.model.Train; import org.springframework.data.release.model.Train;
@@ -118,8 +117,9 @@ public class GitCommands implements CommandMarker {
public void backportChangelogs(@CliOption(key = "", mandatory = true) TrainIteration iteration, // public void backportChangelogs(@CliOption(key = "", mandatory = true) TrainIteration iteration, //
@CliOption(key = "target", mandatory = true) String trains) { @CliOption(key = "target", mandatory = true) String trains) {
List<Train> targets = Stream.of(trains.split(",")).map(it -> ReleaseTrains.getTrainByName(it)) List<Train> targets = Stream.of(trains.split(",")).//
.collect(Collectors.toList()); map(it -> ReleaseTrains.getTrainByName(it)).//
collect(Collectors.toList());
git.backportChangelogs(iteration, targets); git.backportChangelogs(iteration, targets);
} }
@@ -149,7 +149,7 @@ public class GitCommands implements CommandMarker {
filter(branch -> ticketBranches.hasTicketFor(branch, resolved)).// filter(branch -> ticketBranches.hasTicketFor(branch, resolved)).//
forEachOrdered(branch -> { forEachOrdered(branch -> {
Optional<Ticket> ticket = ticketBranches.getTicket(branch); Optional<Ticket> ticket = ticketBranches.findTicket(branch);
table.addRow(branch.toString(), // table.addRow(branch.toString(), //
ticket.map(t -> t.getTicketStatus().getLabel()).orElse(""), // ticket.map(t -> t.getTicketStatus().getLabel()).orElse(""), //

View File

@@ -15,7 +15,9 @@
*/ */
package org.springframework.data.release.git; package org.springframework.data.release.git;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@@ -41,9 +43,8 @@ import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.transport.TagOpt; import org.eclipse.jgit.transport.TagOpt;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.io.Workspace; import org.springframework.data.release.io.Workspace;
import org.springframework.data.release.jira.IssueTracker; import org.springframework.data.release.issues.IssueTracker;
import org.springframework.data.release.jira.Ticket; import org.springframework.data.release.issues.Ticket;
import org.springframework.data.release.jira.TicketBranches;
import org.springframework.data.release.model.ArtifactVersion; import org.springframework.data.release.model.ArtifactVersion;
import org.springframework.data.release.model.ModuleIteration; import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.Project; import org.springframework.data.release.model.Project;
@@ -62,13 +63,14 @@ import org.springframework.util.Assert;
*/ */
@Component @Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired) ) @RequiredArgsConstructor(onConstructor = @__(@Autowired) )
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
public class GitOperations { public class GitOperations {
private final GitServer server = new GitServer(); GitServer server = new GitServer();
private final Workspace workspace; Workspace workspace;
private final Logger logger; Logger logger;
private final PluginRegistry<IssueTracker, Project> issueTracker; PluginRegistry<IssueTracker, Project> issueTracker;
private final GitProperties gitProperties; GitProperties gitProperties;
/** /**
* Returns the {@link GitProject} for the given {@link Project}. * Returns the {@link GitProject} for the given {@link Project}.
@@ -297,8 +299,8 @@ public class GitOperations {
Collection<Ticket> tickets = tracker.findTickets(project, ticketIds.keySet()); Collection<Ticket> tickets = tracker.findTickets(project, ticketIds.keySet());
return TicketBranches.from(tickets.stream() return TicketBranches
.collect(Collectors.toMap(ticket -> ticketIds.get(ticket.getId()), ticket -> ticket))); .from(tickets.stream().collect(Collectors.toMap(ticket -> ticketIds.get(ticket.getId()), ticket -> ticket)));
}); });
} }
@@ -659,12 +661,10 @@ public class GitOperations {
} }
private static interface GitCallback<T> { private static interface GitCallback<T> {
T doWithGit(Git git) throws Exception; T doWithGit(Git git) throws Exception;
} }
private static interface VoidGitCallback { private static interface VoidGitCallback {
void doWithGit(Git git) throws Exception; void doWithGit(Git git) throws Exception;
} }
} }

View File

@@ -23,7 +23,6 @@ import javax.annotation.PostConstruct;
import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.data.release.utils.HttpBasicCredentials; import org.springframework.data.release.utils.HttpBasicCredentials;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -43,8 +42,6 @@ public class GitProperties {
private @Getter(AccessLevel.PRIVATE) String password; private @Getter(AccessLevel.PRIVATE) String password;
private String username, author, email; private String username, author, email;
@Value("${github.api.url}") private String githubApiBaseUrl;
@PostConstruct @PostConstruct
public void init() { public void init() {
@@ -52,7 +49,6 @@ public class GitProperties {
Assert.hasText(password, "No GitHub password (git.password) configured!"); Assert.hasText(password, "No GitHub password (git.password) configured!");
Assert.hasText(author, "No Git author (git.author) configured!"); Assert.hasText(author, "No Git author (git.author) configured!");
Assert.hasText(email, "No Git email (git.email) configured!"); Assert.hasText(email, "No Git email (git.email) configured!");
Assert.hasText(githubApiBaseUrl, "No GitHub API base url configured!");
} }
/** /**

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.data.release.jira; package org.springframework.data.release.git;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
@@ -22,7 +22,7 @@ import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.springframework.data.release.Streamable; import org.springframework.data.release.Streamable;
import org.springframework.data.release.git.Branch; import org.springframework.data.release.issues.Ticket;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
@@ -53,7 +53,7 @@ public class TicketBranches implements Streamable<Branch> {
Assert.notNull(branch, "Branch must not be null!"); Assert.notNull(branch, "Branch must not be null!");
return getTicket(branch).// return findTicket(branch).//
map(ticket -> requireResolved ? ticket.getTicketStatus().isResolved() : true).// map(ticket -> requireResolved ? ticket.getTicketStatus().isResolved() : true).//
orElse(false); orElse(false);
} }
@@ -76,7 +76,7 @@ public class TicketBranches implements Streamable<Branch> {
* @param branch must not be {@literal null}. * @param branch must not be {@literal null}.
* @return * @return
*/ */
public Optional<Ticket> getTicket(Branch branch) { public Optional<Ticket> findTicket(Branch branch) {
Assert.notNull(branch, "Branch must not be null!"); Assert.notNull(branch, "Branch must not be null!");
return Optional.ofNullable(ticketBranches.get(branch)); return Optional.ofNullable(ticketBranches.get(branch));

View File

@@ -21,7 +21,6 @@ import java.io.IOException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.CliComponent; import org.springframework.data.release.CliComponent;
import org.springframework.data.release.build.BuildOperations;
import org.springframework.data.release.utils.Logger; import org.springframework.data.release.utils.Logger;
import org.springframework.shell.core.CommandMarker; import org.springframework.shell.core.CommandMarker;
import org.springframework.shell.core.annotation.CliCommand; import org.springframework.shell.core.annotation.CliCommand;
@@ -34,7 +33,6 @@ import org.springframework.shell.core.annotation.CliCommand;
public class WorkspaceCommands implements CommandMarker { public class WorkspaceCommands implements CommandMarker {
private final Workspace workspace; private final Workspace workspace;
private final BuildOperations build;
private final Logger logger; private final Logger logger;
@CliCommand("workspace cleanup") @CliCommand("workspace cleanup")
@@ -45,19 +43,4 @@ public class WorkspaceCommands implements CommandMarker {
workspace.cleanup(); workspace.cleanup();
} }
/**
* Removes all Spring Data artifacts from the local repository.
*
* @throws IOException
*/
@CliCommand("workspace purge artifacts")
public void purge() throws IOException {
logger.log("Workspace", "Cleaning up workspace directory at %s.",
workspace.getWorkingDirectory().getAbsolutePath());
workspace.purge(build.getLocalRepository(),
path -> build.getLocalRepository().relativize(path).startsWith("org/springframework/data"));
}
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2014 the original author or authors. * Copyright 2014-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -13,15 +13,14 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.data.release.jira; package org.springframework.data.release.issues;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import java.util.Date; import java.util.Date;
import java.util.Locale; import java.util.Locale;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import org.springframework.data.release.model.ArtifactVersion; import org.springframework.data.release.model.ArtifactVersion;
import org.springframework.data.release.model.ModuleIteration; import org.springframework.data.release.model.ModuleIteration;
import org.springframework.format.datetime.DateFormatter; import org.springframework.format.datetime.DateFormatter;
@@ -30,7 +29,7 @@ import org.springframework.shell.support.util.OsUtils;
/** /**
* @author Oliver Gierke * @author Oliver Gierke
*/ */
@RequiredArgsConstructor(access = AccessLevel.PACKAGE) @RequiredArgsConstructor(staticName = "of")
@EqualsAndHashCode @EqualsAndHashCode
public class Changelog { public class Changelog {

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.data.release.jira; package org.springframework.data.release.issues;
import java.util.Collection; import java.util.Collection;
@@ -25,13 +25,15 @@ import org.springframework.data.release.model.TrainIteration;
import org.springframework.plugin.core.Plugin; import org.springframework.plugin.core.Plugin;
/** /**
* Interface for issue tracker operations.
*
* @author Oliver Gierke * @author Oliver Gierke
* @author Mark Paluch * @author Mark Paluch
*/ */
public interface IssueTracker extends Plugin<Project> { public interface IssueTracker extends Plugin<Project> {
/** /**
* Reset internal state (cache, ...). * Reset internal state (cache, etc).
*/ */
void reset(); void reset();
@@ -55,7 +57,7 @@ public interface IssueTracker extends Plugin<Project> {
/** /**
* Returns the {@link Ticket} that tracks modifications in the context of a release. * Returns the {@link Ticket} that tracks modifications in the context of a release.
* *
* @param module the module to lookup the {@link Ticket} for. * @param module the module to lookup the {@link Ticket} for, must not be {@literal null}.
* @return * @return
*/ */
Ticket getReleaseTicketFor(ModuleIteration module); Ticket getReleaseTicketFor(ModuleIteration module);
@@ -64,8 +66,8 @@ public interface IssueTracker extends Plugin<Project> {
* Query the issue tracker for multiple {@link Ticket#id ticket Ids}. Tickets that are not found are not returned * Query the issue tracker for multiple {@link Ticket#id ticket Ids}. Tickets that are not found are not returned
* within the result. * within the result.
* *
* @param project * @param project must not be {@literal null}.
* @param ticketIds collection of {@link Ticket#id ticket Ids} * @param ticketIds collection of {@link Ticket#id ticket Ids}, must not be {@literal null}.
* @return * @return
*/ */
Collection<Ticket> findTickets(Project project, Collection<String> ticketIds); Collection<Ticket> findTickets(Project project, Collection<String> ticketIds);
@@ -73,18 +75,40 @@ public interface IssueTracker extends Plugin<Project> {
/** /**
* Creates a release version if release version is missing. * Creates a release version if release version is missing.
* *
* @param moduleIteration must not be {@literal null}. * @param module must not be {@literal null}.
* @param credentials must not be {@literal null}.
*/ */
void createReleaseVersion(ModuleIteration moduleIteration); void createReleaseVersion(ModuleIteration module);
/** /**
* Create release ticket if release ticket is missing. * Create release ticket if release ticket is missing.
* <p>
* TODO: Return created ticket
* *
* @param iteration must not be {@literal null}. * @param module must not be {@literal null}.
*/
void createReleaseTicket(ModuleIteration module);
/**
* Assigns the ticket to the current user.
*
* @param ticket must not be {@literal null}.
* @param credentials must not be {@literal null}. * @param credentials must not be {@literal null}.
*/ */
void createReleaseTicket(ModuleIteration moduleIteration); void assignTicketToMe(Ticket ticket);
Changelog getChangelogFor(ModuleIteration iteration); /**
* Assigns the release ticket for the given {@link ModuleIteration} to the current user.
*
* @param module must not be {@literal null}.
* @return
*/
Ticket assignReleaseTicketToMe(ModuleIteration module);
/**
* Returns the {@link Changelog} for the given {@link ModuleIteration}.
*
* @param module must not be {@literal null}.
* @return
*/
Changelog getChangelogFor(ModuleIteration module);
} }

View File

@@ -13,17 +13,22 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.data.release.cli; package org.springframework.data.release.issues;
import static org.springframework.data.release.utils.ExecutionUtils.*;
import lombok.AccessLevel;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
import java.util.function.BiFunction;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport; import java.util.stream.StreamSupport;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.CliComponent; import org.springframework.data.release.CliComponent;
import org.springframework.data.release.jira.Changelog;
import org.springframework.data.release.jira.IssueTracker;
import org.springframework.data.release.jira.JiraConnector;
import org.springframework.data.release.jira.Tickets;
import org.springframework.data.release.model.ModuleIteration; import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.Project; import org.springframework.data.release.model.Project;
import org.springframework.data.release.model.TrainIteration; import org.springframework.data.release.model.TrainIteration;
@@ -39,56 +44,38 @@ import org.springframework.util.StringUtils;
* @author Mark Paluch * @author Mark Paluch
*/ */
@CliComponent @CliComponent
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
@RequiredArgsConstructor(onConstructor = @__(@Autowired) )
public class IssueTrackerCommands implements CommandMarker { public class IssueTrackerCommands implements CommandMarker {
private final PluginRegistry<IssueTracker, Project> tracker; @NonNull PluginRegistry<IssueTracker, Project> tracker;
private final JiraConnector jira;
/**
* @param tracker must not be {@literal null}.
* @param jira must not be {@literal null}.
* @param environment must not be {@literal null}.
*/
@Autowired
public IssueTrackerCommands(PluginRegistry<IssueTracker, Project> tracker, JiraConnector jira) {
this.tracker = tracker;
this.jira = jira;
}
@CliCommand("tracker evict") @CliCommand("tracker evict")
public void evict() { public void evict() {
StreamSupport.stream(tracker.spliterator(), false).forEach(IssueTracker::reset); StreamSupport.stream(tracker.spliterator(), false).forEach(IssueTracker::reset);
} }
@CliCommand(value = "jira tickets") @CliCommand(value = "tracker tickets")
public String jira(@CliOption(key = "", mandatory = true) TrainIteration iteration, // public String jira(@CliOption(key = "", mandatory = true) TrainIteration iteration, //
@CliOption(key = "for-current-user", specifiedDefaultValue = "true", @CliOption(key = "for-current-user", specifiedDefaultValue = "true",
unspecifiedDefaultValue = "false") boolean forCurrentUser) { unspecifiedDefaultValue = "false") boolean forCurrentUser) {
Tickets tickets = StreamSupport.stream(tracker.spliterator(), false). // return tracker.getPlugins().stream().//
flatMap(issueTracker -> issueTracker. // flatMap(it -> it.getTicketsFor(iteration, forCurrentUser).stream()).//
getTicketsFor(iteration, forCurrentUser).stream()) collect(Tickets.toTicketsCollector()).toString();
. //
collect(Tickets.toTicketsCollector());
return tickets.toString();
} }
@CliCommand(value = "tracker releasetickets") @CliCommand(value = "tracker releasetickets")
public String releaseTickets(@CliOption(key = "", mandatory = true) TrainIteration iteration) { public String releaseTickets(@CliOption(key = "", mandatory = true) TrainIteration iteration) {
return runAndReturn(iteration, module -> getTrackerFor(module).getReleaseTicketFor(module),
Tickets tickets = StreamSupport.stream(tracker.spliterator(), false). // Tickets.toTicketsCollector()).toString();
flatMap(issueTracker -> issueTracker.getTicketsFor(iteration).stream()). //
collect(Tickets.toTicketsCollector());
return tickets.getReleaseTickets(iteration).toString();
} }
@CliCommand(value = "jira self-assign releasetickets") @CliCommand(value = "tracker self-assign releasetickets")
public String jiraSelfAssignReleaseTickets(@CliOption(key = "", mandatory = true) TrainIteration iteration) { public String jiraSelfAssignReleaseTickets(@CliOption(key = "", mandatory = true) TrainIteration iteration) {
Tickets releaseTickets = jira.getTicketsFor(iteration).getReleaseTickets(iteration); return runAndReturn(iteration, module -> getTrackerFor(module).assignReleaseTicketToMe(module),
releaseTickets.forEach(ticket -> jira.assignTicketToMe(ticket)); Tickets.toTicketsCollector()).toString();
return releaseTickets(iteration);
} }
@CliCommand(value = "tracker create releaseversions") @CliCommand(value = "tracker create releaseversions")
@@ -99,21 +86,14 @@ public class IssueTrackerCommands implements CommandMarker {
@CliCommand(value = "tracker create releasetickets") @CliCommand(value = "tracker create releasetickets")
public String createReleaseTickets(@CliOption(key = "", mandatory = true) TrainIteration iteration) { public String createReleaseTickets(@CliOption(key = "", mandatory = true) TrainIteration iteration) {
iteration.forEach(this::createReleaseVersion); iteration.stream().//
forEach(module -> getTrackerFor(module).createReleaseTicket(module));
evict(); evict();
return releaseTickets(iteration); return releaseTickets(iteration);
} }
private void createReleaseVersion(ModuleIteration moduleIteration) {
getPluginFor(moduleIteration).createReleaseVersion(moduleIteration);
}
private IssueTracker getPluginFor(ModuleIteration moduleIteration) {
return tracker.getPluginFor(moduleIteration.getProject());
}
@CliCommand("tracker changelog") @CliCommand("tracker changelog")
public String changelog(@CliOption(key = "", mandatory = true) TrainIteration iteration, // public String changelog(@CliOption(key = "", mandatory = true) TrainIteration iteration, //
@CliOption(key = "module") String moduleName) { @CliOption(key = "module") String moduleName) {
@@ -121,7 +101,7 @@ public class IssueTrackerCommands implements CommandMarker {
if (StringUtils.hasText(moduleName)) { if (StringUtils.hasText(moduleName)) {
ModuleIteration module = iteration.getModule(moduleName); ModuleIteration module = iteration.getModule(moduleName);
return tracker.getPluginFor(module.getProject()).getChangelogFor(module).toString(); return getTrackerFor(module).getChangelogFor(module).toString();
} }
return ExecutionUtils.runAndReturn(iteration, this::getChangelog).// return ExecutionUtils.runAndReturn(iteration, this::getChangelog).//
@@ -129,6 +109,18 @@ public class IssueTrackerCommands implements CommandMarker {
} }
private Changelog getChangelog(ModuleIteration module) { private Changelog getChangelog(ModuleIteration module) {
return tracker.getPluginFor(module.getProject()).getChangelogFor(module); return getTrackerFor(module).getChangelogFor(module);
}
private void createReleaseVersion(ModuleIteration moduleIteration) {
getTrackerFor(moduleIteration).createReleaseVersion(moduleIteration);
}
private <T> Stream<T> doWithBuildSystem(TrainIteration train, BiFunction<IssueTracker, ModuleIteration, T> function) {
return train.stream().map(module -> function.apply(getTrackerFor(module), module));
}
private IssueTracker getTrackerFor(ModuleIteration moduleIteration) {
return tracker.getPluginFor(moduleIteration.getProject());
} }
} }

View File

@@ -13,60 +13,63 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.data.release.jira; package org.springframework.data.release.issues;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cache.CacheManager; import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager; import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.data.release.git.GitProperties;
import org.springframework.data.release.model.Project; import org.springframework.data.release.model.Project;
import org.springframework.data.release.utils.Logger;
import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.plugin.core.OrderAwarePluginRegistry; import org.springframework.plugin.core.OrderAwarePluginRegistry;
import org.springframework.plugin.core.PluginRegistry; import org.springframework.plugin.core.PluginRegistry;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import com.fasterxml.jackson.annotation.JsonCreator.Mode;
import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
/** /**
* @author Oliver Gierke * @author Oliver Gierke
* @author Mark Paluch * @author Mark Paluch
*/ */
@Configuration @Configuration
@EnableCaching @EnableCaching(proxyTargetClass = true)
class IssueTrackerConfiguration { class IssueTrackerConfiguration {
@Bean @Bean
public CacheManager cacheManager() { CacheManager cacheManager() {
return new ConcurrentMapCacheManager(); return new ConcurrentMapCacheManager();
} }
@Bean @Bean
public PluginRegistry<IssueTracker, Project> issueTrackers(List<? extends IssueTracker> plugins) { ObjectMapper jacksonObjectMapper() {
return OrderAwarePluginRegistry.create(plugins);
}
@Bean
public ObjectMapper jacksonObjectMapper() {
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.setSerializationInclusion(Include.NON_NULL); mapper.setSerializationInclusion(Include.NON_NULL);
mapper.registerModule(new ParameterNamesModule(Mode.PROPERTIES));
mapper.registerModule(new SyntheticLambdaFactoryMethodIgnoringModule());
return mapper; return mapper;
} }
@Bean @Bean
public RestTemplate restTemplate() { @Qualifier("tracker")
RestTemplate restTemplate() {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(jacksonObjectMapper()); converter.setObjectMapper(jacksonObjectMapper());
@@ -81,12 +84,36 @@ class IssueTrackerConfiguration {
} }
@Bean @Bean
public Jira jira(Logger logger, JiraProperties jiraProperties) { PluginRegistry<IssueTracker, Project> issueTrackers(List<? extends IssueTracker> plugins) {
return new Jira(restTemplate(), logger, jiraProperties); return OrderAwarePluginRegistry.create(plugins);
} }
@Bean /**
public GitHubIssueTracker github(Logger logger, GitProperties properties) { * @author Oliver Gierke
return new GitHubIssueTracker(restTemplate(), logger, properties); */
static class SyntheticLambdaFactoryMethodIgnoringModule extends SimpleModule {
private static final long serialVersionUID = -3075786389813846512L;
@Override
public void setupModule(SetupContext context) {
context.insertAnnotationIntrospector(new NopAnnotationIntrospector() {
private static final long serialVersionUID = 479313244908256455L;
@Override
public boolean hasIgnoreMarker(AnnotatedMember m) {
if (!(m instanceof AnnotatedMethod)) {
return super.hasIgnoreMarker(m);
}
AnnotatedMethod method = (AnnotatedMethod) m;
return method.getName().startsWith("lambda$") ? true : super.hasIgnoreMarker(m);
}
});
}
} }
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2014 the original author or authors. * Copyright 2014-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -13,13 +13,14 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.data.release.jira; package org.springframework.data.release.issues;
import lombok.Value;
import org.springframework.data.release.model.ModuleIteration; import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.Tracker; import org.springframework.data.release.model.Tracker;
import org.springframework.data.release.model.TrainIteration; import org.springframework.data.release.model.TrainIteration;
import org.springframework.util.Assert;
import lombok.Value;
/** /**
* Value object to represent a {@link Ticket}. * Value object to represent a {@link Ticket}.
@@ -29,9 +30,8 @@ import lombok.Value;
@Value @Value
public class Ticket { public class Ticket {
private final String id; String id, summary;
private final String summary; TicketStatus ticketStatus;
private final TicketStatus ticketStatus;
/* /*
* (non-Javadoc) * (non-Javadoc)
@@ -39,18 +39,32 @@ public class Ticket {
*/ */
@Override @Override
public String toString() { public String toString() {
return String.format("%13s - %s", id, summary); return String.format("%14s - %s", id, summary);
} }
public boolean isResolved() { public boolean isResolved() {
return ticketStatus.isResolved(); return ticketStatus.isResolved();
} }
public boolean isReleaseTicketFor(ModuleIteration moduleIteration) { /**
return summary.equals(Tracker.releaseTicketSummary(moduleIteration)); * Returns whether the current {@link Ticket} is the release ticket for the given {@link ModuleIteration}.
*
* @param module must not be {@literal null}.
* @return
*/
public boolean isReleaseTicketFor(ModuleIteration module) {
Assert.notNull(module, "Module must not be null!");
return summary.equals(Tracker.releaseTicketSummary(module));
} }
public boolean isReleaseTicketFor(TrainIteration iteration) { /**
return iteration.stream().filter(this::isReleaseTicketFor).findFirst().isPresent(); * Returns whether the current {@link Ticket} is a release ticket for the given {@link TrainIteration}.
*
* @param train must not be {@literal null}.
* @return
*/
public boolean isReleaseTicketFor(TrainIteration train) {
return train.stream().anyMatch(this::isReleaseTicketFor);
} }
} }

View File

@@ -14,14 +14,24 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.data.release.jira; package org.springframework.data.release.issues;
/** /**
* @author Mark Paluch * @author Mark Paluch
*/ */
public interface TicketStatus { public interface TicketStatus {
/**
* Returns
*
* @return
*/
String getLabel(); String getLabel();
/**
* Returns whether the ticket is marked as resolved.
*
* @return
*/
boolean isResolved(); boolean isResolved();
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2014 the original author or authors. * Copyright 2014-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -13,7 +13,10 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.data.release.jira; package org.springframework.data.release.issues;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@@ -33,9 +36,6 @@ import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.TrainIteration; import org.springframework.data.release.model.TrainIteration;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import lombok.RequiredArgsConstructor;
import lombok.Value;
/** /**
* Value object to represent a list of {@link Ticket}s. * Value object to represent a list of {@link Ticket}s.
* *
@@ -46,8 +46,8 @@ import lombok.Value;
@RequiredArgsConstructor @RequiredArgsConstructor
public class Tickets implements Streamable<Ticket> { public class Tickets implements Streamable<Ticket> {
private final List<Ticket> tickets; List<Ticket> tickets;
private final int overallTotal; int overallTotal;
public Tickets(List<Ticket> tickets) { public Tickets(List<Ticket> tickets) {
this(Collections.unmodifiableList(tickets), tickets.size()); this(Collections.unmodifiableList(tickets), tickets.size());
@@ -68,9 +68,7 @@ public class Tickets implements Streamable<Ticket> {
public Ticket getReleaseTicket(ModuleIteration moduleIteration) { public Ticket getReleaseTicket(ModuleIteration moduleIteration) {
Optional<Ticket> releaseTicket = findReleaseTicket(moduleIteration); return findReleaseTicket(moduleIteration).orElseThrow(
return releaseTicket.orElseThrow(
() -> new IllegalStateException(String.format("Did not find a release ticket for %s!", moduleIteration))); () -> new IllegalStateException(String.format("Did not find a release ticket for %s!", moduleIteration)));
} }
@@ -84,19 +82,26 @@ public class Tickets implements Streamable<Ticket> {
return stream().// return stream().//
filter(ticket -> ticket.isReleaseTicketFor(iteration)).// filter(ticket -> ticket.isReleaseTicketFor(iteration)).//
distinct().//
collect(toTicketsCollector()); collect(toTicketsCollector());
} }
private Optional<Ticket> findReleaseTicket(ModuleIteration moduleIteration) { private Optional<Ticket> findReleaseTicket(ModuleIteration moduleIteration) {
return stream().// return stream().//
filter(ticket -> ticket.isReleaseTicketFor(moduleIteration)).// filter(ticket -> ticket.isReleaseTicketFor(moduleIteration)).//
findFirst(); findFirst();
} }
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override @Override
public String toString() { public String toString() {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
builder.append(String.format("Train only tickets: %s of %s", tickets.size(), overallTotal)); builder.append(String.format("Train only tickets: %s of %s", tickets.size(), overallTotal));
builder.append("\n"); builder.append("\n");
builder.append(StringUtils.collectionToDelimitedString(tickets, "\n")); builder.append(StringUtils.collectionToDelimitedString(tickets, "\n"));
@@ -112,16 +117,29 @@ public class Tickets implements Streamable<Ticket> {
public static Collector<? super Ticket, ?, Tickets> toTicketsCollector() { public static Collector<? super Ticket, ?, Tickets> toTicketsCollector() {
return new Collector<Ticket, List<Ticket>, Tickets>() { return new Collector<Ticket, List<Ticket>, Tickets>() {
/*
* (non-Javadoc)
* @see java.util.stream.Collector#supplier()
*/
@Override @Override
public Supplier<List<Ticket>> supplier() { public Supplier<List<Ticket>> supplier() {
return ArrayList::new; return ArrayList::new;
} }
/*
* (non-Javadoc)
* @see java.util.stream.Collector#accumulator()
*/
@Override @Override
public BiConsumer<List<Ticket>, Ticket> accumulator() { public BiConsumer<List<Ticket>, Ticket> accumulator() {
return List::add; return List::add;
} }
/*
* (non-Javadoc)
* @see java.util.stream.Collector#combiner()
*/
@Override @Override
public BinaryOperator<List<Ticket>> combiner() { public BinaryOperator<List<Ticket>> combiner() {
return (left, right) -> { return (left, right) -> {
@@ -130,16 +148,23 @@ public class Tickets implements Streamable<Ticket> {
}; };
} }
/*
* (non-Javadoc)
* @see java.util.stream.Collector#finisher()
*/
@Override @Override
public Function<List<Ticket>, Tickets> finisher() { public Function<List<Ticket>, Tickets> finisher() {
return tickets -> new Tickets(tickets); return tickets -> new Tickets(tickets);
} }
/*
* (non-Javadoc)
* @see java.util.stream.Collector#characteristics()
*/
@Override @Override
public Set<Characteristics> characteristics() { public Set<Characteristics> characteristics() {
return Collections.emptySet(); return Collections.emptySet();
} }
}; };
} }
} }

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.data.release.jira; package org.springframework.data.release.issues.github;
import java.net.URI; import java.net.URI;
import java.util.ArrayList; import java.util.ArrayList;
@@ -23,19 +23,21 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Stream;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.ParameterizedTypeReference;
import org.springframework.data.release.git.GitProject; import org.springframework.data.release.git.GitProject;
import org.springframework.data.release.git.GitProperties; import org.springframework.data.release.issues.Changelog;
import org.springframework.data.release.model.Iteration; import org.springframework.data.release.issues.IssueTracker;
import org.springframework.data.release.issues.Ticket;
import org.springframework.data.release.issues.Tickets;
import org.springframework.data.release.issues.github.GitHubIssue.Milestone;
import org.springframework.data.release.model.ModuleIteration; import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.Project; import org.springframework.data.release.model.Project;
import org.springframework.data.release.model.Projects;
import org.springframework.data.release.model.ReleaseTrains;
import org.springframework.data.release.model.Tracker; import org.springframework.data.release.model.Tracker;
import org.springframework.data.release.model.TrainIteration; import org.springframework.data.release.model.TrainIteration;
import org.springframework.data.release.utils.Logger; import org.springframework.data.release.utils.Logger;
@@ -43,19 +45,18 @@ import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.web.client.HttpStatusCodeException; import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestOperations; import org.springframework.web.client.RestOperations;
import org.springframework.web.util.UriTemplate; import org.springframework.web.util.UriTemplate;
import lombok.RequiredArgsConstructor;
/** /**
* @author Oliver Gierke * @author Oliver Gierke
* @author Mark Paluch * @author Mark Paluch
*/ */
@RequiredArgsConstructor @Component
class GitHubIssueTracker implements IssueTracker { class GitHub implements IssueTracker {
private static final String MILESTONE_URI = "{githubBaseUrl}/repos/spring-projects/{repoName}/milestones?state={state}"; private static final String MILESTONE_URI = "{githubBaseUrl}/repos/spring-projects/{repoName}/milestones?state={state}";
private static final String ISSUES_BY_MILESTONE_AND_ASSIGNEE_URI_TEMPLATE = "{githubBaseUrl}/repos/spring-projects/{repoName}/issues?milestone={id}&state=all&assignee={assignee}"; private static final String ISSUES_BY_MILESTONE_AND_ASSIGNEE_URI_TEMPLATE = "{githubBaseUrl}/repos/spring-projects/{repoName}/issues?milestone={id}&state=all&assignee={assignee}";
@@ -64,13 +65,26 @@ class GitHubIssueTracker implements IssueTracker {
private static final String ISSUE_BY_ID_URI_TEMPLATE = "{githubBaseUrl}/repos/spring-projects/{repoName}/issues/{id}"; private static final String ISSUE_BY_ID_URI_TEMPLATE = "{githubBaseUrl}/repos/spring-projects/{repoName}/issues/{id}";
private static final String ISSUES_URI_TEMPLATE = "{githubBaseUrl}/repos/spring-projects/{repoName}/issues"; private static final String ISSUES_URI_TEMPLATE = "{githubBaseUrl}/repos/spring-projects/{repoName}/issues";
private static final ParameterizedTypeReference<List<GitHubIssue.Milestone>> MILESTONES_TYPE = new ParameterizedTypeReference<List<GitHubIssue.Milestone>>() {}; private static final ParameterizedTypeReference<List<Milestone>> MILESTONES_TYPE = new ParameterizedTypeReference<List<Milestone>>() {};
private static final ParameterizedTypeReference<List<GitHubIssue>> ISSUES_TYPE = new ParameterizedTypeReference<List<GitHubIssue>>() {}; private static final ParameterizedTypeReference<List<GitHubIssue>> ISSUES_TYPE = new ParameterizedTypeReference<List<GitHubIssue>>() {};
private static final ParameterizedTypeReference<GitHubIssue> ISSUE_TYPE = new ParameterizedTypeReference<GitHubIssue>() {}; private static final ParameterizedTypeReference<GitHubIssue> ISSUE_TYPE = new ParameterizedTypeReference<GitHubIssue>() {};
private final RestOperations operations; private final RestOperations operations;
private final Logger logger; private final Logger logger;
private final GitProperties properties; private final GitHubProperties properties;
/**
* @param operations
* @param logger
* @param properties
*/
@Autowired
public GitHub(@Qualifier("tracker") RestOperations operations, Logger logger, GitHubProperties properties) {
this.operations = operations;
this.logger = logger;
this.properties = properties;
}
/* /*
* (non-Javadoc) * (non-Javadoc)
@@ -103,24 +117,29 @@ class GitHubIssueTracker implements IssueTracker {
String repositoryName = GitProject.of(project).getRepositoryName(); String repositoryName = GitProject.of(project).getRepositoryName();
List<Ticket> tickets = new ArrayList<>(); List<Ticket> tickets = new ArrayList<>();
for (String ticketId : ticketIds) {
ticketIds.forEach(ticketId -> {
Map<String, Object> parameters = newUrlTemplateVariables(); Map<String, Object> parameters = newUrlTemplateVariables();
parameters.put("repoName", repositoryName); parameters.put("repoName", repositoryName);
parameters.put("id", ticketId); parameters.put("id", ticketId);
try { try {
GitHubIssue gitHubIssue = operations.exchange(ISSUE_BY_ID_URI_TEMPLATE, HttpMethod.GET, GitHubIssue gitHubIssue = operations.exchange(ISSUE_BY_ID_URI_TEMPLATE, HttpMethod.GET,
new HttpEntity<>(newUserScopedHttpHeaders()), ISSUE_TYPE, parameters).getBody(); new HttpEntity<>(newUserScopedHttpHeaders()), ISSUE_TYPE, parameters).getBody();
tickets.add(toTicket(gitHubIssue)); tickets.add(toTicket(gitHubIssue));
} catch (HttpStatusCodeException e) { } catch (HttpStatusCodeException e) {
if (e.getStatusCode() == HttpStatus.NOT_FOUND) { if (e.getStatusCode() == HttpStatus.NOT_FOUND) {
continue; return;
} }
throw e; throw e;
} }
} });
return tickets; return tickets;
} }
@@ -133,13 +152,13 @@ class GitHubIssueTracker implements IssueTracker {
@Cacheable("changelogs") @Cacheable("changelogs")
public Changelog getChangelogFor(ModuleIteration moduleIteration) { public Changelog getChangelogFor(ModuleIteration moduleIteration) {
Tickets tickets = getIssuesFor(moduleIteration, false).stream().// Tickets tickets = getIssuesFor(moduleIteration, false).//
map(issue -> toTicket(issue)).// map(issue -> toTicket(issue)).//
collect(Tickets.toTicketsCollector()); collect(Tickets.toTicketsCollector());
logger.log(moduleIteration, "Created changelog with %s entries.", tickets.getOverallTotal()); logger.log(moduleIteration, "Created changelog with %s entries.", tickets.getOverallTotal());
return new Changelog(moduleIteration, tickets); return Changelog.of(moduleIteration, tickets);
} }
/* /*
@@ -151,11 +170,6 @@ class GitHubIssueTracker implements IssueTracker {
return project.uses(Tracker.GITHUB); return project.uses(Tracker.GITHUB);
} }
@Cacheable("tickets")
public Tickets getTicketsFor(ModuleIteration iteration) {
return getTicketsFor(iteration, false);
}
@Override @Override
public Tickets getTicketsFor(TrainIteration iteration) { public Tickets getTicketsFor(TrainIteration iteration) {
return getTicketsFor(iteration, false); return getTicketsFor(iteration, false);
@@ -182,14 +196,17 @@ class GitHubIssueTracker implements IssueTracker {
return tickets; return tickets;
} }
/*
* (non-Javadoc)
* @see org.springframework.data.release.tracker.IssueTracker#createReleaseVersion(org.springframework.data.release.model.ModuleIteration)
*/
@Override @Override
public void createReleaseVersion(ModuleIteration moduleIteration) { public void createReleaseVersion(ModuleIteration moduleIteration) {
Assert.notNull(moduleIteration, "ModuleIteration must not be null."); Assert.notNull(moduleIteration, "ModuleIteration must not be null.");
String repositoryName = GitProject.of(moduleIteration.getProject()).getRepositoryName(); String repositoryName = GitProject.of(moduleIteration.getProject()).getRepositoryName();
Optional<Milestone> milestone = findMilestone(moduleIteration, repositoryName);
Optional<GitHubIssue.Milestone> milestone = findMilestone(moduleIteration, repositoryName);
if (milestone.isPresent()) { if (milestone.isPresent()) {
return; return;
@@ -206,13 +223,17 @@ class GitHubIssueTracker implements IssueTracker {
new HttpEntity<Object>(githubMilestone.toMilestone(), httpHeaders), GitHubIssue.Milestone.class, parameters); new HttpEntity<Object>(githubMilestone.toMilestone(), httpHeaders), GitHubIssue.Milestone.class, parameters);
} }
/*
*
* (non-Javadoc)
* @see org.springframework.data.release.tracker.IssueTracker#createReleaseTicket(org.springframework.data.release.model.ModuleIteration)
*/
@Override @Override
public void createReleaseTicket(ModuleIteration moduleIteration) { public void createReleaseTicket(ModuleIteration moduleIteration) {
Assert.notNull(moduleIteration, "ModuleIteration must not be null."); Assert.notNull(moduleIteration, "ModuleIteration must not be null.");
HttpHeaders httpHeaders = newUserScopedHttpHeaders(); HttpHeaders httpHeaders = newUserScopedHttpHeaders();
Tickets tickets = getTicketsFor(moduleIteration); Tickets tickets = getTicketsFor(moduleIteration);
if (tickets.hasReleaseTicket(moduleIteration)) { if (tickets.hasReleaseTicket(moduleIteration)) {
@@ -220,59 +241,61 @@ class GitHubIssueTracker implements IssueTracker {
} }
String repositoryName = GitProject.of(moduleIteration.getProject()).getRepositoryName(); String repositoryName = GitProject.of(moduleIteration.getProject()).getRepositoryName();
Milestone milestone = getMilestone(moduleIteration, repositoryName);
GitHubIssue gitHubIssue = GitHubIssue.of(Tracker.releaseTicketSummary(moduleIteration), milestone);
Map<String, Object> parameters = newUrlTemplateVariables(); Map<String, Object> parameters = newUrlTemplateVariables();
parameters.put("repoName", repositoryName); parameters.put("repoName", repositoryName);
GitHubIssue.Milestone milestone = getMilestone(moduleIteration, repositoryName);
logger.log(moduleIteration, "Creating release ticket…"); logger.log(moduleIteration, "Creating release ticket…");
GitHubIssue gitHubIssue = new GitHubIssue(Tracker.releaseTicketSummary(moduleIteration), milestone);
operations.exchange(ISSUES_URI_TEMPLATE, HttpMethod.POST, new HttpEntity<Object>(gitHubIssue, httpHeaders), operations.exchange(ISSUES_URI_TEMPLATE, HttpMethod.POST, new HttpEntity<Object>(gitHubIssue, httpHeaders),
GitHubIssue.class, parameters).getBody(); GitHubIssue.class, parameters).getBody();
} }
private Tickets getTicketsFor(ModuleIteration moduleIteration, boolean forCurrentUser) { @Cacheable("tickets")
public Tickets getTicketsFor(ModuleIteration iteration) {
List<GitHubIssue> issues = getIssuesFor(moduleIteration, forCurrentUser); return getTicketsFor(iteration, false);
return issues.stream().map(this::toTicket).collect(Tickets.toTicketsCollector());
} }
private List<GitHubIssue> getIssuesFor(ModuleIteration moduleIteration, boolean forCurrentUser) { /*
*
String repositoryName = GitProject.of(moduleIteration.getProject()).getRepositoryName(); * (non-Javadoc)
* @see org.springframework.data.release.jira.IssueTracker#assignTicketToMe(org.springframework.data.release.jira.Ticket)
GitHubIssue.Milestone milestone = getMilestone(moduleIteration, repositoryName); */
@Override
Map<String, Object> parameters = newUrlTemplateVariables(); public void assignTicketToMe(Ticket ticket) {
parameters.put("repoName", repositoryName); logger.log("Ticket", "Skipping ticket assignment for %s", ticket);
parameters.put("id", milestone.getNumber());
if (forCurrentUser) {
parameters.put("assignee", properties.getUsername());
return operations.exchange(ISSUES_BY_MILESTONE_AND_ASSIGNEE_URI_TEMPLATE, HttpMethod.GET,
new HttpEntity<>(newUserScopedHttpHeaders()), ISSUES_TYPE, parameters).getBody();
}
return operations.exchange(ISSUES_BY_MILESTONE_URI_TEMPLATE, HttpMethod.GET,
new HttpEntity<>(newUserScopedHttpHeaders()), ISSUES_TYPE, parameters).getBody();
} }
private GitHubIssue.Milestone getMilestone(ModuleIteration moduleIteration, String repositoryName) { /*
* (non-Javadoc)
* @see org.springframework.data.release.jira.IssueTracker#assignReleaseTicketToMe(org.springframework.data.release.model.ModuleIteration)
*/
@Override
public Ticket assignReleaseTicketToMe(ModuleIteration module) {
Optional<GitHubIssue.Milestone> milestone = findMilestone(moduleIteration, repositoryName); logger.log("Ticket", "Skipping ticket assignment for %s", module);
return milestone return null;
.orElseThrow(() -> new IllegalStateException(String.format("No milestone for %s found containing %s!", //
moduleIteration.getProject().getFullName(), //
moduleIteration.getShortVersionString())));
} }
@Cacheable("milestone") private HttpHeaders newUserScopedHttpHeaders() {
protected Optional<GitHubIssue.Milestone> findMilestone(ModuleIteration moduleIteration, String repositoryName) {
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", properties.getHttpCredentials().toString());
return headers;
}
private Map<String, Object> newUrlTemplateVariables() {
Map<String, Object> parameters = new HashMap<>();
parameters.put("githubBaseUrl", properties.getApiUrl());
return parameters;
}
private Optional<Milestone> findMilestone(ModuleIteration moduleIteration, String repositoryName) {
for (String state : Arrays.asList("close", "open")) { for (String state : Arrays.asList("close", "open")) {
@@ -288,7 +311,7 @@ class GitHubIssueTracker implements IssueTracker {
new HttpEntity<>(newUserScopedHttpHeaders()), MILESTONES_TYPE, parameters).getBody(); new HttpEntity<>(newUserScopedHttpHeaders()), MILESTONES_TYPE, parameters).getBody();
Optional<GitHubIssue.Milestone> milestone = milestones.stream(). // Optional<GitHubIssue.Milestone> milestone = milestones.stream(). //
filter(m -> m.matchesIteration(moduleIteration)). // filter(m -> m.matches(moduleIteration)). //
findFirst(). // findFirst(). //
map(m -> { map(m -> {
logger.log(moduleIteration, "Found milestone %s.", m); logger.log(moduleIteration, "Found milestone %s.", m);
@@ -303,38 +326,50 @@ class GitHubIssueTracker implements IssueTracker {
return Optional.empty(); return Optional.empty();
} }
private HttpHeaders newUserScopedHttpHeaders() { private Tickets getTicketsFor(ModuleIteration moduleIteration, boolean forCurrentUser) {
HttpHeaders headers = new HttpHeaders(); return getIssuesFor(moduleIteration, forCurrentUser).//
headers.set("Authorization", properties.getHttpCredentials().toString()); map(GitHub::toTicket).//
collect(Tickets.toTicketsCollector());
return headers;
} }
private Map<String, Object> newUrlTemplateVariables() { private Stream<GitHubIssue> getIssuesFor(ModuleIteration moduleIteration, boolean forCurrentUser) {
Map<String, Object> parameters = new HashMap<>();
parameters.put("githubBaseUrl", properties.getGithubApiBaseUrl()); String repositoryName = GitProject.of(moduleIteration.getProject()).getRepositoryName();
return parameters;
GitHubIssue.Milestone milestone = getMilestone(moduleIteration, repositoryName);
Map<String, Object> parameters = newUrlTemplateVariables();
parameters.put("repoName", repositoryName);
parameters.put("id", milestone.getNumber());
if (forCurrentUser) {
parameters.put("assignee", properties.getUsername());
return getForIssues(ISSUES_BY_MILESTONE_AND_ASSIGNEE_URI_TEMPLATE, parameters);
}
return getForIssues(ISSUES_BY_MILESTONE_URI_TEMPLATE, parameters);
} }
private Ticket toTicket(GitHubIssue issue) { private Stream<GitHubIssue> getForIssues(String template, Map<String, Object> parameters) {
return operations
.exchange(template, HttpMethod.GET, new HttpEntity<>(newUserScopedHttpHeaders()), ISSUES_TYPE, parameters)
.getBody().stream();
}
private Milestone getMilestone(ModuleIteration moduleIteration, String repositoryName) {
Optional<Milestone> milestone = findMilestone(moduleIteration, repositoryName);
return milestone
.orElseThrow(() -> new IllegalStateException(String.format("No milestone for %s found containing %s!", //
moduleIteration.getProject().getFullName(), //
moduleIteration.getShortVersionString())));
}
private static Ticket toTicket(GitHubIssue issue) {
return new Ticket(issue.getId(), issue.getTitle(), new GithubTicketStatus(issue.getState())); return new Ticket(issue.getId(), issue.getTitle(), new GithubTicketStatus(issue.getState()));
} }
public static void main(String[] args) {
try (ConfigurableApplicationContext context = new ClassPathXmlApplicationContext(
"META-INF/spring/spring-shell-plugin.xml")) {
IssueTracker tracker = context.getBean("gitHubIssueTracker", IssueTracker.class);
TrainIteration iteration = new TrainIteration(ReleaseTrains.CODD, Iteration.SR2);
ModuleIteration module = iteration.getModule(Projects.BUILD);
Changelog changelog = tracker.getChangelogFor(module);
System.out.println(changelog);
System.out.println(tracker.getReleaseTicketFor(module));
}
}
} }

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2014-2016 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
*
* http://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 org.springframework.data.release.issues.github;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import org.springframework.data.release.model.ModuleIteration;
/**
* @author Oliver Gierke
* @author Mark Paluch
*/
@Value
@RequiredArgsConstructor
class GitHubIssue {
String number, title, state;
Object milestone;
public static GitHubIssue of(String title, Milestone milestone) {
return new GitHubIssue(null, title, null, milestone.number);
}
public String getId() {
return number == null ? null : "#".concat(number);
}
/**
* @author Oliver Gierke
* @author Mark Paluch
*/
@Value
static class Milestone {
Long number;
String title, description;
public static Milestone of(String title, String description) {
return new Milestone(null, title, description);
}
public boolean matches(ModuleIteration moduleIteration) {
return title.contains(moduleIteration.getShortVersionString());
}
}
}

View File

@@ -13,19 +13,28 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.data.release.jira; package org.springframework.data.release.issues.github;
import org.springframework.data.release.model.ModuleIteration;
import lombok.Value; import lombok.Value;
import org.springframework.data.release.issues.github.GitHubIssue.Milestone;
import org.springframework.data.release.model.ModuleIteration;
/** /**
* @author Mark Paluch * @author Mark Paluch
*/ */
@Value @Value
class GithubMilestone { class GithubMilestone {
private ModuleIteration module; ModuleIteration module;
public String getDescription() {
return module.getTrain().getName() + " " + module.getIteration().getName();
}
public Milestone toMilestone() {
return Milestone.of(toString(), getDescription());
}
/* /*
* (non-Javadoc) * (non-Javadoc)
@@ -35,12 +44,4 @@ class GithubMilestone {
public String toString() { public String toString() {
return module.getMediumVersionString(); return module.getMediumVersionString();
} }
public String getDescription() {
return module.getTrain().getName() + " " + module.getIteration().getName();
}
public GitHubIssue.Milestone toMilestone() {
return new GitHubIssue.Milestone(toString(), getDescription());
}
} }

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2016 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
*
* http://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 org.springframework.data.release.issues.github;
import lombok.AccessLevel;
import lombok.Data;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.data.release.git.GitProperties;
import org.springframework.data.release.utils.HttpBasicCredentials;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
/**
* @author Oliver Gierke
*/
@Data
@Component
@ConfigurationProperties(prefix = "github")
@RequiredArgsConstructor(onConstructor = @__(@Autowired) )
public class GitHubProperties {
private final @Getter(AccessLevel.NONE) GitProperties gitProperties;
private String apiUrl;
public String getUsername() {
return gitProperties.getUsername();
}
public HttpBasicCredentials getHttpCredentials() {
return gitProperties.getHttpCredentials();
}
@PostConstruct
public void init() {
Assert.hasText(apiUrl, "No GitHub API base url configured!");
}
}

View File

@@ -14,25 +14,42 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.data.release.jira; package org.springframework.data.release.issues.github;
import lombok.AllArgsConstructor; import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import lombok.experimental.FieldDefaults;
import org.springframework.data.release.issues.TicketStatus;
/** /**
* Value object for a GitHub ticket status. * Value object for a GitHub ticket status.
* *
* @author Mark Paluch * @author Mark Paluch
*/ */
@AllArgsConstructor @EqualsAndHashCode
@ToString
@RequiredArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
class GithubTicketStatus implements TicketStatus { class GithubTicketStatus implements TicketStatus {
private final String status; private final String status;
/*
* (non-Javadoc)
* @see org.springframework.data.release.jira.TicketStatus#getLabel()
*/
@Override @Override
public String getLabel() { public String getLabel() {
return status; return status;
} }
/*
* (non-Javadoc)
* @see org.springframework.data.release.jira.TicketStatus#isResolved()
*/
@Override @Override
public boolean isResolved() { public boolean isResolved() {
return "closed".equalsIgnoreCase(status); return "closed".equalsIgnoreCase(status);

View File

@@ -14,19 +14,17 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.data.release.jira; package org.springframework.data.release.issues.jira;
import lombok.Data; import lombok.Value;
/** /**
* Value object for a created Jira issue. * Value object for a created JIRA issue.
* *
* @author Mark Paluch * @author Mark Paluch
* @author Oliver Gierke
*/ */
@Data @Value
public class CreatedJiraIssue { class CreatedJiraIssue {
String id, key;
private String id;
private String key;
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2014 the original author or authors. * Copyright 2014-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -13,19 +13,19 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.data.release.jira; package org.springframework.data.release.issues.jira;
import javax.xml.bind.DatatypeConverter;
import lombok.Value; import lombok.Value;
import javax.xml.bind.DatatypeConverter;
/** /**
* @author Oliver Gierke * @author Oliver Gierke
*/ */
@Value @Value
public class Credentials { class Credentials {
private final String username, password; String username, password;
public String asBase64() { public String asBase64() {
return DatatypeConverter.printBase64Binary(String.format("%s:%s", username, password).getBytes()); return DatatypeConverter.printBase64Binary(String.format("%s:%s", username, password).getBytes());

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.data.release.jira; package org.springframework.data.release.issues.jira;
import java.net.URI; import java.net.URI;
import java.util.ArrayList; import java.util.ArrayList;
@@ -25,19 +25,21 @@ import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.springframework.boot.SpringApplication; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.ParameterizedTypeReference;
import org.springframework.data.release.Application; import org.springframework.data.release.issues.Changelog;
import org.springframework.data.release.jira.JiraIssue.Component; import org.springframework.data.release.issues.Ticket;
import org.springframework.data.release.jira.JiraIssue.Fields; import org.springframework.data.release.issues.Tickets;
import org.springframework.data.release.model.Iteration; import org.springframework.data.release.issues.jira.JiraIssue.Component;
import org.springframework.data.release.issues.jira.JiraIssue.Fields;
import org.springframework.data.release.issues.jira.JiraIssue.Resolution;
import org.springframework.data.release.issues.jira.JiraIssue.Status;
import org.springframework.data.release.model.ModuleIteration; import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.Project; import org.springframework.data.release.model.Project;
import org.springframework.data.release.model.ProjectKey; import org.springframework.data.release.model.ProjectKey;
import org.springframework.data.release.model.ReleaseTrains;
import org.springframework.data.release.model.Tracker; import org.springframework.data.release.model.Tracker;
import org.springframework.data.release.model.TrainIteration; import org.springframework.data.release.model.TrainIteration;
import org.springframework.data.release.utils.Logger; import org.springframework.data.release.utils.Logger;
@@ -48,13 +50,11 @@ import org.springframework.util.Assert;
import org.springframework.web.client.RestOperations; import org.springframework.web.client.RestOperations;
import org.springframework.web.util.UriTemplate; import org.springframework.web.util.UriTemplate;
import lombok.RequiredArgsConstructor;
/** /**
* @author Oliver Gierke * @author Oliver Gierke
* @author Mark Paluch * @author Mark Paluch
*/ */
@RequiredArgsConstructor @org.springframework.stereotype.Component
class Jira implements JiraConnector { class Jira implements JiraConnector {
private static final String BASE_URI = "{jiraBaseUrl}/rest/api/2"; private static final String BASE_URI = "{jiraBaseUrl}/rest/api/2";
@@ -69,9 +69,21 @@ class Jira implements JiraConnector {
private final RestOperations operations; private final RestOperations operations;
private final Logger logger; private final Logger logger;
private final JiraProperties jiraProperties; private final JiraProperties jiraProperties;
/**
* @param operations
* @param logger
* @param jiraProperties
*/
@Autowired
public Jira(@Qualifier("tracker") RestOperations operations, Logger logger, JiraProperties jiraProperties) {
this.operations = operations;
this.logger = logger;
this.jiraProperties = jiraProperties;
}
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see org.springframework.data.release.jira.JiraConnector#reset() * @see org.springframework.data.release.jira.JiraConnector#reset()
@@ -93,13 +105,11 @@ class Jira implements JiraConnector {
JiraIssues issues = getJiraIssues(query, new HttpHeaders(), 0); JiraIssues issues = getJiraIssues(query, new HttpHeaders(), 0);
if (issues.getIssues().isEmpty()) { if (!issues.hasIssues()) {
throw new IllegalArgumentException(String.format("Did not find a release ticket for %s!", moduleIteration)); throw new IllegalArgumentException(String.format("Did not find a release ticket for %s!", moduleIteration));
} }
JiraIssue issue = issues.getIssues().get(0); return toTicket(issues.stream().findFirst().get());
return toTicket(issue);
} }
/* /*
@@ -125,19 +135,27 @@ class Jira implements JiraConnector {
JiraIssues issues = operations.exchange(SEARCH_TEMPLATE, HttpMethod.GET, null, JiraIssues.class, parameters) JiraIssues issues = operations.exchange(SEARCH_TEMPLATE, HttpMethod.GET, null, JiraIssues.class, parameters)
.getBody(); .getBody();
return issues.getIssues().stream().// return issues.stream().//
map(this::toTicket).// map(this::toTicket).//
collect(Collectors.toList()); collect(Collectors.toList());
} }
@Cacheable("tickets") /*
* (non-Javadoc)
* @see org.springframework.data.release.jira.IssueTracker#getTicketsFor(org.springframework.data.release.model.TrainIteration)
*/
@Override @Override
@Cacheable("tickets")
public Tickets getTicketsFor(TrainIteration iteration) { public Tickets getTicketsFor(TrainIteration iteration) {
return getTicketsFor(iteration, false); return getTicketsFor(iteration, false);
} }
@Cacheable("tickets") /*
* (non-Javadoc)
* @see org.springframework.data.release.jira.IssueTracker#getTicketsFor(org.springframework.data.release.model.TrainIteration, boolean)
*/
@Override @Override
@Cacheable("tickets")
public Tickets getTicketsFor(TrainIteration trainIteration, boolean forCurrentUser) { public Tickets getTicketsFor(TrainIteration trainIteration, boolean forCurrentUser) {
JqlQuery query = JqlQuery JqlQuery query = JqlQuery
@@ -208,7 +226,7 @@ class Jira implements JiraConnector {
JiraVersion jiraVersion = new JiraVersion(moduleIteration); JiraVersion jiraVersion = new JiraVersion(moduleIteration);
logger.log(moduleIteration, "Creating Jira release version %s", jiraVersion); logger.log(moduleIteration, "Creating Jira release version %s", jiraVersion);
JiraReleaseVersion jiraReleaseVersion = new JiraReleaseVersion(moduleIteration, jiraVersion); JiraReleaseVersion jiraReleaseVersion = JiraReleaseVersion.of(moduleIteration, jiraVersion);
operations.exchange(VERSIONS_TEMPLATE, HttpMethod.POST, new HttpEntity<Object>(jiraReleaseVersion, httpHeaders), operations.exchange(VERSIONS_TEMPLATE, HttpMethod.POST, new HttpEntity<Object>(jiraReleaseVersion, httpHeaders),
JiraReleaseVersion.class, parameters).getBody(); JiraReleaseVersion.class, parameters).getBody();
@@ -235,6 +253,10 @@ class Jira implements JiraConnector {
return versionsForModuleIteration.stream().findFirst(); return versionsForModuleIteration.stream().findFirst();
} }
/*
* (non-Javadoc)
* @see org.springframework.data.release.jira.IssueTracker#createReleaseTicket(org.springframework.data.release.model.ModuleIteration)
*/
@Override @Override
public void createReleaseTicket(ModuleIteration moduleIteration) { public void createReleaseTicket(ModuleIteration moduleIteration) {
@@ -250,8 +272,7 @@ class Jira implements JiraConnector {
return; return;
} }
Optional<JiraReleaseVersion> jiraReleaseVersion = findJiraReleaseVersion(moduleIteration); findJiraReleaseVersion(moduleIteration).orElseThrow(
jiraReleaseVersion.orElseThrow(
() -> new IllegalStateException(String.format("Did not find a release version for %s", moduleIteration))); () -> new IllegalStateException(String.format("Did not find a release version for %s", moduleIteration)));
JiraComponents jiraComponents = getJiraComponents(moduleIteration.getProjectKey()); JiraComponents jiraComponents = getJiraComponents(moduleIteration.getProjectKey());
@@ -264,6 +285,10 @@ class Jira implements JiraConnector {
CreatedJiraIssue.class, parameters).getBody(); CreatedJiraIssue.class, parameters).getBody();
} }
/*
* (non-Javadoc)
* @see org.springframework.data.release.jira.JiraConnector#assignTicketToMe(org.springframework.data.release.jira.Ticket)
*/
@Override @Override
public void assignTicketToMe(Ticket ticket) { public void assignTicketToMe(Ticket ticket) {
@@ -280,19 +305,16 @@ class Jira implements JiraConnector {
String.class, parameters).getBody(); String.class, parameters).getBody();
} }
private JiraIssue prepareJiraIssueToCreate(ModuleIteration moduleIteration, JiraComponents jiraComponents) { /*
* (non-Javadoc)
* @see org.springframework.data.release.jira.IssueTracker#assignReleaseTicketToMe(org.springframework.data.release.model.ModuleIteration)
*/
@Override
public Ticket assignReleaseTicketToMe(ModuleIteration module) {
JiraIssue jiraIssue = JiraIssue.createTask(); Ticket ticket = getReleaseTicketFor(module);
jiraIssue.project(moduleIteration.getProjectKey()).summary(Tracker.releaseTicketSummary(moduleIteration)) assignTicketToMe(ticket);
.fixVersion(moduleIteration); return ticket;
Fields fields = jiraIssue.getFields();
Optional<JiraComponent> component = jiraComponents.findComponent(INFRASTRUCTURE_COMPONENT_NAME);
component.ifPresent(
jiraComponent -> fields.setComponents(Collections.singletonList(Component.from(jiraComponent.getName()))));
return jiraIssue;
} }
/* /*
@@ -304,8 +326,8 @@ class Jira implements JiraConnector {
// for each module // for each module
for (ModuleIteration moduleIteration : trainIteration) { for (ModuleIteration moduleIteration : trainIteration) {
Tickets tickets = getTicketsFor(moduleIteration);
Tickets tickets = getTicketsFor(moduleIteration);
Ticket releaseTicket = tickets.getReleaseTicket(moduleIteration); Ticket releaseTicket = tickets.getReleaseTicket(moduleIteration);
if (releaseTicket.isResolved()) { if (releaseTicket.isResolved()) {
@@ -366,7 +388,7 @@ class Jira implements JiraConnector {
Tickets tickets = issues.stream().map(this::toTicket).collect(Tickets.toTicketsCollector()); Tickets tickets = issues.stream().map(this::toTicket).collect(Tickets.toTicketsCollector());
logger.log(moduleIteration, "Created changelog with %s entries.", tickets.getOverallTotal()); logger.log(moduleIteration, "Created changelog with %s entries.", tickets.getOverallTotal());
return new Changelog(moduleIteration, tickets); return Changelog.of(moduleIteration, tickets);
} }
/* /*
@@ -391,6 +413,21 @@ class Jira implements JiraConnector {
return JiraComponents.of(components); return JiraComponents.of(components);
} }
private JiraIssue prepareJiraIssueToCreate(ModuleIteration moduleIteration, JiraComponents jiraComponents) {
JiraIssue jiraIssue = JiraIssue.createTask();
jiraIssue.project(moduleIteration.getProjectKey()).summary(Tracker.releaseTicketSummary(moduleIteration))
.fixVersion(moduleIteration);
Fields fields = jiraIssue.getFields();
Optional<JiraComponent> component = jiraComponents.findComponent(INFRASTRUCTURE_COMPONENT_NAME);
component.ifPresent(
jiraComponent -> fields.setComponents(Collections.singletonList(Component.of(jiraComponent.getName()))));
return jiraIssue;
}
private JiraIssues execute(String context, JqlQuery query, HttpHeaders headers, JiraIssuesCallback callback) { private JiraIssues execute(String context, JqlQuery query, HttpHeaders headers, JiraIssuesCallback callback) {
JiraIssues issues; JiraIssues issues;
@@ -458,15 +495,16 @@ class Jira implements JiraConnector {
private Ticket toTicket(JiraIssue issue) { private Ticket toTicket(JiraIssue issue) {
JiraIssue.Fields fields = issue.getFields(); Fields fields = issue.getFields();
JiraTicketStatus jiraTicketStatus; JiraTicketStatus jiraTicketStatus;
if (fields.getStatus() != null && fields.getResolution() != null) {
JiraIssue.Status status = fields.getStatus();
boolean resolved = status.getStatusCategory().getKey().equals("done");
JiraIssue.Resolution resolution = fields.getResolution();
jiraTicketStatus = new JiraTicketStatus(resolved, status.getName(), resolution.getName()); if (fields.hasStatusAndResolution()) {
Status status = fields.getStatus();
boolean resolved = status.getStatusCategory().getKey().equals("done");
Resolution resolution = fields.getResolution();
jiraTicketStatus = JiraTicketStatus.of(resolved, status.getName(), resolution.getName());
} else { } else {
jiraTicketStatus = JiraTicketStatus.UNKNOWN; jiraTicketStatus = JiraTicketStatus.UNKNOWN;
} }
@@ -486,24 +524,9 @@ class Jira implements JiraConnector {
return headers; return headers;
} }
public static void main(String[] args) {
try (ConfigurableApplicationContext context = SpringApplication.run(Application.class, args)) {
JiraConnector tracker = context.getBean(JiraConnector.class);
TrainIteration iteration = new TrainIteration(ReleaseTrains.GOSLING, Iteration.SR1);
ModuleIteration module = iteration.getModule("JPA");
// Changelog changelog = tracker.getChangelogFor(module);
// System.out.println(changelog);
System.out.println(tracker.getReleaseTicketFor(module));
}
}
private Map<String, Object> newUrlTemplateVariables() { private Map<String, Object> newUrlTemplateVariables() {
Map<String, Object> parameters = new HashMap<>(); Map<String, Object> parameters = new HashMap<>();
parameters.put("jiraBaseUrl", jiraProperties.getUrl()); parameters.put("jiraBaseUrl", jiraProperties.getApiUrl());
return parameters; return parameters;
} }

View File

@@ -13,30 +13,20 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.data.release.issues.jira;
package org.springframework.data.release.jira;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Value; import lombok.Value;
/* /**
* Value object to bind REST responses to. * Value object to bind REST responses to.
* *
* @author Mark Paluch * @author Mark Paluch
* @author Oliver Gierke
*/ */
@Value @Value
class JiraComponent { class JiraComponent {
private String id; String id, name;
private String name;
@JsonCreator
public JiraComponent(@JsonProperty("id") String id, @JsonProperty("name") String name) {
this.id = id;
this.name = name;
}
public boolean hasName(String name) { public boolean hasName(String name) {
return this.name.equals(name); return this.name.equals(name);

View File

@@ -14,28 +14,25 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.data.release.jira; package org.springframework.data.release.issues.jira;
import lombok.NonNull;
import lombok.Value;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
import java.util.Optional; import java.util.Optional;
import org.springframework.util.Assert; /**
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NonNull;
/*
* Value object to represent a list of {@link JiraComponent}s. * Value object to represent a list of {@link JiraComponent}s.
*
* @author Mark Paluch * @author Mark Paluch
* @author Oliver Gierke
*/ */
@Data @Value(staticConstructor = "of")
@AllArgsConstructor(access = AccessLevel.PRIVATE)
class JiraComponents implements Iterable<JiraComponent> { class JiraComponents implements Iterable<JiraComponent> {
@NonNull private Collection<JiraComponent> jiraComponents; @NonNull Collection<JiraComponent> jiraComponents;
/** /**
* Try to find a component by its {@code name}. * Try to find a component by its {@code name}.
@@ -47,12 +44,10 @@ class JiraComponents implements Iterable<JiraComponent> {
return jiraComponents.stream().filter(jiraComponent -> jiraComponent.hasName(name)).findFirst(); return jiraComponents.stream().filter(jiraComponent -> jiraComponent.hasName(name)).findFirst();
} }
public static JiraComponents of(Collection<JiraComponent> jiraComponents) { /*
* (non-Javadoc)
Assert.notNull(jiraComponents, "JiraComponents must not be null!"); * @see java.lang.Iterable#iterator()
return new JiraComponents(jiraComponents); */
}
@Override @Override
public Iterator<JiraComponent> iterator() { public Iterator<JiraComponent> iterator() {
return jiraComponents.iterator(); return jiraComponents.iterator();

View File

@@ -13,10 +13,12 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.data.release.jira; package org.springframework.data.release.issues.jira;
import java.util.Optional; import java.util.Optional;
import org.springframework.data.release.issues.IssueTracker;
import org.springframework.data.release.issues.Tickets;
import org.springframework.data.release.model.ModuleIteration; import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.TrainIteration; import org.springframework.data.release.model.TrainIteration;
@@ -24,32 +26,28 @@ import org.springframework.data.release.model.TrainIteration;
* @author Oliver Gierke * @author Oliver Gierke
* @author Mark Paluch * @author Mark Paluch
*/ */
public interface JiraConnector extends IssueTracker { interface JiraConnector extends IssueTracker {
/** /**
* Verifies the state of all {@link Tickets} before releasing. In particular: Check whether the release ticket exists, * Verifies the state of all {@link Tickets} before releasing. In particular: Check whether the release ticket exists,
* check whether all other issue tickets are in a resolved state. * check whether all other issue tickets are in a resolved state.
* *
* @param iteration * @param iteration must not be {@literal null}.
*/ */
void verifyBeforeRelease(TrainIteration iteration); void verifyBeforeRelease(TrainIteration iteration);
/**
* Closes the given {@link TrainIteration}.
*
* @param iteration must not be {@literal null}.
*/
void closeIteration(TrainIteration iteration); void closeIteration(TrainIteration iteration);
/** /**
* Lookup a Jira release version. * Lookup a JIRA release version.
* *
* @param moduleIteration must not be {@literal null}. * @param moduleIteration must not be {@literal null}.
* @return * @return
*/ */
Optional<JiraReleaseVersion> findJiraReleaseVersion(ModuleIteration moduleIteration); Optional<JiraReleaseVersion> findJiraReleaseVersion(ModuleIteration moduleIteration);
/**
* Assigns the ticket to the current user.
*
* @param ticket must not be {@literal null}.
* @param credentials must not be {@literal null}.
*/
void assignTicketToMe(Ticket ticket);
} }

View File

@@ -13,69 +13,32 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.data.release.jira; package org.springframework.data.release.issues.jira;
import lombok.Data;
import lombok.Value;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import org.springframework.data.release.issues.Ticket;
import org.springframework.data.release.model.ModuleIteration; import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.ProjectKey; import org.springframework.data.release.model.ProjectKey;
import org.springframework.data.release.model.Tracker; import org.springframework.data.release.model.Tracker;
import org.springframework.data.release.model.Train; import org.springframework.data.release.model.Train;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/** /**
* Value object to bind REST responses to. * Value object to bind REST responses to.
* *
* @author Oliver Gierke * @author Oliver Gierke
* @author Mark Paluch * @author Mark Paluch
*/ */
@Data @Value
class JiraIssue { class JiraIssue {
private String key; String key;
private Fields fields; Fields fields;
/**
* Returns whether the ticket is only fixed in the given {@link Train}. {@literal false} indicates the {@link Ticket}
* has been resolved for
*
* @param train
* @return
*/
public boolean wasBackportedFrom(Train train) {
List<FixVersions> fixVersions = fields.getFixVersions();
return fixVersions != null && wasBackportedFrom(train, fixVersions);
}
private boolean wasBackportedFrom(Train train, List<FixVersions> fixVersions) {
return fixVersions.size() > 1 && isPresent(train, fixVersions);
}
private boolean isPresent(Train train, List<FixVersions> fixVersions) {
return fixVersions.stream().//
filter(fixVersion -> fixVersion.isTrain(train)).//
findFirst().//
isPresent();
}
/**
* Returns whether the ticket is a release ticket.
*
* @param moduleIteration
* @return
*/
public boolean isReleaseTicket(ModuleIteration moduleIteration) {
List<FixVersions> fixVersions = fields.getFixVersions();
return fixVersions.size() == 1 && isPresent(moduleIteration.getTrain(), fixVersions)
&& fields.hasSummary(Tracker.releaseTicketSummary(moduleIteration));
}
/** /**
* Creates a new Jira issue value object. * Creates a new Jira issue value object.
@@ -83,10 +46,7 @@ class JiraIssue {
* @return * @return
*/ */
public static JiraIssue create() { public static JiraIssue create() {
return new JiraIssue(null, new Fields());
JiraIssue jiraIssue = new JiraIssue();
jiraIssue.setFields(new Fields());
return jiraIssue;
} }
/** /**
@@ -100,6 +60,32 @@ class JiraIssue {
return jiraIssue; return jiraIssue;
} }
/**
* Returns whether the ticket is only fixed in the given {@link Train}. {@literal false} indicates the {@link Ticket}
* has been resolved for
*
* @param train
* @return
*/
public boolean wasBackportedFrom(Train train) {
List<FixVersion> fixVersions = fields.getFixVersions();
return fixVersions != null && wasBackportedFrom(train, fixVersions);
}
/**
* Returns whether the ticket is a release ticket.
*
* @param moduleIteration
* @return
*/
public boolean isReleaseTicket(ModuleIteration moduleIteration) {
List<FixVersion> fixVersions = fields.getFixVersions();
return fixVersions.size() == 1 && isPresent(moduleIteration.getTrain(), fixVersions)
&& fields.hasSummary(Tracker.releaseTicketSummary(moduleIteration));
}
/** /**
* Assign to the user specified in {@link Credentials}. * Assign to the user specified in {@link Credentials}.
* *
@@ -131,7 +117,7 @@ class JiraIssue {
*/ */
public JiraIssue project(ProjectKey projectKey) { public JiraIssue project(ProjectKey projectKey) {
getFields().setProject(new Project(projectKey)); getFields().setProject(Project.from(projectKey));
return this; return this;
} }
@@ -156,18 +142,29 @@ class JiraIssue {
public JiraIssue fixVersion(ModuleIteration moduleIteration) { public JiraIssue fixVersion(ModuleIteration moduleIteration) {
Assert.notNull(moduleIteration, "ModuleIteration must not be null!"); Assert.notNull(moduleIteration, "ModuleIteration must not be null!");
getFields().setFixVersions(Collections.singletonList(new JiraVersion(moduleIteration).toFixVersions())); getFields().setFixVersions(Collections.singletonList(new JiraVersion(moduleIteration).toFixVersion()));
return this; return this;
} }
private static boolean wasBackportedFrom(Train train, List<FixVersion> fixVersions) {
return fixVersions.size() > 1 && isPresent(train, fixVersions);
}
private static boolean isPresent(Train train, List<FixVersion> fixVersions) {
return fixVersions.stream().//
filter(fixVersion -> fixVersion.isTrain(train)).//
findFirst().//
isPresent();
}
@Data @Data
static class Fields { static class Fields {
Project project; Project project;
IssueType issuetype; IssueType issuetype;
String summary; String summary;
List<FixVersions> fixVersions; List<FixVersion> fixVersions;
List<Component> components; List<Component> components;
Status status; Status status;
Resolution resolution; Resolution resolution;
@@ -176,33 +173,31 @@ class JiraIssue {
public boolean hasSummary(String other) { public boolean hasSummary(String other) {
return summary.equals(other); return summary.equals(other);
} }
public boolean hasStatusAndResolution() {
return status != null && resolution != null;
}
} }
@Data @Value
@NoArgsConstructor
@AllArgsConstructor
static class Component { static class Component {
String id; String id, name;
String name;
public static Component from(String name) { public static Component of(String name) {
Assert.hasText(name, "Name must not be empty!"); Assert.hasText(name, "Name must not be empty!");
return new Component(null, name); return new Component(null, name);
} }
} }
@Data @Value
@NoArgsConstructor static class FixVersion {
@AllArgsConstructor
static class FixVersions {
String id; String id, name;
String name;
/** /**
* Returns whether this {@link FixVersions} matches the given {@link Train}. * Returns whether this {@link FixVersion} matches the given {@link Train}.
* *
* @param train * @param train
* @return * @return
@@ -212,9 +207,7 @@ class JiraIssue {
} }
} }
@Data @Value
@NoArgsConstructor
@AllArgsConstructor
static class IssueType { static class IssueType {
public final static IssueType TASK = new IssueType("Task"); public final static IssueType TASK = new IssueType("Task");
@@ -222,50 +215,39 @@ class JiraIssue {
String name; String name;
} }
@Data @Value
@NoArgsConstructor
@AllArgsConstructor
static class Status { static class Status {
String name; String name;
StatusCategory statusCategory; StatusCategory statusCategory;
} }
@Data @Value
@NoArgsConstructor
@AllArgsConstructor
static class Project { static class Project {
String key; String key;
public Project(ProjectKey projectKey) { public static Project from(ProjectKey projectKey) {
this.key = projectKey.getKey(); return new Project(projectKey.getKey());
} }
} }
@Data @Value
@NoArgsConstructor
@AllArgsConstructor
static class Resolution { static class Resolution {
String name; String name;
} }
@Data @Value
@NoArgsConstructor
@AllArgsConstructor
static class StatusCategory { static class StatusCategory {
String key; String key;
} }
@Data @Value
@NoArgsConstructor
@AllArgsConstructor
static class JiraUser { static class JiraUser {
String name; String name, displayName;
String displayName;
static JiraUser from(String username) { static JiraUser from(String username) {
return new JiraUser(username, null); return new JiraUser(username, null);

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2014 the original author or authors. * Copyright 2014-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -13,26 +13,26 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.data.release.jira; package org.springframework.data.release.issues.jira;
import lombok.Getter;
import lombok.Value;
import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import org.springframework.data.release.Streamable; import org.springframework.data.release.Streamable;
import lombok.Data;
/** /**
* @author Oliver Gierke * @author Oliver Gierke
*/ */
@Data @Value
class JiraIssues implements Streamable<JiraIssue> { class JiraIssues implements Streamable<JiraIssue> {
private int startAt; int startAt, maxResult;
private int maxResults; @Getter int total;
private int total; List<JiraIssue> issues = new ArrayList<>();
private List<JiraIssue> issues;
public int getNextStartAt() { public int getNextStartAt() {
return startAt + issues.size(); return startAt + issues.size();
@@ -42,6 +42,10 @@ class JiraIssues implements Streamable<JiraIssue> {
return startAt + issues.size() < total; return startAt + issues.size() < total;
} }
public boolean hasIssues() {
return !issues.isEmpty();
}
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see java.lang.Iterable#iterator() * @see java.lang.Iterable#iterator()

View File

@@ -14,7 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.data.release.jira; package org.springframework.data.release.issues.jira;
import lombok.AccessLevel;
import lombok.Data;
import lombok.Getter;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
@@ -22,12 +26,8 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import lombok.AccessLevel;
import lombok.Data;
import lombok.Getter;
/** /**
* Configurable properties for Git. * Configurable properties for JIRA.
* *
* @author Mark Paluch * @author Mark Paluch
*/ */
@@ -37,14 +37,14 @@ import lombok.Getter;
class JiraProperties { class JiraProperties {
private @Getter(AccessLevel.PRIVATE) String password; private @Getter(AccessLevel.PRIVATE) String password;
private String username, url; private String username, apiUrl;
@PostConstruct @PostConstruct
public void init() { public void init() {
Assert.hasText(username, "No Jira username (jira.username) configured!"); Assert.hasText(username, "No Jira username (jira.username) configured!");
Assert.hasText(password, "No Jira password (jira.password) configured!"); Assert.hasText(password, "No Jira password (jira.password) configured!");
Assert.hasText(url, "No Jira url (jira.url) configured!"); Assert.hasText(apiUrl, "No Jira url (jira.api-url) configured!");
} }
/** /**
@@ -55,5 +55,4 @@ class JiraProperties {
public Credentials getCredentials() { public Credentials getCredentials() {
return new Credentials(username, password); return new Credentials(username, password);
} }
} }

View File

@@ -14,46 +14,30 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.data.release.jira; package org.springframework.data.release.issues.jira;
import lombok.Data; import lombok.Value;
import org.springframework.data.release.model.ModuleIteration; import org.springframework.data.release.model.ModuleIteration;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
/** /**
* Value object to bind REST responses to. * Value object to bind REST responses to.
* *
* @author Mark Paluch * @author Mark Paluch
*/ */
@Data @Value
public class JiraReleaseVersion { class JiraReleaseVersion {
private String id; String id, name, project, description;
private String name;
private String project;
private String description;
@JsonCreator public static JiraReleaseVersion of(ModuleIteration moduleIteration, JiraVersion jiraVersion) {
public JiraReleaseVersion(@JsonProperty("id") String id, @JsonProperty("name") String name,
@JsonProperty("string") String project, @JsonProperty("description") String description) {
this.id = id;
this.name = name;
this.project = project;
this.description = description;
}
public JiraReleaseVersion(ModuleIteration moduleIteration, JiraVersion jiraVersion) {
Assert.notNull(moduleIteration, "ModuleIteration must not be null."); Assert.notNull(moduleIteration, "ModuleIteration must not be null.");
Assert.notNull(jiraVersion, "JiraVersion must not be null."); Assert.notNull(jiraVersion, "JiraVersion must not be null.");
project = moduleIteration.getProjectKey().getKey(); return new JiraReleaseVersion(null, jiraVersion.toString(), moduleIteration.getProjectKey().getKey(),
name = jiraVersion.toString(); jiraVersion.getDescription());
description = jiraVersion.getDescription();
} }
public boolean hasSameNameAs(JiraVersion jiraVersion) { public boolean hasSameNameAs(JiraVersion jiraVersion) {

View File

@@ -13,28 +13,26 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.data.release.jira; package org.springframework.data.release.issues.jira;
import lombok.Value;
import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import org.springframework.data.release.Streamable; import org.springframework.data.release.Streamable;
import lombok.Data;
/** /**
* Value object to bind REST responses to. * Value object to bind REST responses to.
* *
* @author Mark Paluch * @author Mark Paluch
*/ */
@Data @Value
class JiraReleaseVersions implements Streamable<JiraReleaseVersion> { class JiraReleaseVersions implements Streamable<JiraReleaseVersion> {
private int startAt; int startAt, maxResults, total;
private int maxResults; List<JiraReleaseVersion> values = new ArrayList<>();
private int total;
private List<JiraReleaseVersion> values;
public int getNextStartAt() { public int getNextStartAt() {
return startAt + values.size(); return startAt + values.size();

View File

@@ -14,21 +14,25 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.data.release.jira; package org.springframework.data.release.issues.jira;
import lombok.RequiredArgsConstructor; import lombok.NonNull;
import lombok.Value;
import org.springframework.data.release.issues.TicketStatus;
/** /**
* @author Mark Paluch * @author Mark Paluch
* @author Oliver Gierke
*/ */
@RequiredArgsConstructor @Value(staticConstructor = "of")
public class JiraTicketStatus implements TicketStatus { class JiraTicketStatus implements TicketStatus {
public static final JiraTicketStatus UNKNOWN = new JiraTicketStatus(false, "unknown", null); public static final JiraTicketStatus UNKNOWN = JiraTicketStatus.of(false, "unknown", null);
private final boolean resolved; boolean resolved;
private final String status; @NonNull String status;
private final String resolution; String resolution;
/* /*
* (non-Javadoc) * (non-Javadoc)

View File

@@ -13,20 +13,20 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.data.release.jira; package org.springframework.data.release.issues.jira;
import org.springframework.data.release.jira.JiraIssue.FixVersions;
import org.springframework.data.release.model.ModuleIteration;
import lombok.Value; import lombok.Value;
import org.springframework.data.release.issues.jira.JiraIssue.FixVersion;
import org.springframework.data.release.model.ModuleIteration;
/** /**
* @author Oliver Gierke * @author Oliver Gierke
*/ */
@Value @Value
class JiraVersion { class JiraVersion {
private ModuleIteration module; ModuleIteration module;
/* /*
* (non-Javadoc) * (non-Javadoc)
@@ -41,7 +41,7 @@ class JiraVersion {
return module.getTrain().getName() + " " + module.getIteration().getName(); return module.getTrain().getName() + " " + module.getIteration().getName();
} }
public FixVersions toFixVersions() { public FixVersion toFixVersion() {
return new FixVersions(null, toString()); return new FixVersion(null, toString());
} }
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2014 the original author or authors. * Copyright 2014-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -13,7 +13,9 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.data.release.jira; package org.springframework.data.release.issues.jira;
import lombok.Value;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@@ -22,10 +24,9 @@ import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.springframework.data.release.model.ModuleIteration; import org.springframework.data.release.model.ModuleIteration;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import lombok.Value;
/** /**
* @author Oliver Gierke * @author Oliver Gierke
* @author Mark Paluch * @author Mark Paluch
@@ -38,16 +39,16 @@ class JqlQuery {
private final String query; private final String query;
public JqlQuery and(String clause) { /**
return new JqlQuery(String.format("(%s) AND %s", query, clause)); * Creates a new {@link JqlQuery} for the given {@link ModuleIteration}.
} *
* @param iteration must not be {@literal null}.
public JqlQuery orderBy(String orderBy) { * @return
return new JqlQuery(String.format("%s ORDER BY %s", query, orderBy)); */
}
public static JqlQuery from(ModuleIteration iteration) { public static JqlQuery from(ModuleIteration iteration) {
Assert.notNull(iteration, "Module iteration must not be null!");
JiraVersion version = new JiraVersion(iteration); JiraVersion version = new JiraVersion(iteration);
return new JqlQuery(String.format(PROJECT_VERSION_TEMPLATE, iteration.getProjectKey(), version)); return new JqlQuery(String.format(PROJECT_VERSION_TEMPLATE, iteration.getProjectKey(), version));
@@ -72,6 +73,18 @@ class JqlQuery {
return new JqlQuery(StringUtils.collectionToDelimitedString(parts, " OR ")); return new JqlQuery(StringUtils.collectionToDelimitedString(parts, " OR "));
} }
public JqlQuery and(String clause) {
return new JqlQuery(String.format("(%s) AND %s", query, clause));
}
public JqlQuery orderBy(String orderBy) {
return new JqlQuery(String.format("%s ORDER BY %s", query, orderBy));
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override @Override
public String toString() { public String toString() {
return query; return query;

View File

@@ -1,91 +0,0 @@
/*
* Copyright 2014-2016 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
*
* http://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 org.springframework.data.release.jira;
import org.springframework.data.release.model.ModuleIteration;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Value;
/**
* @author Oliver Gierke
* @author Mark Paluch
*/
@Value
class GitHubIssue {
private String number;
private String title;
private String state;
private Object milestone;
@JsonCreator
public GitHubIssue(@JsonProperty("number") String number, @JsonProperty("title") String title,
@JsonProperty("state") String state, @JsonProperty("milestone") Object milestone) {
this.number = number;
this.title = title;
this.state = state;
this.milestone = milestone;
}
public GitHubIssue(String title, Milestone milestone) {
this.title = title;
this.milestone = milestone.getNumber();
this.number = null;
this.state = null;
}
public String getId() {
if (number == null) {
return null;
}
return "#".concat(number);
}
/**
* @author Oliver Gierke
* @author Mark Paluch
*/
@Value
static class Milestone {
private final String title;
private final Long number;
private final String description;
@JsonCreator
public Milestone(@JsonProperty("title") String title, @JsonProperty("number") Long number,
@JsonProperty("description") String description) {
this.title = title;
this.number = number;
this.description = description;
}
public Milestone(String title, String description) {
this.number = null;
this.title = title;
this.description = description;
}
public boolean matchesIteration(ModuleIteration moduleIteration) {
return title.contains(moduleIteration.getShortVersionString());
}
}
}

View File

@@ -24,8 +24,8 @@ import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.git.GitOperations; import org.springframework.data.release.git.GitOperations;
import org.springframework.data.release.io.Workspace; import org.springframework.data.release.io.Workspace;
import org.springframework.data.release.jira.Changelog; import org.springframework.data.release.issues.Changelog;
import org.springframework.data.release.jira.IssueTracker; import org.springframework.data.release.issues.IssueTracker;
import org.springframework.data.release.model.Iteration; import org.springframework.data.release.model.Iteration;
import org.springframework.data.release.model.ModuleIteration; import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.Project; import org.springframework.data.release.model.Project;

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2015 the original author or authors. * Copyright 2015-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.data.release.utils;
import java.util.Collection; import java.util.Collection;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collector;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.springframework.data.release.Streamable; import org.springframework.data.release.Streamable;
@@ -62,16 +63,22 @@ public class ExecutionUtils {
* @return * @return
*/ */
public static <T, S> Collection<S> runAndReturn(Streamable<T> streamable, Function<T, S> function) { public static <T, S> Collection<S> runAndReturn(Streamable<T> streamable, Function<T, S> function) {
return runAndReturn(streamable, function, Collectors.toList());
}
public static <T, S, R> R runAndReturn(Streamable<T> streamable, Function<T, S> function,
Collector<? super S, ?, R> collector) {
Assert.notNull(streamable, "Iterable must not be null!"); Assert.notNull(streamable, "Iterable must not be null!");
Assert.notNull(function, "Consumer must not be null!"); Assert.notNull(function, "Consumer must not be null!");
return streamable.stream().// return streamable.stream().//
map(it -> CompletableFuture.supplyAsync(() -> function.apply(it))).// map(it -> CompletableFuture.supplyAsync(() -> function.apply(it))).//
filter(it -> it != null).//
collect(Collectors.toList()).// collect(Collectors.toList()).//
stream().// stream().//
map(future -> future.join()).// map(future -> future.join()).//
collect(Collectors.toList()); collect(collector);
} }
public static interface ConsumerWithException<T> { public static interface ConsumerWithException<T> {

View File

@@ -1,7 +1,7 @@
io.work-dir=~/temp/spring-data-shell/workspace io.work-dir=~/temp/spring-data-shell/workspace
# Maven setup # Maven setup
maven.localRepository=~/temp/spring-data-shell/repository maven.local-repository=~/temp/spring-data-shell/repository
maven.plugins.versions=org.codehaus.mojo:versions-maven-plugin:2.2 maven.plugins.versions=org.codehaus.mojo:versions-maven-plugin:2.2
deployment.server.uri=https://repo.spring.io deployment.server.uri=https://repo.spring.io
@@ -12,4 +12,10 @@ deployment.username=buildmaster
# GPG setup # GPG setup
deployment.gpg.executable=/usr/local/bin/gpg2 deployment.gpg.executable=/usr/local/bin/gpg2
# deployment.gpg.keyname # deployment.gpg.keyname
# deployment.gpg.password # deployment.gpg.password
# JIRA
jira.api-url=https://jira.spring.io
# GitHub
github.api-url=https://api.github.com

View File

@@ -30,7 +30,6 @@ import org.springframework.data.release.model.Phase;
import org.springframework.data.release.model.Projects; import org.springframework.data.release.model.Projects;
import org.springframework.data.release.model.ReleaseTrains; import org.springframework.data.release.model.ReleaseTrains;
import org.springframework.data.release.model.TrainIteration; import org.springframework.data.release.model.TrainIteration;
import org.springframework.data.release.model.UpdateInformation;
import org.xmlbeam.ProjectionFactory; import org.xmlbeam.ProjectionFactory;
import org.xmlbeam.io.XBFileIO; import org.xmlbeam.io.XBFileIO;
@@ -75,7 +74,7 @@ public class MavenIntegrationTests extends AbstractIntegrationTests {
TrainIteration iteration = new TrainIteration(ReleaseTrains.HOPPER, Iteration.M1); TrainIteration iteration = new TrainIteration(ReleaseTrains.HOPPER, Iteration.M1);
ModuleIteration build = iteration.getModule(Projects.BUILD); ModuleIteration build = iteration.getModule(Projects.BUILD);
UpdateInformation information = new UpdateInformation(iteration, Phase.PREPARE); UpdateInformation information = UpdateInformation.of(iteration, Phase.PREPARE);
maven.updateProjectDescriptors(build, information); maven.updateProjectDescriptors(build, information);
maven.prepareVersion(build, Phase.PREPARE); maven.prepareVersion(build, Phase.PREPARE);

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2015 the original author or authors. * Copyright 2015-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.data.release.model; package org.springframework.data.release.build;
import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*; import static org.junit.Assert.*;
@@ -21,6 +21,11 @@ import static org.junit.Assert.*;
import java.util.Arrays; import java.util.Arrays;
import org.junit.Test; import org.junit.Test;
import org.springframework.data.release.model.Iteration;
import org.springframework.data.release.model.Phase;
import org.springframework.data.release.model.Projects;
import org.springframework.data.release.model.ReleaseTrains;
import org.springframework.data.release.model.TrainIteration;
/** /**
* Unit tests for {@link UpdateInformation}. * Unit tests for {@link UpdateInformation}.
@@ -33,17 +38,17 @@ public class UpdateInformationUnitTests {
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void rejectsNullTrainIteration() { public void rejectsNullTrainIteration() {
new UpdateInformation(null, Phase.CLEANUP); UpdateInformation.of(null, Phase.CLEANUP);
} }
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void rejectsNullPhase() { public void rejectsNullPhase() {
new UpdateInformation(hopperM1, null); UpdateInformation.of(hopperM1, null);
} }
@Test @Test
public void exposesMilestoneRepositoryForMilestone() { public void exposesMilestoneRepositoryForMilestone() {
assertThat(new UpdateInformation(hopperM1, Phase.PREPARE).getRepository().getId(), is("spring-libs-milestone")); assertThat(UpdateInformation.of(hopperM1, Phase.PREPARE).getRepository().getId(), is("spring-libs-milestone"));
} }
@Test @Test
@@ -51,7 +56,7 @@ public class UpdateInformationUnitTests {
Arrays.asList(Iteration.GA, Iteration.SR1).forEach(iteration -> { Arrays.asList(Iteration.GA, Iteration.SR1).forEach(iteration -> {
TrainIteration trainIteration = new TrainIteration(ReleaseTrains.HOPPER, iteration); TrainIteration trainIteration = new TrainIteration(ReleaseTrains.HOPPER, iteration);
assertThat(new UpdateInformation(trainIteration, Phase.PREPARE).getRepository().getId(), assertThat(UpdateInformation.of(trainIteration, Phase.PREPARE).getRepository().getId(),
is("spring-libs-release")); is("spring-libs-release"));
}); });
} }
@@ -59,10 +64,10 @@ public class UpdateInformationUnitTests {
@Test @Test
public void calculatesProjectVersionToSetCorrectly() { public void calculatesProjectVersionToSetCorrectly() {
UpdateInformation updateInformation = new UpdateInformation(hopperM1, Phase.PREPARE); UpdateInformation updateInformation = UpdateInformation.of(hopperM1, Phase.PREPARE);
assertThat(updateInformation.getProjectVersionToSet(Projects.JPA).toString(), is("1.10.0.M1")); assertThat(updateInformation.getProjectVersionToSet(Projects.JPA).toString(), is("1.10.0.M1"));
updateInformation = new UpdateInformation(hopperM1, Phase.CLEANUP); updateInformation = UpdateInformation.of(hopperM1, Phase.CLEANUP);
assertThat(updateInformation.getProjectVersionToSet(Projects.JPA).toString(), is("1.10.0.BUILD-SNAPSHOT")); assertThat(updateInformation.getProjectVersionToSet(Projects.JPA).toString(), is("1.10.0.BUILD-SNAPSHOT"));
} }
} }

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.data.release.jira; package org.springframework.data.release.issues.github;
import static com.github.tomakehurst.wiremock.client.WireMock.*; import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.*; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.*;
@@ -25,25 +25,31 @@ import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.junit.Assume;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.AbstractIntegrationTests; import org.springframework.data.release.AbstractIntegrationTests;
import org.springframework.data.release.issues.Ticket;
import org.springframework.data.release.issues.github.GitHub;
import org.springframework.data.release.issues.github.GitHubProperties;
import org.springframework.data.release.model.Iteration; import org.springframework.data.release.model.Iteration;
import org.springframework.data.release.model.ModuleIteration; import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.Projects; import org.springframework.data.release.model.Projects;
import org.springframework.data.release.model.ReleaseTrains; import org.springframework.data.release.model.ReleaseTrains;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder;
import com.github.tomakehurst.wiremock.common.ClasspathFileSource; import com.github.tomakehurst.wiremock.common.ClasspathFileSource;
import com.github.tomakehurst.wiremock.junit.WireMockRule; import com.github.tomakehurst.wiremock.junit.WireMockRule;
/** /**
* Integration Tests for {@link GitHubIssueTracker} using a local {@link WireMockRule} server. * Integration Tests for {@link GitHub} using a local {@link WireMockRule} server.
* *
* @author Mark Paluch * @author Mark Paluch
*/ */
@@ -59,11 +65,17 @@ public class GitHubIssueTrackerIntegrationTests extends AbstractIntegrationTests
@Rule public ExpectedException expectedException = ExpectedException.none(); @Rule public ExpectedException expectedException = ExpectedException.none();
@Autowired IssueTracker github; @Autowired GitHub github;
@Autowired GitHubProperties properties;
@Before @Before
public void before() throws Exception { public void before() throws Exception {
UriComponents uriComponents = UriComponentsBuilder.fromUriString(properties.getApiUrl()).build();
Assume.assumeThat(uriComponents.getHost(), is("localhost"));
github.reset(); github.reset();
} }
/** /**

View File

@@ -13,12 +13,13 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.data.release.jira; package org.springframework.data.release.issues.github;
import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import org.junit.Test; import org.junit.Test;
import org.springframework.data.release.issues.github.GithubMilestone;
import org.springframework.data.release.model.Iteration; import org.springframework.data.release.model.Iteration;
import org.springframework.data.release.model.ModuleIteration; import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.ReleaseTrains; import org.springframework.data.release.model.ReleaseTrains;

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.data.release.jira; package org.springframework.data.release.issues.jira;
import static org.hamcrest.core.Is.*; import static org.hamcrest.core.Is.*;
import static org.junit.Assert.*; import static org.junit.Assert.*;
@@ -22,6 +22,8 @@ import static org.junit.Assert.*;
import java.util.Collections; import java.util.Collections;
import org.junit.Test; import org.junit.Test;
import org.springframework.data.release.issues.jira.JiraComponent;
import org.springframework.data.release.issues.jira.JiraComponents;
/** /**
* Unit tests for {@link JiraComponents}. * Unit tests for {@link JiraComponents}.

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.data.release.jira; package org.springframework.data.release.issues.jira;
import static com.github.tomakehurst.wiremock.client.WireMock.*; import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.*; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.*;
@@ -26,12 +26,18 @@ import java.util.Collection;
import java.util.Optional; import java.util.Optional;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.junit.Assume;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.AbstractIntegrationTests; import org.springframework.data.release.AbstractIntegrationTests;
import org.springframework.data.release.issues.Ticket;
import org.springframework.data.release.issues.jira.Jira;
import org.springframework.data.release.issues.jira.JiraConnector;
import org.springframework.data.release.issues.jira.JiraProperties;
import org.springframework.data.release.issues.jira.JiraReleaseVersion;
import org.springframework.data.release.model.Iteration; import org.springframework.data.release.model.Iteration;
import org.springframework.data.release.model.ModuleIteration; import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.ProjectKey; import org.springframework.data.release.model.ProjectKey;
@@ -39,6 +45,8 @@ import org.springframework.data.release.model.Projects;
import org.springframework.data.release.model.ReleaseTrains; import org.springframework.data.release.model.ReleaseTrains;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder;
import com.github.tomakehurst.wiremock.common.ClasspathFileSource; import com.github.tomakehurst.wiremock.common.ClasspathFileSource;
@@ -49,7 +57,7 @@ import com.github.tomakehurst.wiremock.junit.WireMockRule;
* *
* @author Mark Paluch * @author Mark Paluch
*/ */
public class JiraIntegrationTests extends AbstractIntegrationTests { public class JiraConnectorIntegrationTests extends AbstractIntegrationTests {
public static final String CREATE_ISSUE_URI = "/rest/api/2/issue"; public static final String CREATE_ISSUE_URI = "/rest/api/2/issue";
public static final String CREATE_VERSION_URI = "/rest/api/2/version"; public static final String CREATE_VERSION_URI = "/rest/api/2/version";
@@ -64,9 +72,14 @@ public class JiraIntegrationTests extends AbstractIntegrationTests {
@Rule public ExpectedException expectedException = ExpectedException.none(); @Rule public ExpectedException expectedException = ExpectedException.none();
@Autowired JiraConnector jira; @Autowired JiraConnector jira;
@Autowired JiraProperties properties;
@Before @Before
public void before() throws Exception { public void before() throws Exception {
UriComponents uriComponents = UriComponentsBuilder.fromUriString(properties.getApiUrl()).build();
Assume.assumeThat(uriComponents.getHost(), is("localhost"));
jira.reset(); jira.reset();
} }

View File

@@ -13,12 +13,13 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.data.release.jira; package org.springframework.data.release.issues.jira;
import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import org.junit.Test; import org.junit.Test;
import org.springframework.data.release.issues.jira.JiraVersion;
import org.springframework.data.release.model.Iteration; import org.springframework.data.release.model.Iteration;
import org.springframework.data.release.model.ModuleIteration; import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.ReleaseTrains; import org.springframework.data.release.model.ReleaseTrains;

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.data.release.jira; package org.springframework.data.release.issues.jira;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*; import static org.junit.Assert.*;
@@ -23,6 +23,9 @@ import static org.springframework.test.util.AssertionErrors.fail;
import java.util.Collections; import java.util.Collections;
import org.junit.Test; import org.junit.Test;
import org.springframework.data.release.issues.Ticket;
import org.springframework.data.release.issues.Tickets;
import org.springframework.data.release.issues.jira.JiraTicketStatus;
import org.springframework.data.release.model.Iteration; import org.springframework.data.release.model.Iteration;
import org.springframework.data.release.model.ReleaseTrains; import org.springframework.data.release.model.ReleaseTrains;
@@ -93,5 +96,4 @@ public class TicketsUnitTests {
.getReleaseTickets(ReleaseTrains.HOPPER.getModuleIteration(Iteration.GA, "JPA").getTrainIteration()); .getReleaseTickets(ReleaseTrains.HOPPER.getModuleIteration(Iteration.GA, "JPA").getTrainIteration());
assertThat(result.getTickets().contains(ticket), is(true)); assertThat(result.getTickets().contains(ticket), is(true));
} }
} }

View File

@@ -2,11 +2,12 @@
logging.level.org.springframework=WARN logging.level.org.springframework=WARN
logging.level.org.springframework.data.release=INFO logging.level.org.springframework.data.release=INFO
logging.level.org.springframework.web.client=TRACE logging.level.org.springframework.web.client=TRACE
logging.level.org.springframework.http=DEBUG
# Deployment # Deployment
deployment.repository-prefix=test- deployment.repository-prefix=test-
jira.username=dummy jira.username=dummy
jira.password=dummy jira.password=dummy
jira.url=http://localhost:8888 jira.api-url=http://localhost:8888
github.api.url=http://localhost:8888 github.api-url=http://localhost:8888