• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

temporalio / sdk-java / #169

pending completion
#169

push

github-actions

web-flow
Remove use of deprecated API (#1758)

4 of 4 new or added lines in 1 file covered. (100.0%)

17345 of 21558 relevant lines covered (80.46%)

0.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

61.44
/temporal-sdk/src/main/java/io/temporal/internal/statemachines/StateMachineDefinition.java
1
/*
2
 * Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved.
3
 *
4
 * Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
5
 *
6
 * Modifications copyright (C) 2017 Uber Technologies, Inc.
7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this material except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 *   http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 */
20

21
package io.temporal.internal.statemachines;
22

23
import io.temporal.api.enums.v1.CommandType;
24
import io.temporal.api.enums.v1.EventType;
25
import io.temporal.internal.common.ProtocolType;
26
import io.temporal.workflow.Functions;
27
import java.util.ArrayList;
28
import java.util.Arrays;
29
import java.util.Comparator;
30
import java.util.HashSet;
31
import java.util.LinkedHashMap;
32
import java.util.List;
33
import java.util.Map;
34
import java.util.Objects;
35
import java.util.Set;
36

37
/**
38
 * State machine definition of a single server side entity like activity, workflow task or the whole
39
 * workflow.
40
 *
41
 * <p>Based on the idea that each entity goes through state transitions and the same operation like
42
 * timeout is applicable to some states only and can lead to different actions in each state. Each
43
 * valid state transition should be registered through add methods. The associated action is invoked
44
 * when the state transition is requested.
45
 *
46
 * <p>There are three types of events that can cause state transitions:
47
 *
48
 * <ul>
49
 *   <li>Explicit event. The event that is reported through {@link
50
 *       StateMachine#handleExplicitEvent(Object, Object)}.
51
 *   <li>History event. This event is caused by processing an event recorded in the event history.
52
 *       It is reported through {@link StateMachine#handleHistoryEvent(EventType, Object)}.
53
 *   <li>Command event. This event is caused by reporting that a certain command is prepared to be
54
 *       sent as part of the workflow task response to the service. It is reported through {@link
55
 *       StateMachine#handleCommand(CommandType, Object)}.
56
 * </ul>
57
 */
58
final class StateMachineDefinition<State, ExplicitEvent, Data> {
59

60
  /** Map of transitions to actions. */
61
  private final Map<
1✔
62
          Transition<State, TransitionEvent<ExplicitEvent>>, TransitionAction<State, Data>>
63
      transitions =
64
          new LinkedHashMap<>(); // linked to maintain the same order for diagram generation
65

66
  private final String name;
67
  private final State initialState;
68
  private final List<State> finalStates;
69
  private final Set<EventType> validEventTypes = new HashSet<>();
1✔
70

71
  /**
72
   * Create a new instance of the StateMachine.
73
   *
74
   * @param name Human readable name of the state machine. Used for logging.
75
   * @param initialState the initial state the machine is created at.
76
   * @param finalStates the states at which the state machine cannot make any state transitions and
77
   *     reports {@link StateMachine#isFinalState()} as true.
78
   * @param <State>
79
   * @param <ExplicitEvent>
80
   * @param <Data>
81
   * @return
82
   */
83
  public static <State, ExplicitEvent, Data>
84
      StateMachineDefinition<State, ExplicitEvent, Data> newInstance(
85
          String name, State initialState, State... finalStates) {
86
    return new StateMachineDefinition<>(name, initialState, finalStates);
1✔
87
  }
88

89
  private StateMachineDefinition(String name, State initialState, State[] finalStates) {
1✔
90
    this.name = Objects.requireNonNull(name);
1✔
91
    this.initialState = Objects.requireNonNull(initialState);
1✔
92
    this.finalStates = Arrays.asList(finalStates);
1✔
93
  }
1✔
94

95
  public String getName() {
96
    return name;
1✔
97
  }
98

99
  public State getInitialState() {
100
    return initialState;
1✔
101
  }
102

103
  /** All possible history event types that are known to this state machine instance. */
104
  public Set<EventType> getValidEventTypes() {
105
    return validEventTypes;
1✔
106
  }
107

108
  /**
109
   * Registers a transition between states.
110
   *
111
   * @param from initial state that transition applies to
112
   * @param explicitEvent explicitEvent that caused the transition. Delivered through {@link
113
   *     StateMachine#handleExplicitEvent(Object, Object)}.
114
   * @param to destination state of a transition.
115
   * @param action action to invoke upon transition.
116
   * @return the current StateMachine instance for the fluid pattern.
117
   */
118
  StateMachineDefinition<State, ExplicitEvent, Data> add(
119
      State from, ExplicitEvent explicitEvent, State to, Functions.Proc1<Data> action) {
120
    checkFinalState(from);
1✔
121
    add(
1✔
122
        new Transition<>(from, new TransitionEvent<>(explicitEvent)),
123
        new FixedTransitionAction<>(to, action));
124
    return this;
1✔
125
  }
126

127
  /**
128
   * Registers a transition between states.
129
   *
130
   * @param from initial state that transition applies to
131
   * @param explicitEvent explicitEvent that caused the transition. Delivered through {@link
132
   *     StateMachine#handleExplicitEvent(Object, Object)}.
133
   * @param to destination state of a transition.
134
   * @return the current StateMachine instance for the fluid pattern.
135
   */
136
  StateMachineDefinition<State, ExplicitEvent, Data> add(
137
      State from, ExplicitEvent explicitEvent, State to) {
138
    checkFinalState(from);
1✔
139
    add(
1✔
140
        new Transition<>(from, new TransitionEvent<>(explicitEvent)),
141
        new FixedTransitionAction<>(to, (data) -> {}));
1✔
142
    return this;
1✔
143
  }
144

145
  /**
146
   * Registers a transition between states.
147
   *
148
   * @param from initial state that transition applies to
149
   * @param eventType history event that caused the transition. Delivered through {@link
150
   *     StateMachine#handleHistoryEvent(EventType, Object)}.
151
   * @param to destination state of a transition.
152
   * @param action action to invoke upon transition.
153
   * @return the current StateMachine instance for the fluid pattern.
154
   */
155
  StateMachineDefinition<State, ExplicitEvent, Data> add(
156
      State from, EventType eventType, State to, Functions.Proc1<Data> action) {
157
    checkFinalState(from);
1✔
158
    add(
1✔
159
        new Transition<>(from, new TransitionEvent<>(eventType)),
160
        new FixedTransitionAction<>(to, action));
161
    validEventTypes.add(eventType);
1✔
162
    return this;
1✔
163
  }
164

165
  /**
166
   * Registers a transition between states.
167
   *
168
   * @param from initial state that transition applies to
169
   * @param eventType history event that caused the transition. Delivered through {@link
170
   *     StateMachine#handleHistoryEvent(EventType, Object)}.
171
   * @param to destination state of a transition.
172
   * @return the current StateMachine instance for the fluid pattern.
173
   */
174
  StateMachineDefinition<State, ExplicitEvent, Data> add(
175
      State from, EventType eventType, State to) {
176
    checkFinalState(from);
1✔
177
    add(
1✔
178
        new Transition<>(from, new TransitionEvent<>(eventType)),
179
        new FixedTransitionAction<>(to, (data) -> {}));
1✔
180
    validEventTypes.add(eventType);
1✔
181
    return this;
1✔
182
  }
183

184
  /**
185
   * Registers a dynamic transition between states. Used when the same event can transition to more
186
   * than one state depending on data.
187
   *
188
   * @param from initial state that transition applies to
189
   * @param eventType history event that caused the transition. Delivered through {@link
190
   *     StateMachine#handleHistoryEvent(EventType, Object)}.
191
   * @param toStates allowed destination states of a transition.
192
   * @param action action to invoke upon transition.
193
   * @return the current StateMachine instance for the fluid pattern.
194
   */
195
  StateMachineDefinition<State, ExplicitEvent, Data> add(
196
      State from, EventType eventType, State[] toStates, DynamicCallback<State, Data> action) {
197
    checkFinalState(from);
1✔
198
    add(
1✔
199
        new Transition<>(from, new TransitionEvent<>(eventType)),
200
        new DynamicTransitionAction<>(toStates, action));
201
    validEventTypes.add(eventType);
1✔
202
    return this;
1✔
203
  }
204

205
  /**
206
   * Registers a transition between states.
207
   *
208
   * @param from initial state that transition applies to
209
   * @param commandType command that caused the transition. Delivered through {@link
210
   *     StateMachine#handleCommand(CommandType, Object)}.
211
   * @param to destination state of a transition.
212
   * @param action action to invoke upon transition.
213
   * @return the current StateMachine instance for the fluid pattern.
214
   */
215
  StateMachineDefinition<State, ExplicitEvent, Data> add(
216
      State from, CommandType commandType, State to, Functions.Proc1<Data> action) {
217
    checkFinalState(from);
1✔
218
    add(
1✔
219
        new Transition<>(from, new TransitionEvent<>(commandType)),
220
        new FixedTransitionAction<>(to, action));
221
    return this;
1✔
222
  }
223

224
  /**
225
   * Registers a transition between states.
226
   *
227
   * @param from initial state that transition applies to
228
   * @param commandType command that caused the transition. Delivered through {@link
229
   *     StateMachine#handleCommand(CommandType, Object)}.
230
   * @param to destination state of a transition.
231
   * @return the current StateMachine instance for the fluid pattern.
232
   */
233
  StateMachineDefinition<State, ExplicitEvent, Data> add(
234
      State from, CommandType commandType, State to) {
235
    checkFinalState(from);
1✔
236
    add(
1✔
237
        new Transition<>(from, new TransitionEvent<>(commandType)),
238
        new FixedTransitionAction<>(to, (data -> {})));
1✔
239
    return this;
1✔
240
  }
241

242
  /**
243
   * Registers a dynamic transition between states. Used when the same explicitEvent can transition
244
   * to more than one state depending on data.
245
   *
246
   * @param from initial state that transition applies to
247
   * @param toStates allowed destination states of a transition.
248
   * @param action action to invoke upon transition
249
   * @return the current StateMachine instance for the fluid pattern.
250
   */
251
  StateMachineDefinition<State, ExplicitEvent, Data> add(
252
      State from,
253
      ExplicitEvent explicitEvent,
254
      State[] toStates,
255
      DynamicCallback<State, Data> action) {
256
    checkFinalState(from);
1✔
257
    add(
1✔
258
        new Transition<>(from, new TransitionEvent<>(explicitEvent)),
259
        new DynamicTransitionAction<>(toStates, action));
260
    return this;
1✔
261
  }
262

263
  /**
264
   * Registers a transition between states.
265
   *
266
   * @param from initial state that transition applies to
267
   * @param messageType message that caused the transition. Delivered through {@link
268
   *     StateMachine#handleMessage(ProtocolType, Object)}.
269
   * @param to destination state of a transition.
270
   * @param action action to invoke upon transition.
271
   * @return the current StateMachine instance for the fluid pattern.
272
   */
273
  StateMachineDefinition<State, ExplicitEvent, Data> add(
274
      State from, ProtocolType messageType, State to, Functions.Proc1<Data> action) {
275
    checkFinalState(from);
1✔
276
    add(
1✔
277
        new Transition<>(from, new TransitionEvent<>(messageType)),
278
        new FixedTransitionAction<>(to, action));
279
    return this;
1✔
280
  }
281

282
  /**
283
   * Registers a transition between states.
284
   *
285
   * @param from initial state that transition applies to
286
   * @param messageType message that caused the transition. Delivered through {@link
287
   *     StateMachine#handleMessage(ProtocolType, Object)}.
288
   * @param to destination state of a transition.
289
   * @return the current StateMachine instance for the fluid pattern.
290
   */
291
  StateMachineDefinition<State, ExplicitEvent, Data> add(
292
      State from, ProtocolType messageType, State to) {
293
    checkFinalState(from);
×
294
    add(
×
295
        new Transition<>(from, new TransitionEvent<>(messageType)),
296
        new FixedTransitionAction<>(to, (data -> {})));
×
297
    return this;
×
298
  }
299

300
  /**
301
   * Registers a dynamic transition between states. Used when the same explicitEvent can transition
302
   * to more than one state depending on data.
303
   *
304
   * @param from initial state that transition applies to
305
   * @param messageType message that caused the transition. Delivered through {@link
306
   *     StateMachine#handleMessage(ProtocolType, Object)}.
307
   * @param toStates allowed destination states of a transition.
308
   * @param action action to invoke upon transition
309
   * @return the current StateMachine instance for the fluid pattern.
310
   */
311
  StateMachineDefinition<State, ExplicitEvent, Data> add(
312
      State from, ProtocolType messageType, State[] toStates, DynamicCallback<State, Data> action) {
313
    checkFinalState(from);
×
314
    add(
×
315
        new Transition<>(from, new TransitionEvent<>(messageType)),
316
        new DynamicTransitionAction<>(toStates, action));
317
    return this;
×
318
  }
319

320
  @Override
321
  public String toString() {
322
    return "StateMachine{" + "name='" + name + "'}";
×
323
  }
324

325
  private void checkFinalState(State from) {
326
    if (finalStates.contains(from)) {
1✔
327
      throw new IllegalArgumentException("State transition from a final state is not allowed");
×
328
    }
329
  }
1✔
330

331
  private void add(
332
      Transition<State, TransitionEvent<ExplicitEvent>> transition,
333
      TransitionAction<State, Data> target) {
334
    if (transitions.containsKey(transition)) {
1✔
335
      throw new IllegalArgumentException("Duplicated transition is not allowed: " + transition);
×
336
    }
337
    transitions.put(transition, target);
1✔
338
  }
1✔
339

340
  public boolean isFinalState(State state) {
341
    return finalStates.contains(state);
1✔
342
  }
343

344
  /**
345
   * Generates PlantUML (plantuml.com/state-diagram) diagram representation of the state machine.
346
   */
347
  public String asPlantUMLStateDiagram() {
348
    StringBuilder result = new StringBuilder();
1✔
349
    result.append("@startuml\n");
1✔
350
    result.append("title ");
1✔
351
    result.append(this.getName());
1✔
352
    result.append(" State Transitions\n");
1✔
353
    result.append("\n[*] --> ");
1✔
354
    result.append(initialState);
1✔
355
    result.append('\n');
1✔
356
    List<Transition<State, TransitionEvent<ExplicitEvent>>> transitionList = new ArrayList<>();
1✔
357
    transitionList.addAll(transitions.keySet());
1✔
358
    transitionList.sort(Comparator.comparing((tt) -> tt.getFrom().toString()));
1✔
359
    for (Transition<State, TransitionEvent<ExplicitEvent>> transition : transitionList) {
1✔
360
      TransitionAction<State, Data> action = transitions.get(transition);
1✔
361
      List<State> targets = action.getAllowedStates();
1✔
362
      for (State target : targets) {
1✔
363
        result.append(transition.getFrom());
1✔
364
        result.append(" --> ");
1✔
365
        result.append(target);
1✔
366
        result.append(": ");
1✔
367
        result.append(transition.getExplicitEvent());
1✔
368
        result.append('\n');
1✔
369
      }
1✔
370
    }
1✔
371
    for (State finalState : finalStates) {
1✔
372
      result.append(finalState);
1✔
373
      result.append(" --> [*]\n");
1✔
374
    }
1✔
375
    result.append("center footer Copyright (C) ");
1✔
376
    result.append("2020");
1✔
377
    result.append(" Temporal Technologies, Inc. All Rights Reserved.\n");
1✔
378
    result.append("@enduml\n");
1✔
379
    return result.toString();
1✔
380
  }
381

382
  /**
383
   * Given a list of state machines returns transitions these state machines haven't performed. Used
384
   * to ensure unit test coverage.
385
   */
386
  public List<Transition<State, TransitionEvent<ExplicitEvent>>> getUnvisitedTransitions(
387
      List<StateMachine<State, ExplicitEvent, Data>> stateMachines) {
388
    Set<Transition<State, TransitionEvent<ExplicitEvent>>> taken = new HashSet<>();
1✔
389
    for (StateMachine<State, ExplicitEvent, Data> stateMachine : stateMachines) {
1✔
390
      List<Transition<State, TransitionEvent<ExplicitEvent>>> history =
1✔
391
          stateMachine.getTransitionHistory();
1✔
392
      taken.addAll(history);
1✔
393
    }
1✔
394
    List<Transition<State, TransitionEvent<ExplicitEvent>>> result = new ArrayList<>();
1✔
395
    for (Transition<State, TransitionEvent<ExplicitEvent>> transition : transitions.keySet()) {
1✔
396
      if (!taken.contains(transition)) {
1✔
397
        result.add(transition);
×
398
      }
399
    }
1✔
400
    return result;
1✔
401
  }
402

403
  /**
404
   * Given a list of state machines generate state diagram that shows which state transitions have
405
   * not been covered.
406
   */
407
  public String asPlantUMLStateDiagramCoverage(
408
      List<StateMachine<State, ExplicitEvent, Data>> stateMachines) {
409
    Set<State> visited = new HashSet<>();
×
410
    Set<Transition> taken = new HashSet<>();
×
411
    for (StateMachine<State, ExplicitEvent, Data> stateMachine : stateMachines) {
×
412
      List<Transition<State, TransitionEvent<ExplicitEvent>>> history =
×
413
          stateMachine.getTransitionHistory();
×
414
      for (Transition<State, TransitionEvent<ExplicitEvent>> transition : history) {
×
415
        visited.add(transition.getFrom());
×
416
        taken.add(transition);
×
417
      }
×
418
    }
×
419
    StringBuilder result = new StringBuilder();
×
420
    result.append("@startuml\n");
×
421
    result.append("title Test Coverage of ");
×
422
    result.append(this.getName());
×
423
    result.append(" State Transitions\n");
×
424
    result.append(
×
425
        "skinparam {\n"
426
            + "  ArrowColor green\n"
427
            + "  ArrowThickness 2\n"
428
            + "}\n"
429
            + "\n"
430
            + "skinparam state {\n"
431
            + " BackgroundColor green\n"
432
            + " BorderColor black\n"
433
            + " BackgroundColor<<NotCovered>> red\n"
434
            + "}\n");
435
    result.append("[*] --> ");
×
436
    result.append(initialState);
×
437
    result.append('\n');
×
438
    List<Transition<State, TransitionEvent<ExplicitEvent>>> transitionList = new ArrayList<>();
×
439
    transitionList.addAll(transitions.keySet());
×
440
    transitionList.sort(Comparator.comparing((tt) -> tt.getFrom().toString()));
×
441
    for (Transition<State, TransitionEvent<ExplicitEvent>> transition : transitionList) {
×
442
      TransitionAction<State, Data> action = transitions.get(transition);
×
443
      List<State> targets = action.getAllowedStates();
×
444
      for (State target : targets) {
×
445
        State from = transition.getFrom();
×
446
        result.append(from);
×
447
        if (!visited.contains(from)) {
×
448
          result.append("<< NotCovered >>");
×
449
        }
450
        if (taken.contains(transition)) {
×
451
          result.append(" --> ");
×
452
        } else {
453
          result.append(" -[#red]-> ");
×
454
        }
455
        result.append(target);
×
456
        result.append(": ");
×
457
        result.append(transition.getExplicitEvent());
×
458
        result.append('\n');
×
459
      }
×
460
    }
×
461
    for (State finalState : finalStates) {
×
462
      result.append(finalState);
×
463
      result.append(" --> [*]\n");
×
464
    }
×
465
    result.append("center footer Copyright (C) ");
×
466
    result.append("2020");
×
467
    result.append(" Temporal Technologies, Inc. All Rights Reserved.\n");
×
468
    result.append("@enduml\n");
×
469
    return result.toString();
×
470
  }
471

472
  public TransitionAction<State, Data> getTransitionAction(
473
      Transition<State, TransitionEvent<ExplicitEvent>> transition) {
474
    return transitions.get(transition);
1✔
475
  }
476
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc