Skip to content
This repository was archived by the owner on Aug 13, 2020. It is now read-only.

Commit 07f3b6b

Browse files
Merge pull request #768 from CJSCommonPlatform/make-system-commands-asynchronous
Make SystemCommands run asynchronously
2 parents 46934a5 + 1adc47c commit 07f3b6b

File tree

12 files changed

+264
-41
lines changed

12 files changed

+264
-41
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ on [Keep a CHANGELOG](http://keepachangelog.com/). This project adheres to
55

66
## [Unreleased]
77

8+
## [6.0.12] - 2018-09-08
9+
### Changed
10+
- All JMX SystemCommands are now run asynchronously. i.e the call to the JMX bean will return immediately
11+
- The JMX SystemCommandBean now only throws 2 Exceptions
12+
1. UnsupportedSystemCommandException. If the command is unrecognised by the context.
13+
2. SystemCommandFailedException. On any other failure. NB. this Exception also contains the stacktrace of the server side exception
14+
815
## [6.0.11] - 2018-08-30
916
### Added
1017
- ordering to subscriptions descriptor yamls

framework-utilities/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121
<groupId>com.google.guava</groupId>
2222
<artifactId>guava</artifactId>
2323
</dependency>
24+
<dependency>
25+
<groupId>org.apache.commons</groupId>
26+
<artifactId>commons-lang3</artifactId>
27+
</dependency>
2428

2529
<!-- Test dependencies -->
2630
<dependency>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package uk.gov.justice.services.framework.utilities.exceptions;
2+
3+
import org.apache.commons.lang3.exception.ExceptionUtils;
4+
5+
public class StackTraceProvider {
6+
7+
public String getStackTrace(final Throwable throwable) {
8+
return ExceptionUtils.getStackTrace(throwable);
9+
}
10+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package uk.gov.justice.services.framework.utilities.exceptions;
2+
3+
import static org.hamcrest.CoreMatchers.is;
4+
import static org.junit.Assert.assertThat;
5+
6+
import org.junit.Test;
7+
import org.junit.runner.RunWith;
8+
import org.mockito.InjectMocks;
9+
import org.mockito.runners.MockitoJUnitRunner;
10+
11+
@RunWith(MockitoJUnitRunner.class)
12+
public class StackTraceProviderTest {
13+
14+
@InjectMocks
15+
private StackTraceProvider stackTraceProvider;
16+
17+
@Test
18+
public void shouldGetTheStackTraceOfAnExceptionAsAString() throws Exception {
19+
20+
final String stackTrace = stackTraceProvider.getStackTrace(new Exception("Ooops"));
21+
22+
final String stackTracePrefix = "java.lang.Exception: Ooops\n" +
23+
"\tat uk.gov.justice.services.framework.utilities.exceptions.StackTraceProviderTest.shouldGetTheStackTraceOfAnExceptionAsAString(StackTraceProviderTest.java";
24+
25+
assertThat(stackTrace.startsWith(stackTracePrefix), is(true));
26+
}
27+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package uk.gov.justice.services.jmx.api;
2+
3+
public class SystemCommandFailedException extends RuntimeException {
4+
5+
private final String serverStackTrace;
6+
7+
public SystemCommandFailedException(final String message, final String serverStackTrace) {
8+
super(message);
9+
this.serverStackTrace = serverStackTrace;
10+
}
11+
12+
public String getServerStackTrace() {
13+
return serverStackTrace;
14+
}
15+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package uk.gov.justice.services.jmx.api;
2+
3+
public class UnsupportedSystemCommandException extends RuntimeException {
4+
5+
public UnsupportedSystemCommandException(final String message) {
6+
super(message);
7+
}
8+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package uk.gov.justice.services.jmx.api.mbean;
2+
3+
import static java.lang.String.format;
4+
5+
import uk.gov.justice.services.framework.utilities.exceptions.StackTraceProvider;
6+
import uk.gov.justice.services.jmx.api.SystemCommandFailedException;
7+
import uk.gov.justice.services.jmx.api.command.SystemCommand;
8+
import uk.gov.justice.services.jmx.command.SystemCommandStore;
9+
10+
import javax.ejb.Asynchronous;
11+
import javax.ejb.Stateless;
12+
import javax.inject.Inject;
13+
14+
import org.slf4j.Logger;
15+
16+
@Stateless
17+
public class AsynchronousCommandRunnerBean {
18+
19+
@Inject
20+
private SystemCommandStore systemCommandStore;
21+
22+
@Inject
23+
private StackTraceProvider stackTraceProvider;
24+
25+
@Inject
26+
private Logger logger;
27+
28+
public boolean isSupported(final SystemCommand systemCommand) {
29+
return systemCommandStore.isSupported(systemCommand);
30+
}
31+
32+
@Asynchronous
33+
public void run(final SystemCommand systemCommand) {
34+
try {
35+
systemCommandStore.findCommandProxy(systemCommand).invokeCommand(systemCommand);
36+
} catch (final Throwable e) {
37+
final String message = format("Failed to run System Command '%s'", systemCommand.getName());
38+
logger.error(message, e);
39+
40+
throw new SystemCommandFailedException(
41+
message + ". Caused by " + e.getClass().getName() + ": " + e.getMessage(),
42+
stackTraceProvider.getStackTrace(e));
43+
}
44+
}
45+
}

jmx/jmx-command-handling/src/main/java/uk/gov/justice/services/jmx/api/mbean/SystemCommander.java

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,11 @@
22

33
import static java.lang.String.format;
44

5-
import uk.gov.justice.services.jmx.api.SystemCommandException;
6-
import uk.gov.justice.services.jmx.api.SystemCommandInvocationException;
5+
import uk.gov.justice.services.jmx.api.UnsupportedSystemCommandException;
76
import uk.gov.justice.services.jmx.api.command.SystemCommand;
87
import uk.gov.justice.services.jmx.api.state.ApplicationManagementState;
98
import uk.gov.justice.services.jmx.command.ApplicationManagementStateRegistry;
109
import uk.gov.justice.services.jmx.command.SystemCommandScanner;
11-
import uk.gov.justice.services.jmx.command.SystemCommandStore;
1210

1311
import java.util.List;
1412

@@ -22,7 +20,7 @@ public class SystemCommander implements SystemCommanderMBean {
2220
private Logger logger;
2321

2422
@Inject
25-
private SystemCommandStore systemCommandStore;
23+
private AsynchronousCommandRunnerBean asynchronousCommandRunnerBean;
2624

2725
@Inject
2826
private SystemCommandScanner systemCommandScanner;
@@ -33,15 +31,12 @@ public class SystemCommander implements SystemCommanderMBean {
3331
@Override
3432
public void call(final SystemCommand systemCommand) {
3533

36-
final String commandName = systemCommand.getName();
37-
logger.info(format("Received System Command '%s'", commandName));
34+
logger.info(format("Received System Command '%s'", systemCommand.getName()));
3835

39-
try {
40-
systemCommandStore.findCommandProxy(systemCommand).invokeCommand(systemCommand);
41-
} catch (final SystemCommandInvocationException e) {
42-
final String message = format("Failed to run System Command '%s'", commandName);
43-
logger.error(message, e);
44-
throw new SystemCommandException(message, e);
36+
if(asynchronousCommandRunnerBean.isSupported(systemCommand)) {
37+
asynchronousCommandRunnerBean.run(systemCommand);
38+
} else {
39+
throw new UnsupportedSystemCommandException(format("The system command '%s' is not supported on this context.", systemCommand.getName()));
4540
}
4641
}
4742

jmx/jmx-command-handling/src/main/java/uk/gov/justice/services/jmx/command/SystemCommandStore.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,23 @@
99
import java.util.List;
1010
import java.util.Map;
1111

12-
import javax.enterprise.context.ApplicationScoped;
1312
import javax.inject.Inject;
13+
import javax.inject.Singleton;
1414

1515
import org.slf4j.Logger;
1616

17-
@ApplicationScoped
17+
@Singleton
1818
public class SystemCommandStore {
1919

2020
private Map<String, SystemCommandHandlerProxy> handlers = new HashMap<>();
2121

2222
@Inject
2323
private Logger logger;
2424

25+
public boolean isSupported(final SystemCommand systemCommand) {
26+
return handlers.containsKey(systemCommand.getName());
27+
}
28+
2529
public SystemCommandHandlerProxy findCommandProxy(final SystemCommand systemCommand) {
2630

2731
final SystemCommandHandlerProxy systemCommandHandlerProxy = handlers.get(
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package uk.gov.justice.services.jmx.api.mbean;
2+
3+
import static org.hamcrest.CoreMatchers.is;
4+
import static org.hamcrest.CoreMatchers.nullValue;
5+
import static org.junit.Assert.assertThat;
6+
import static org.junit.Assert.fail;
7+
import static org.mockito.Mockito.doThrow;
8+
import static org.mockito.Mockito.mock;
9+
import static org.mockito.Mockito.verify;
10+
import static org.mockito.Mockito.when;
11+
12+
import uk.gov.justice.services.framework.utilities.exceptions.StackTraceProvider;
13+
import uk.gov.justice.services.jmx.api.SystemCommandException;
14+
import uk.gov.justice.services.jmx.api.SystemCommandFailedException;
15+
import uk.gov.justice.services.jmx.command.SystemCommandHandlerProxy;
16+
import uk.gov.justice.services.jmx.command.SystemCommandStore;
17+
import uk.gov.justice.services.jmx.command.TestCommand;
18+
19+
import org.junit.Test;
20+
import org.junit.runner.RunWith;
21+
import org.mockito.InjectMocks;
22+
import org.mockito.Mock;
23+
import org.mockito.runners.MockitoJUnitRunner;
24+
import org.slf4j.Logger;
25+
26+
27+
@RunWith(MockitoJUnitRunner.class)
28+
public class AsynchronousCommandRunnerBeanTest {
29+
30+
@Mock
31+
private SystemCommandStore systemCommandStore;
32+
33+
@Mock
34+
private StackTraceProvider stackTraceProvider;
35+
36+
@Mock
37+
private Logger logger;
38+
39+
@InjectMocks
40+
private AsynchronousCommandRunnerBean asynchronousCommandRunnerBean;
41+
42+
@Test
43+
public void shouldReturnFalseIfCommandUnsupported() throws Exception {
44+
45+
final TestCommand testCommand = new TestCommand();
46+
final boolean supported = true;
47+
48+
when(systemCommandStore.isSupported(testCommand)).thenReturn(supported);
49+
50+
assertThat(asynchronousCommandRunnerBean.isSupported(testCommand), is(supported));
51+
}
52+
53+
@Test
54+
public void shouldFindTheCorrectProxyForTheCommandAndInvoke() throws Exception {
55+
56+
final TestCommand testCommand = new TestCommand();
57+
58+
final SystemCommandHandlerProxy systemCommandHandlerProxy = mock(SystemCommandHandlerProxy.class);
59+
60+
when(systemCommandStore.findCommandProxy(testCommand)).thenReturn(systemCommandHandlerProxy);
61+
62+
asynchronousCommandRunnerBean.run(testCommand);
63+
64+
verify(systemCommandHandlerProxy).invokeCommand(testCommand);
65+
}
66+
67+
@Test
68+
public void shouldThrowSystemCommandFailedExceptionIfCommandFails() throws Exception {
69+
70+
final TestCommand testCommand = new TestCommand();
71+
final SystemCommandException systemCommandException = new SystemCommandException("Ooops");
72+
final String stackTrace = "stack trace";
73+
74+
final SystemCommandHandlerProxy systemCommandHandlerProxy = mock(SystemCommandHandlerProxy.class);
75+
76+
when(systemCommandStore.findCommandProxy(testCommand)).thenReturn(systemCommandHandlerProxy);
77+
doThrow(systemCommandException).when(systemCommandHandlerProxy).invokeCommand(testCommand);
78+
when(stackTraceProvider.getStackTrace(systemCommandException)).thenReturn(stackTrace);
79+
80+
try {
81+
asynchronousCommandRunnerBean.run(testCommand);
82+
fail();
83+
} catch (final SystemCommandFailedException expected) {
84+
assertThat(expected.getMessage(), is("Failed to run System Command 'TEST_COMMAND'. Caused by uk.gov.justice.services.jmx.api.SystemCommandException: Ooops"));
85+
assertThat(expected.getServerStackTrace(), is(stackTrace));
86+
assertThat(expected.getCause(), is(nullValue()));
87+
}
88+
89+
90+
}
91+
}

0 commit comments

Comments
 (0)