import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.annotation.CheckForNull;
import org.slf4j.Logger;
private final Thread shutdownHook = new Thread(new MonitorShutdownHook(), "Monitor Shutdown Hook");
private final List<WatcherThread> watcherThreads = new CopyOnWriteArrayList<>();
- private final Lifecycle lifecycle = new Lifecycle();
+ private final Lifecycle lifecycle;
private final TerminatorThread terminator = new TerminatorThread();
private final RestartRequestWatcherThread restartWatcher = new RestartRequestWatcherThread();
@CheckForNull
HardStopWatcherThread hardStopWatcher;
- Monitor(int processNumber, FileSystem fileSystem, SystemExit exit, boolean watchForHardStop) {
+ Monitor(int processNumber, FileSystem fileSystem, SystemExit exit, boolean watchForHardStop, Lifecycle.LifecycleListener... listeners) {
this.processNumber = processNumber;
this.fileSystem = fileSystem;
this.systemExit = exit;
this.watchForHardStop = watchForHardStop;
+ this.lifecycle = new Lifecycle(listeners);
}
public static Monitor create(int processNumber, FileSystem fileSystem, boolean watchForHardStop) {
return new Monitor(processNumber, fileSystem, new SystemExit(), watchForHardStop);
}
+ public static Monitor create(int processNumber, FileSystem fileSystem, boolean watchForHardStop, Lifecycle.LifecycleListener listener) {
+ return new Monitor(processNumber, fileSystem, new SystemExit(), watchForHardStop, Objects.requireNonNull(listener));
+ }
+
/**
* Starts commands and blocks current thread until all processes are in state {@link State#STARTED}.
* @throws java.lang.IllegalArgumentException if commands list is empty
import java.util.Collections;
import java.util.EnumMap;
import java.util.EnumSet;
+import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
+import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Map<State, Set<State>> TRANSITIONS = buildTransitions();
+ private final List<LifecycleListener> listeners;
private State state = INIT;
+ public Lifecycle(LifecycleListener... listeners) {
+ this.listeners = Arrays.stream(listeners).filter(Objects::nonNull).collect(Collectors.toList());
+ }
+
private static Map<State, Set<State>> buildTransitions() {
Map<State, Set<State>> res = new EnumMap<>(State.class);
res.put(INIT, toSet(STARTING));
if (TRANSITIONS.get(currentState).contains(to)) {
this.state = to;
res = true;
+ listeners.forEach(listener -> listener.successfulTransition(currentState, to));
}
LOG.trace("tryToMoveTo from {} to {} => {}", currentState, to, res);
return res;
public int hashCode() {
return state.hashCode();
}
+
+ public interface LifecycleListener {
+ /**
+ * Called when a transition from state {@code from} to state {@code to} was successful.
+ */
+ void successfulTransition(State from, State to);
+ }
}
*/
package org.sonar.process;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.process.Lifecycle.State;
-import static org.sonar.process.Lifecycle.State.*;
+import static org.sonar.process.Lifecycle.State.INIT;
+import static org.sonar.process.Lifecycle.State.RESTARTING;
+import static org.sonar.process.Lifecycle.State.STARTED;
+import static org.sonar.process.Lifecycle.State.STARTING;
+import static org.sonar.process.Lifecycle.State.STOPPING;
+import static org.sonar.process.Lifecycle.State.values;
public class LifecycleTest {
@Test
public void try_to_move_does_not_support_jumping_states() {
- Lifecycle lifecycle = new Lifecycle();
+ TestLifeCycleListener listener = new TestLifeCycleListener();
+ Lifecycle lifecycle = new Lifecycle(listener);
assertThat(lifecycle.getState()).isEqualTo(INIT);
+ assertThat(listener.getTransitions()).isEmpty();
assertThat(lifecycle.tryToMoveTo(STARTED)).isFalse();
assertThat(lifecycle.getState()).isEqualTo(INIT);
+ assertThat(listener.getTransitions()).isEmpty();
assertThat(lifecycle.tryToMoveTo(STARTING)).isTrue();
assertThat(lifecycle.getState()).isEqualTo(STARTING);
+ assertThat(listener.getTransitions()).containsOnly(new Transition(INIT, STARTING));
}
@Test
@Test
public void can_move_to_STOPPING_from_STARTING_and_STARTED_only() {
for (State state : values()) {
- boolean tryToMoveTo = newLifeCycle(state).tryToMoveTo(STOPPING);
+ TestLifeCycleListener listener = new TestLifeCycleListener();
+ boolean tryToMoveTo = newLifeCycle(state, listener).tryToMoveTo(STOPPING);
if (state == STARTING || state == STARTED) {
assertThat(tryToMoveTo).describedAs("from state " + state).isTrue();
+ assertThat(listener.getTransitions()).containsOnly(new Transition(state, STOPPING));
} else {
assertThat(tryToMoveTo).describedAs("from state " + state).isFalse();
+ assertThat(listener.getTransitions()).isEmpty();
}
}
}
@Test
public void can_move_to_STARTING_from_RESTARTING() {
- assertThat(newLifeCycle(RESTARTING).tryToMoveTo(STARTING)).isTrue();
+ TestLifeCycleListener listener = new TestLifeCycleListener();
+ assertThat(newLifeCycle(RESTARTING, listener).tryToMoveTo(STARTING)).isTrue();
+ assertThat(listener.getTransitions()).containsOnly(new Transition(RESTARTING, STARTING));
}
- private static Lifecycle newLifeCycle(State state) {
+ private static Lifecycle newLifeCycle(State state, TestLifeCycleListener... listeners) {
switch (state) {
case INIT:
- return new Lifecycle();
+ return new Lifecycle(listeners);
case STARTING:
- return newLifeCycle(INIT, state);
+ return newLifeCycle(INIT, state, listeners);
case STARTED:
- return newLifeCycle(STARTING, state);
+ return newLifeCycle(STARTING, state, listeners);
case RESTARTING:
- return newLifeCycle(STARTED, state);
+ return newLifeCycle(STARTED, state, listeners);
case STOPPING:
- return newLifeCycle(STARTED, state);
+ return newLifeCycle(STARTED, state, listeners);
case HARD_STOPPING:
- return newLifeCycle(STARTING, state);
+ return newLifeCycle(STARTING, state, listeners);
case STOPPED:
- return newLifeCycle(STOPPING, state);
+ return newLifeCycle(STOPPING, state, listeners);
default:
throw new IllegalArgumentException("Unsupported state " + state);
}
}
- private static Lifecycle newLifeCycle(State from, State to) {
+ private static Lifecycle newLifeCycle(State from, State to, TestLifeCycleListener... listeners) {
Lifecycle lifecycle;
- lifecycle = newLifeCycle(from);
+ lifecycle = newLifeCycle(from, listeners);
assertThat(lifecycle.tryToMoveTo(to)).isTrue();
+ Arrays.stream(listeners).forEach(TestLifeCycleListener::clear);
return lifecycle;
}
+
+ private static final class TestLifeCycleListener implements Lifecycle.LifecycleListener {
+ private final List<Transition> transitions = new ArrayList<>();
+
+ @Override
+ public void successfulTransition(State from, State to) {
+ transitions.add(new Transition(from, to));
+ }
+
+ public List<Transition> getTransitions() {
+ return transitions;
+ }
+
+ public void clear() {
+ this.transitions.clear();
+ }
+ }
+
+ private static final class Transition {
+ private final State from;
+ private final State to;
+
+ private Transition(State from, State to) {
+ this.from = from;
+ this.to = to;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Transition that = (Transition) o;
+ return from == that.from && to == that.to;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(from, to);
+ }
+
+ @Override
+ public String toString() {
+ return "Transition{" + from + " => " + to + '}';
+ }
+ }
}
import java.util.List;
import java.util.Properties;
import org.apache.commons.io.FilenameUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.process.Lifecycle;
import org.sonar.process.ProcessId;
import org.sonar.process.ProcessProperties;
import org.sonar.process.Props;
import org.sonar.process.monitor.JavaCommand;
import org.sonar.process.monitor.Monitor;
+import static org.sonar.process.Lifecycle.State;
import static org.sonar.process.ProcessId.APP;
import static org.sonar.process.ProcessProperties.HTTPS_PROXY_HOST;
import static org.sonar.process.ProcessProperties.HTTPS_PROXY_PORT;
/**
* Properties about proxy that must be set as system properties
*/
- private static final String[] PROXY_PROPERTY_KEYS = new String[]{
+ private static final String[] PROXY_PROPERTY_KEYS = new String[] {
HTTP_PROXY_HOST,
HTTP_PROXY_PORT,
"http.nonProxyHosts",
private final Monitor monitor;
public App(AppFileSystem appFileSystem, boolean watchForHardStop) {
- this(Monitor.create(APP.getIpcIndex(), appFileSystem, watchForHardStop));
+ this(Monitor.create(APP.getIpcIndex(), appFileSystem, watchForHardStop, new AppLifecycleListener()));
}
App(Monitor monitor) {
monitor.stop();
}
+ private static class AppLifecycleListener implements Lifecycle.LifecycleListener {
+ private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
+
+ @Override
+ public void successfulTransition(State from, State to) {
+ if (to == State.STARTED) {
+ LOGGER.info("SonarQube is up");
+ }
+ }
+ }
}