Skip to content

Commit 0639ecf

Browse files
committed
feat: FSM 3
1 parent d9ccc94 commit 0639ecf

File tree

5 files changed

+253
-14
lines changed

5 files changed

+253
-14
lines changed

hivemq-edge/src/main/java/com/hivemq/protocols/fsm/I18nProtocolAdapterMessage.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ public enum I18nProtocolAdapterMessage implements I18nError {
2626
FSM_TRANSITION_FAILURE_UNABLE_TO_TRANSITION_FROM_STATE_TO_STATE,
2727
FSM_TRANSITION_SUCCESS_STATE_IS_UNCHANGED,
2828
FSM_TRANSITION_SUCCESS_TRANSITIONED_FROM_STATE_TO_STATE,
29+
FSM_CONNECTION_TRANSITION_FAILURE_UNABLE_TO_TRANSITION_FROM_STATE_TO_STATE,
30+
FSM_CONNECTION_TRANSITION_SUCCESS_STATE_IS_UNCHANGED,
31+
FSM_CONNECTION_TRANSITION_SUCCESS_TRANSITIONED_FROM_STATE_TO_STATE,
2932
PROTOCOL_ADAPTER_MANAGER_PROTOCOL_ADAPTER_DELETED,
3033
PROTOCOL_ADAPTER_MANAGER_PROTOCOL_ADAPTER_NOT_FOUND,
3134
;

hivemq-edge/src/main/java/com/hivemq/protocols/fsm/ProtocolAdapterConnectionState.java

Lines changed: 95 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,26 +21,107 @@
2121
import java.util.function.Function;
2222

2323
public enum ProtocolAdapterConnectionState {
24-
Closed(context -> 0),
25-
Closing(context -> 0),
26-
Connected(context -> 0),
27-
Connecting(context -> 0),
28-
Disconnected(context -> 0),
29-
Disconnecting(context -> 0),
30-
Error(context -> 0),
31-
ErrorClosing(context -> 0),
24+
Closed(ProtocolAdapterConnectionState::transitionFromClosed),
25+
Closing(ProtocolAdapterConnectionState::transitionFromClosing),
26+
Connected(ProtocolAdapterConnectionState::transitionFromConnected),
27+
Connecting(ProtocolAdapterConnectionState::transitionFromConnecting),
28+
Disconnected(ProtocolAdapterConnectionState::transitionFromDisconnected),
29+
Disconnecting(ProtocolAdapterConnectionState::transitionFromDisconnecting),
30+
Error(ProtocolAdapterConnectionState::transitionFromError),
31+
ErrorClosing(ProtocolAdapterConnectionState::transitionFromErrorClosing),
3232
;
3333

34-
private final @NotNull Function<Object, Integer> transitionFunction;
34+
private final @NotNull Function<ProtocolAdapterConnectionState, ProtocolAdapterConnectionTransitionResponse>
35+
transitionFunction;
3536

36-
ProtocolAdapterConnectionState(@NotNull final Function<Object, Integer> transitionFunction) {
37+
ProtocolAdapterConnectionState(@NotNull final Function<ProtocolAdapterConnectionState, ProtocolAdapterConnectionTransitionResponse> transitionFunction) {
3738
this.transitionFunction = transitionFunction;
3839
}
3940

40-
public @NotNull Integer transition(
41-
final @NotNull ProtocolAdapterConnectionState targetState,
42-
final @NotNull Object context) {
43-
return transitionFunction.apply(context);
41+
public static @NotNull ProtocolAdapterConnectionTransitionResponse transitionFromClosed(
42+
final @NotNull ProtocolAdapterConnectionState toState) {
43+
final ProtocolAdapterConnectionState fromState = ProtocolAdapterConnectionState.Closed;
44+
return switch (toState) {
45+
case Closed -> ProtocolAdapterConnectionTransitionResponse.notChanged(fromState);
46+
case Disconnected -> ProtocolAdapterConnectionTransitionResponse.success(fromState, toState);
47+
default -> ProtocolAdapterConnectionTransitionResponse.failure(fromState, toState);
48+
};
49+
}
50+
51+
public static @NotNull ProtocolAdapterConnectionTransitionResponse transitionFromClosing(
52+
final @NotNull ProtocolAdapterConnectionState toState) {
53+
final ProtocolAdapterConnectionState fromState = ProtocolAdapterConnectionState.Closing;
54+
return switch (toState) {
55+
case Closing -> ProtocolAdapterConnectionTransitionResponse.notChanged(fromState);
56+
case Closed -> ProtocolAdapterConnectionTransitionResponse.success(fromState, toState);
57+
default -> ProtocolAdapterConnectionTransitionResponse.failure(fromState, toState);
58+
};
59+
}
60+
61+
public static @NotNull ProtocolAdapterConnectionTransitionResponse transitionFromConnected(
62+
final @NotNull ProtocolAdapterConnectionState toState) {
63+
final ProtocolAdapterConnectionState fromState = ProtocolAdapterConnectionState.Connected;
64+
return switch (toState) {
65+
case Connected -> ProtocolAdapterConnectionTransitionResponse.notChanged(fromState);
66+
case Disconnecting, Closing, ErrorClosing ->
67+
ProtocolAdapterConnectionTransitionResponse.success(fromState, toState);
68+
default -> ProtocolAdapterConnectionTransitionResponse.failure(fromState, toState);
69+
};
70+
}
71+
72+
public static @NotNull ProtocolAdapterConnectionTransitionResponse transitionFromConnecting(
73+
final @NotNull ProtocolAdapterConnectionState toState) {
74+
final ProtocolAdapterConnectionState fromState = ProtocolAdapterConnectionState.Connecting;
75+
return switch (toState) {
76+
case Connecting -> ProtocolAdapterConnectionTransitionResponse.notChanged(fromState);
77+
case Connected, Error -> ProtocolAdapterConnectionTransitionResponse.success(fromState, toState);
78+
default -> ProtocolAdapterConnectionTransitionResponse.failure(fromState, toState);
79+
};
80+
}
81+
82+
public static @NotNull ProtocolAdapterConnectionTransitionResponse transitionFromDisconnected(
83+
final @NotNull ProtocolAdapterConnectionState toState) {
84+
final ProtocolAdapterConnectionState fromState = ProtocolAdapterConnectionState.Disconnected;
85+
return switch (toState) {
86+
case Disconnected -> ProtocolAdapterConnectionTransitionResponse.notChanged(fromState);
87+
case Connecting -> ProtocolAdapterConnectionTransitionResponse.success(fromState, toState);
88+
default -> ProtocolAdapterConnectionTransitionResponse.failure(fromState, toState);
89+
};
90+
}
91+
92+
public static @NotNull ProtocolAdapterConnectionTransitionResponse transitionFromDisconnecting(
93+
final @NotNull ProtocolAdapterConnectionState toState) {
94+
final ProtocolAdapterConnectionState fromState = ProtocolAdapterConnectionState.Disconnecting;
95+
return switch (toState) {
96+
case Disconnecting -> ProtocolAdapterConnectionTransitionResponse.notChanged(fromState);
97+
case Connecting -> ProtocolAdapterConnectionTransitionResponse.success(fromState, toState);
98+
default -> ProtocolAdapterConnectionTransitionResponse.failure(fromState, toState);
99+
};
100+
}
101+
102+
public static @NotNull ProtocolAdapterConnectionTransitionResponse transitionFromError(
103+
final @NotNull ProtocolAdapterConnectionState toState) {
104+
final ProtocolAdapterConnectionState fromState = ProtocolAdapterConnectionState.Error;
105+
return switch (toState) {
106+
case Error -> ProtocolAdapterConnectionTransitionResponse.notChanged(fromState);
107+
case Disconnected -> ProtocolAdapterConnectionTransitionResponse.success(fromState, toState);
108+
default -> ProtocolAdapterConnectionTransitionResponse.failure(fromState, toState);
109+
};
110+
}
111+
112+
public static @NotNull ProtocolAdapterConnectionTransitionResponse transitionFromErrorClosing(
113+
final @NotNull ProtocolAdapterConnectionState toState) {
114+
final ProtocolAdapterConnectionState fromState = ProtocolAdapterConnectionState.ErrorClosing;
115+
return switch (toState) {
116+
case ErrorClosing -> ProtocolAdapterConnectionTransitionResponse.notChanged(fromState);
117+
case Error -> ProtocolAdapterConnectionTransitionResponse.success(fromState, toState);
118+
default -> ProtocolAdapterConnectionTransitionResponse.failure(fromState, toState);
119+
};
120+
}
121+
122+
public @NotNull ProtocolAdapterConnectionTransitionResponse transition(
123+
final @NotNull ProtocolAdapterConnectionState toState) {
124+
return transitionFunction.apply(toState);
44125
}
45126

46127
public boolean isClosed() {
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright 2019-present HiveMQ GmbH
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.hivemq.protocols.fsm;
18+
19+
import org.jetbrains.annotations.NotNull;
20+
21+
import java.util.Map;
22+
23+
public record ProtocolAdapterConnectionTransitionResponse(ProtocolAdapterConnectionState fromState,
24+
ProtocolAdapterConnectionState toState,
25+
ProtocolAdapterTransitionStatus status, String message,
26+
Throwable error) {
27+
28+
public static final String FROM_STATE = "fromState";
29+
public static final String TO_STATE = "toState";
30+
public static final String STATE = "state";
31+
32+
public static ProtocolAdapterConnectionTransitionResponse success(
33+
final @NotNull ProtocolAdapterConnectionState fromState,
34+
final @NotNull ProtocolAdapterConnectionState toState) {
35+
return new ProtocolAdapterConnectionTransitionResponse(fromState,
36+
toState,
37+
ProtocolAdapterTransitionStatus.Success,
38+
I18nProtocolAdapterMessage.FSM_CONNECTION_TRANSITION_SUCCESS_TRANSITIONED_FROM_STATE_TO_STATE.get(Map.of(
39+
FROM_STATE,
40+
fromState.name(),
41+
TO_STATE,
42+
toState.name())),
43+
null);
44+
}
45+
46+
public static ProtocolAdapterConnectionTransitionResponse notChanged(final @NotNull ProtocolAdapterConnectionState state) {
47+
return new ProtocolAdapterConnectionTransitionResponse(state,
48+
state,
49+
ProtocolAdapterTransitionStatus.NotChanged,
50+
I18nProtocolAdapterMessage.FSM_CONNECTION_TRANSITION_SUCCESS_STATE_IS_UNCHANGED.get(Map.of(STATE,
51+
state.name())),
52+
null);
53+
}
54+
55+
public static ProtocolAdapterConnectionTransitionResponse failure(
56+
final @NotNull ProtocolAdapterConnectionState fromState,
57+
final @NotNull ProtocolAdapterConnectionState toState) {
58+
return new ProtocolAdapterConnectionTransitionResponse(fromState,
59+
toState,
60+
ProtocolAdapterTransitionStatus.Failure,
61+
I18nProtocolAdapterMessage.FSM_CONNECTION_TRANSITION_FAILURE_UNABLE_TO_TRANSITION_FROM_STATE_TO_STATE.get(
62+
Map.of(FROM_STATE, fromState.name(), TO_STATE, toState.name())),
63+
null);
64+
}
65+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
fsm.transition.failure.unable.to.transition.from.state.to.state=Unable to transition from ${fromState} to ${toState}.
22
fsm.transition.success.state.is.unchanged=${state} is unchanged.
33
fsm.transition.success.transitioned.from.state.to.state=Transitioned from ${fromState} to ${toState}.
4+
fsm.connection.transition.failure.unable.to.transition.from.state.to.state=Unable to transition connection from ${fromState} to ${toState}.
5+
fsm.connection.transition.success.state.is.unchanged=Connection ${state} is unchanged.
6+
fsm.connection.transition.success.transitioned.from.state.to.state=Connection transitioned from ${fromState} to ${toState}.
47
protocol.adapter.manager.protocol.adapter.deleted=Adapter '${adapterId}' was deleted from the system permanently.
58
protocol.adapter.manager.protocol.adapter.not.found=Adapter '${adapterId}' not found.
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright 2019-present HiveMQ GmbH
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.hivemq.protocols.fsm;
18+
19+
import com.hivemq.common.i18n.StringTemplate;
20+
import org.junit.jupiter.api.Test;
21+
import org.junit.jupiter.api.extension.ExtendWith;
22+
import org.mockito.junit.jupiter.MockitoExtension;
23+
import org.mockito.junit.jupiter.MockitoSettings;
24+
import org.mockito.quality.Strictness;
25+
26+
import java.util.List;
27+
import java.util.Map;
28+
import java.util.Set;
29+
30+
import static org.assertj.core.api.Assertions.assertThat;
31+
32+
@ExtendWith(MockitoExtension.class)
33+
@MockitoSettings(strictness = Strictness.LENIENT)
34+
public class ProtocolAdapterConnectionStateTest {
35+
private static final Map<ProtocolAdapterConnectionState, Set<ProtocolAdapterConnectionState>>
36+
PROTOCOL_ADAPTER_CONNECTION_STATE_MAP = Map.of(ProtocolAdapterConnectionState.Closed,
37+
Set.of(ProtocolAdapterConnectionState.Disconnected),
38+
ProtocolAdapterConnectionState.Closing,
39+
Set.of(ProtocolAdapterConnectionState.Closed),
40+
ProtocolAdapterConnectionState.Connected,
41+
Set.of(ProtocolAdapterConnectionState.Disconnecting,
42+
ProtocolAdapterConnectionState.Closing,
43+
ProtocolAdapterConnectionState.ErrorClosing),
44+
ProtocolAdapterConnectionState.Connecting,
45+
Set.of(ProtocolAdapterConnectionState.Connected, ProtocolAdapterConnectionState.Error),
46+
ProtocolAdapterConnectionState.Disconnected,
47+
Set.of(ProtocolAdapterConnectionState.Connecting),
48+
ProtocolAdapterConnectionState.Disconnecting,
49+
Set.of(ProtocolAdapterConnectionState.Connecting),
50+
ProtocolAdapterConnectionState.Error,
51+
Set.of(ProtocolAdapterConnectionState.Disconnected),
52+
ProtocolAdapterConnectionState.ErrorClosing,
53+
Set.of(ProtocolAdapterConnectionState.Error));
54+
55+
@Test
56+
public void whenEverythingWorks_thenTransitionShouldWork() {
57+
final List<ProtocolAdapterConnectionState> states = List.of(ProtocolAdapterConnectionState.values());
58+
states.forEach(fromState -> {
59+
final Set<ProtocolAdapterConnectionState> possibleToStates =
60+
PROTOCOL_ADAPTER_CONNECTION_STATE_MAP.get(fromState);
61+
assertThat(possibleToStates).isNotNull();
62+
states.forEach(toState -> {
63+
final ProtocolAdapterConnectionTransitionResponse response = fromState.transition(toState);
64+
switch (response.status()) {
65+
case Success -> {
66+
assertThat(possibleToStates).contains(toState);
67+
assertThat(response.message()).isEqualTo(StringTemplate.format(
68+
"Connection transitioned from ${fromState} to ${toState}.",
69+
Map.of("fromState", fromState, "toState", toState)));
70+
}
71+
case Failure -> {
72+
assertThat(possibleToStates).doesNotContain(toState);
73+
assertThat(response.message()).isEqualTo(StringTemplate.format(
74+
"Unable to transition connection from ${fromState} to ${toState}.",
75+
Map.of("fromState", fromState, "toState", toState)));
76+
}
77+
case NotChanged -> {
78+
assertThat(toState).isEqualTo(fromState);
79+
assertThat(response.message()).isEqualTo(StringTemplate.format(
80+
"Connection ${state} is unchanged.",
81+
Map.of("state", fromState)));
82+
}
83+
}
84+
});
85+
});
86+
}
87+
}

0 commit comments

Comments
 (0)