#19 - Made command classes package protected.

Introduced customized execution strategy that makes methods on command classes accessible before executing them. This allows to make command classes package protected.

Introduced TimedCommand base class to output the time a command execution takes.
This commit is contained in:
Oliver Gierke
2016-04-01 16:44:51 +02:00
parent 802c706f4e
commit e09f5434ef
12 changed files with 249 additions and 58 deletions

View File

@@ -50,7 +50,7 @@ public class BootShim {
}
private void configureApplicationContext(ConfigurableApplicationContext annctx) {
this.createAndRegisterBeanDefinition(annctx, JLineShellComponent.class, "shell");
this.createAndRegisterBeanDefinition(annctx, CustomShellComponent.class, "shell");
annctx.getBeanFactory().registerSingleton("commandLine", commandLine);
}

View File

@@ -0,0 +1,112 @@
/*
* 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;
import java.lang.reflect.Method;
import java.util.logging.Logger;
import org.springframework.shell.core.ExecutionProcessor;
import org.springframework.shell.core.ExecutionStrategy;
import org.springframework.shell.core.JLineShellComponent;
import org.springframework.shell.core.SimpleExecutionStrategy;
import org.springframework.shell.event.ParseResult;
import org.springframework.shell.support.logging.HandlerUtils;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
/**
* Extension of {@link JLineShellComponent} to customize the {@link ExecutionStrategy} to one that can deal with package
* protected command classes.
*
* @author Oliver Gierke
* @see https://github.com/spring-projects/spring-shell/pull/93
*/
class CustomShellComponent extends JLineShellComponent {
private final ExecutionStrategy executionStrategy = new CustomExecutionStrategy();
/*
* (non-Javadoc)
* @see org.springframework.shell.core.JLineShellComponent#getExecutionStrategy()
*/
@Override
protected ExecutionStrategy getExecutionStrategy() {
return executionStrategy;
}
/**
* Effectively a copy of {@link SimpleExecutionStrategy} but with the tweaks provided in PR 93 for Spring shell to
* enable execution of package protected command classes.
*
* @author Oliver Gierke
* @see https://github.com/spring-projects/spring-shell/pull/93
*/
static class CustomExecutionStrategy implements ExecutionStrategy {
private static final Logger logger = HandlerUtils.getLogger(SimpleExecutionStrategy.class);
private final Class<?> mutex = SimpleExecutionStrategy.class;
public Object execute(ParseResult parseResult) throws RuntimeException {
Assert.notNull(parseResult, "Parse result required");
synchronized (mutex) {
Assert.isTrue(isReadyForCommands(), "SimpleExecutionStrategy not yet ready for commands");
Object target = parseResult.getInstance();
if (target instanceof ExecutionProcessor) {
ExecutionProcessor processor = ((ExecutionProcessor) target);
parseResult = processor.beforeInvocation(parseResult);
try {
Object result = invoke(parseResult);
processor.afterReturningInvocation(parseResult, result);
return result;
} catch (Throwable th) {
processor.afterThrowingInvocation(parseResult, th);
return handleThrowable(th);
}
} else {
return invoke(parseResult);
}
}
}
private Object invoke(ParseResult parseResult) {
try {
Method method = parseResult.getMethod();
ReflectionUtils.makeAccessible(method);
return ReflectionUtils.invokeMethod(method, parseResult.getInstance(), parseResult.getArguments());
} catch (Throwable th) {
logger.severe("Command failed " + th);
return handleThrowable(th);
}
}
private Object handleThrowable(Throwable th) {
if (th instanceof Error) {
throw ((Error) th);
}
if (th instanceof RuntimeException) {
throw ((RuntimeException) th);
}
throw new RuntimeException(th);
}
public boolean isReadyForCommands() {
return true;
}
public void terminate() {}
}
}

View File

@@ -0,0 +1,68 @@
/*
* 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;
import org.springframework.shell.core.CommandMarker;
import org.springframework.shell.core.ExecutionProcessor;
import org.springframework.shell.event.ParseResult;
import org.springframework.util.StopWatch;
/**
* Base class for command implementations who want to get their execution time logged.
*
* @author Oliver Gierke
*/
public abstract class TimedCommand implements ExecutionProcessor, CommandMarker {
private StopWatch watch;
/*
* (non-Javadoc)
* @see org.springframework.shell.core.ExecutionProcessor#beforeInvocation(org.springframework.shell.event.ParseResult)
*/
@Override
public ParseResult beforeInvocation(ParseResult invocationContext) {
watch = new StopWatch();
watch.start();
return invocationContext;
}
/*
* (non-Javadoc)
* @see org.springframework.shell.core.ExecutionProcessor#afterReturningInvocation(org.springframework.shell.event.ParseResult, java.lang.Object)
*/
@Override
public void afterReturningInvocation(ParseResult invocationContext, Object result) {
stopAndLog();
}
/*
* (non-Javadoc)
* @see org.springframework.shell.core.ExecutionProcessor#afterThrowingInvocation(org.springframework.shell.event.ParseResult, java.lang.Throwable)
*/
@Override
public void afterThrowingInvocation(ParseResult invocationContext, Throwable thrown) {
stopAndLog();
}
private void stopAndLog() {
watch.stop();
System.out.println(String.format("Took: %s sec.", watch.getTotalTimeSeconds()));
}
}

View File

@@ -15,13 +15,15 @@
*/
package org.springframework.data.release.announcement;
import lombok.AccessLevel;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.CliComponent;
import org.springframework.data.release.TimedCommand;
import org.springframework.data.release.model.TrainIteration;
import org.springframework.shell.core.CommandMarker;
import org.springframework.shell.core.annotation.CliCommand;
import org.springframework.shell.core.annotation.CliOption;
@@ -31,13 +33,14 @@ import org.springframework.shell.core.annotation.CliOption;
* @author Oliver Gierke
*/
@CliComponent
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
@RequiredArgsConstructor(onConstructor = @__(@Autowired) )
public class AnnouncementCommands implements CommandMarker {
class AnnouncementCommands extends TimedCommand {
private final @NonNull AnnouncementOperations operations;
@NonNull AnnouncementOperations operations;
@CliCommand("announcement")
public void distribute(@CliOption(key = "", mandatory = true) TrainIteration iteration) throws Exception {
public void announce(@CliOption(key = "", mandatory = true) TrainIteration iteration) throws Exception {
System.out.println(operations.getProjectBulletpoints(iteration));
}
}

View File

@@ -15,13 +15,15 @@
*/
package org.springframework.data.release.build;
import lombok.AccessLevel;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
import java.io.IOException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.CliComponent;
import org.springframework.data.release.TimedCommand;
import org.springframework.data.release.io.Workspace;
import org.springframework.data.release.utils.Logger;
import org.springframework.shell.core.annotation.CliCommand;
@@ -29,13 +31,13 @@ import org.springframework.shell.core.annotation.CliCommand;
/**
* @author Oliver Gierke
*/
@CliComponent
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
@RequiredArgsConstructor(onConstructor = @__(@Autowired) )
public class BuildCommands {
class BuildCommands extends TimedCommand {
private final @NonNull BuildOperations build;
private final @NonNull Workspace workspace;
private final @NonNull Logger logger;
@NonNull BuildOperations build;
@NonNull Workspace workspace;
@NonNull Logger logger;
/**
* Removes all Spring Data artifacts from the local repository.

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");
* you may not use this file except in compliance with the License.
@@ -18,9 +18,9 @@ package org.springframework.data.release.cli;
import java.util.stream.Collectors;
import org.springframework.data.release.CliComponent;
import org.springframework.data.release.TimedCommand;
import org.springframework.data.release.model.ReleaseTrains;
import org.springframework.data.release.model.Train;
import org.springframework.shell.core.CommandMarker;
import org.springframework.shell.core.annotation.CliCommand;
import org.springframework.shell.core.annotation.CliOption;
@@ -28,7 +28,7 @@ import org.springframework.shell.core.annotation.CliOption;
* @author Oliver Gierke
*/
@CliComponent
public class ModelCommands implements CommandMarker {
class ModelCommands extends TimedCommand {
@CliCommand(value = "trains", help = "Displays all release trains or contents of them if a name is provided")
public String train(@CliOption(key = { "", "train" }) Train train) {

View File

@@ -17,12 +17,16 @@ package org.springframework.data.release.cli;
import static org.springframework.data.release.model.Projects.*;
import lombok.AccessLevel;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.CliComponent;
import org.springframework.data.release.TimedCommand;
import org.springframework.data.release.build.BuildOperations;
import org.springframework.data.release.deployment.DeploymentInformation;
import org.springframework.data.release.deployment.DeploymentOperations;
@@ -36,7 +40,6 @@ import org.springframework.data.release.model.Projects;
import org.springframework.data.release.model.ReleaseTrains;
import org.springframework.data.release.model.Train;
import org.springframework.data.release.model.TrainIteration;
import org.springframework.shell.core.CommandMarker;
import org.springframework.shell.core.annotation.CliCommand;
import org.springframework.shell.core.annotation.CliOption;
import org.springframework.util.Assert;
@@ -46,12 +49,13 @@ import org.springframework.util.Assert;
*/
@CliComponent
@RequiredArgsConstructor(onConstructor = @__(@Autowired) )
public class ReleaseCommands implements CommandMarker {
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
class ReleaseCommands extends TimedCommand {
private final GitOperations git;
private final ReleaseOperations misc;
private final DeploymentOperations deployment;
private final BuildOperations build;
@NonNull GitOperations git;
@NonNull ReleaseOperations misc;
@NonNull DeploymentOperations deployment;
@NonNull BuildOperations build;
@CliCommand("release predict")
public String predictTrainAndIteration() throws Exception {
@@ -61,13 +65,6 @@ public class ReleaseCommands implements CommandMarker {
orElse(null);
}
public String getTrainNameForCommonsVersion(ArtifactVersion version) {
return ReleaseTrains.TRAINS.stream().//
filter(train -> version.toString().startsWith(train.getModule(COMMONS).getVersion().toString())).//
findFirst().map(Train::getName).orElse(null);
}
/**
* Prepares the release of the given iteration of the given train.
*
@@ -160,4 +157,11 @@ public class ReleaseCommands implements CommandMarker {
git.checkout(iteration);
build.distributeResources(iteration);
}
String getTrainNameForCommonsVersion(ArtifactVersion version) {
return ReleaseTrains.TRAINS.stream().//
filter(train -> version.toString().startsWith(train.getModule(COMMONS).getVersion().toString())).//
findFirst().map(Train::getName).orElse(null);
}
}

View File

@@ -15,21 +15,24 @@
*/
package org.springframework.data.release.deployment;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.CliComponent;
import org.springframework.shell.core.CommandMarker;
import org.springframework.data.release.TimedCommand;
import org.springframework.shell.core.annotation.CliCommand;
/**
* Commands to interact with Artifactory.
*
* @author Oliver Gierke
*/
@CliComponent
@RequiredArgsConstructor(onConstructor = @__(@Autowired) )
public class ArtifactoryCommands implements CommandMarker {
class ArtifactoryCommands extends TimedCommand {
private final DeploymentOperations deployment;
private final @NonNull DeploymentOperations deployment;
@CliCommand(value = "artifactory verify", help = "Verifies authentication at Artifactory.")
public void verify() {

View File

@@ -15,7 +15,10 @@
*/
package org.springframework.data.release.git;
import lombok.AccessLevel;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
import java.util.List;
import java.util.Optional;
@@ -24,12 +27,12 @@ import java.util.stream.Stream;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.CliComponent;
import org.springframework.data.release.TimedCommand;
import org.springframework.data.release.issues.Ticket;
import org.springframework.data.release.model.Project;
import org.springframework.data.release.model.ReleaseTrains;
import org.springframework.data.release.model.Train;
import org.springframework.data.release.model.TrainIteration;
import org.springframework.shell.core.CommandMarker;
import org.springframework.shell.core.annotation.CliCommand;
import org.springframework.shell.core.annotation.CliOption;
import org.springframework.shell.support.table.Table;
@@ -41,28 +44,29 @@ import org.springframework.util.StringUtils;
*/
@CliComponent
@RequiredArgsConstructor(onConstructor = @__(@Autowired) )
public class GitCommands implements CommandMarker {
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
class GitCommands extends TimedCommand {
private final GitOperations git;
@NonNull GitOperations git;
@CliCommand("git co train")
public void checkout(@CliOption(key = "", mandatory = true) Train train) throws Exception {
void checkout(@CliOption(key = "", mandatory = true) Train train) throws Exception {
git.checkout(train);
}
@CliCommand("git co")
public void checkout(@CliOption(key = "", mandatory = true) TrainIteration iteration) throws Exception {
void checkout(@CliOption(key = "", mandatory = true) TrainIteration iteration) throws Exception {
git.checkout(iteration);
}
@CliCommand("git update")
public void update(@CliOption(key = { "", "train" }, mandatory = true) String trainName)
void update(@CliOption(key = { "", "train" }, mandatory = true) String trainName)
throws Exception, InterruptedException {
git.update(ReleaseTrains.getTrainByName(trainName));
}
@CliCommand("git tags")
public String tags(@CliOption(key = { "project" }, mandatory = true) String projectName) throws Exception {
String tags(@CliOption(key = { "project" }, mandatory = true) String projectName) throws Exception {
Project project = ReleaseTrains.getProjectByName(projectName);

View File

@@ -15,25 +15,27 @@
*/
package org.springframework.data.release.io;
import lombok.AccessLevel;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
import java.io.IOException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.CliComponent;
import org.springframework.data.release.TimedCommand;
import org.springframework.data.release.utils.Logger;
import org.springframework.shell.core.CommandMarker;
import org.springframework.shell.core.annotation.CliCommand;
/**
* @author Oliver Gierke
*/
@CliComponent
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
@RequiredArgsConstructor(onConstructor = @__(@Autowired) )
public class WorkspaceCommands implements CommandMarker {
class WorkspaceCommands extends TimedCommand {
private final Workspace workspace;
private final Logger logger;
@NonNull Workspace workspace;
@NonNull Logger logger;
@CliCommand("workspace cleanup")
public void cleanup() throws IOException {

View File

@@ -22,19 +22,17 @@ import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.CliComponent;
import org.springframework.data.release.TimedCommand;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.Project;
import org.springframework.data.release.model.TrainIteration;
import org.springframework.data.release.utils.ExecutionUtils;
import org.springframework.plugin.core.PluginRegistry;
import org.springframework.shell.core.CommandMarker;
import org.springframework.shell.core.annotation.CliCommand;
import org.springframework.shell.core.annotation.CliOption;
import org.springframework.util.StringUtils;
@@ -46,7 +44,7 @@ import org.springframework.util.StringUtils;
@CliComponent
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
@RequiredArgsConstructor(onConstructor = @__(@Autowired) )
public class IssueTrackerCommands implements CommandMarker {
class IssueTrackerCommands extends TimedCommand {
@NonNull PluginRegistry<IssueTracker, Project> tracker;
@@ -116,10 +114,6 @@ public class IssueTrackerCommands implements CommandMarker {
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

@@ -25,7 +25,6 @@ import java.util.Collections;
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.ReleaseTrains;
@@ -39,7 +38,7 @@ public class TicketsUnitTests {
@Test
public void hasReleaseTicketShouldReturnTrue() throws Exception {
Ticket ticket = new Ticket("1234", "Release 1.10 GA (Hopper)", new JiraTicketStatus(false, "", ""));
Ticket ticket = new Ticket("1234", "Release 1.10 GA (Hopper)", JiraTicketStatus.of(false, "", ""));
Tickets tickets = new Tickets(Collections.singletonList(ticket));
boolean result = tickets.hasReleaseTicket(ReleaseTrains.HOPPER.getModuleIteration(Iteration.GA, "JPA"));
@@ -49,7 +48,7 @@ public class TicketsUnitTests {
@Test
public void hasReleaseTickeForTicketWithoutTrainNameShouldReturnFalse() throws Exception {
Ticket ticket = new Ticket("1234", "Release 1.10 GA", new JiraTicketStatus(false, "", ""));
Ticket ticket = new Ticket("1234", "Release 1.10 GA", JiraTicketStatus.of(false, "", ""));
Tickets tickets = new Tickets(Collections.singletonList(ticket));
boolean result = tickets.hasReleaseTicket(ReleaseTrains.HOPPER.getModuleIteration(Iteration.GA, "JPA"));
@@ -59,7 +58,7 @@ public class TicketsUnitTests {
@Test
public void getReleaseTicketReturnsReleaseTicket() throws Exception {
Ticket ticket = new Ticket("1234", "Release 1.10 GA (Hopper)", new JiraTicketStatus(false, "", ""));
Ticket ticket = new Ticket("1234", "Release 1.10 GA (Hopper)", JiraTicketStatus.of(false, "", ""));
Tickets tickets = new Tickets(Collections.singletonList(ticket));
Ticket releaseTicket = tickets.getReleaseTicket(ReleaseTrains.HOPPER.getModuleIteration(Iteration.GA, "JPA"));
@@ -69,7 +68,7 @@ public class TicketsUnitTests {
@Test(expected = IllegalStateException.class)
public void getReleaseTicketThrowsExceptionWithoutAReleaseTicket() throws Exception {
Ticket ticket = new Ticket("1234", "Release 1.10 GA", new JiraTicketStatus(false, "", ""));
Ticket ticket = new Ticket("1234", "Release 1.10 GA", JiraTicketStatus.of(false, "", ""));
Tickets tickets = new Tickets(Collections.singletonList(ticket));
tickets.getReleaseTicket(ReleaseTrains.HOPPER.getModuleIteration(Iteration.GA, "JPA"));
@@ -79,7 +78,7 @@ public class TicketsUnitTests {
@Test
public void getResolvedReleaseTicket() throws Exception {
Ticket ticket = new Ticket("1234", "Release 1.10 GA (Hopper)", new JiraTicketStatus(true, "", ""));
Ticket ticket = new Ticket("1234", "Release 1.10 GA (Hopper)", JiraTicketStatus.of(true, "", ""));
Tickets tickets = new Tickets(Collections.singletonList(ticket));
Ticket releaseTicket = tickets.getReleaseTicket(ReleaseTrains.HOPPER.getModuleIteration(Iteration.GA, "JPA"));
@@ -89,7 +88,7 @@ public class TicketsUnitTests {
@Test
public void getReleaseTicketsReturnsReleaseTickets() throws Exception {
Ticket ticket = new Ticket("1234", "Release 1.10 GA (Hopper)", new JiraTicketStatus(false, "", ""));
Ticket ticket = new Ticket("1234", "Release 1.10 GA (Hopper)", JiraTicketStatus.of(false, "", ""));
Tickets tickets = new Tickets(Collections.singletonList(ticket));
Tickets result = tickets