Skip to content

Commit d6f4d77

Browse files
committed
addressed pr comments
1 parent 4e7e86c commit d6f4d77

9 files changed

Lines changed: 148 additions & 8 deletions

File tree

client/src/main/java/com/microsoft/durabletask/AbstractTaskEntity.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,9 @@ private Object handleImplicitOperations(TaskEntityOperation operation) {
204204
}
205205

206206
// region Re-entrant self-dispatch
207+
// NOTE: Cross-SDK alignment — dispatch() is a Java-specific addition enabling re-entrant
208+
// self-calls for operation composition. Not present in the .NET SDK. Consider proposing
209+
// for cross-SDK alignment.
207210

208211
/**
209212
* Dispatches an operation to this entity instance synchronously (re-entrant self-call).

client/src/main/java/com/microsoft/durabletask/CallEntityOptions.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
/**
99
* Options for calling a durable entity and waiting for a response.
1010
*/
11+
// NOTE: Cross-SDK alignment — setTimeout(Duration) is a Java-specific addition using a timer-based
12+
// implementation. Not present in the .NET SDK. Consider proposing for cross-SDK alignment.
1113
public final class CallEntityOptions {
1214
private Duration timeout;
1315

client/src/main/java/com/microsoft/durabletask/EntityMetadata.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,8 @@ public String getSerializedState() {
115115
* Gets whether this metadata response includes the entity state.
116116
* <p>
117117
* Queries can exclude the state of the entity from the metadata that is retrieved.
118-
* When this returns {@code false}, {@link #getSerializedState()} and {@link #readStateAs(Class)}
119-
* will return {@code null}.
118+
* When this returns {@code false}, calling {@link #readStateAs(Class)} will throw
119+
* {@link IllegalStateException}. Use this method to check before accessing state.
120120
*
121121
* @return {@code true} if state was requested and included in this metadata
122122
*/
@@ -140,10 +140,17 @@ DataConverter getDataConverter() {
140140
*
141141
* @param stateType the class to deserialize the state into
142142
* @param <T> the target type
143-
* @return the deserialized state, or {@code null} if no state is available
143+
* @return the deserialized state, or {@code null} if the entity has no state
144+
* @throws IllegalStateException if state was not included in this metadata
145+
* (i.e., {@link #isIncludesState()} returns {@code false})
144146
*/
145147
@Nullable
146148
public <T> T readStateAs(Class<T> stateType) {
149+
if (!this.includesState) {
150+
throw new IllegalStateException(
151+
"Entity state was not included in this metadata response. " +
152+
"Check isIncludesState() before accessing state, or request state inclusion when querying.");
153+
}
147154
if (this.serializedState == null) {
148155
return null;
149156
}

client/src/main/java/com/microsoft/durabletask/GrpcDurableEntityClient.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ public void signalEntity(
5656
builder.setScheduledTime(ts);
5757
}
5858

59+
// Capture and propagate distributed trace context (matching .NET SDK pattern)
60+
TraceContext traceContext = TracingHelper.getCurrentTraceContext();
61+
if (traceContext != null) {
62+
builder.setParentTraceContext(traceContext);
63+
}
64+
5965
this.sidecarClient.signalEntity(builder.build());
6066
}
6167

client/src/main/java/com/microsoft/durabletask/TaskEntityState.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,20 @@ public <T> T getState(Class<T> stateType) {
5050
return this.dataConverter.deserialize(this.serializedState, stateType);
5151
}
5252

53+
/**
54+
* Gets the current entity state, deserialized to the specified type,
55+
* returning the provided default value if no state exists.
56+
*
57+
* @param stateType the class to deserialize the state into
58+
* @param defaultValue the value to return if no state exists
59+
* @param <T> the type of the state
60+
* @return the deserialized state, or {@code defaultValue} if no state exists
61+
*/
62+
public <T> T getState(Class<T> stateType, T defaultValue) {
63+
T state = getState(stateType);
64+
return state != null ? state : defaultValue;
65+
}
66+
5367
/**
5468
* Sets the entity state. The state will be serialized using the configured {@link DataConverter}.
5569
*

client/src/main/java/com/microsoft/durabletask/TypedEntityMetadata.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public final class TypedEntityMetadata<T> extends EntityMetadata {
3333
/**
3434
* Creates a new {@code TypedEntityMetadata} from an existing {@link EntityMetadata} and a state type.
3535
* <p>
36-
* The state is eagerly deserialized from the metadata's serialized state.
36+
* The state is eagerly deserialized from the metadata's serialized state when state is included.
3737
*
3838
* @param source the source metadata to wrap
3939
* @param stateType the class to deserialize the state into
@@ -48,18 +48,25 @@ public final class TypedEntityMetadata<T> extends EntityMetadata {
4848
source.isIncludesState(),
4949
source.getDataConverter());
5050
this.stateType = stateType;
51-
this.state = source.readStateAs(stateType);
51+
this.state = source.isIncludesState() ? source.readStateAs(stateType) : null;
5252
}
5353

5454
/**
5555
* Gets the deserialized entity state.
5656
* <p>
57-
* Returns {@code null} if the entity has no state or if state was not included in the query.
57+
* Returns {@code null} if the entity has no state.
5858
*
5959
* @return the deserialized state, or {@code null}
60+
* @throws IllegalStateException if state was not included in this metadata
61+
* (i.e., {@link #isIncludesState()} returns {@code false})
6062
*/
6163
@Nullable
6264
public T getState() {
65+
if (!this.isIncludesState()) {
66+
throw new IllegalStateException(
67+
"Entity state was not included in this metadata response. " +
68+
"Check isIncludesState() before accessing state, or request state inclusion when querying.");
69+
}
6370
return this.state;
6471
}
6572

client/src/test/java/com/microsoft/durabletask/EntityMetadataTest.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,20 @@ void readStateAs_deserializesStringState() {
6060
@Test
6161
void readStateAs_nullState_returnsNull() {
6262
EntityMetadata metadata = new EntityMetadata(
63-
"@counter@c1", Instant.EPOCH, 0, null, null, false, dataConverter);
63+
"@counter@c1", Instant.EPOCH, 0, null, null, true, dataConverter);
6464

6565
Integer state = metadata.readStateAs(Integer.class);
6666
assertNull(state);
6767
}
6868

69+
@Test
70+
void readStateAs_throwsWhenStateNotIncluded() {
71+
EntityMetadata metadata = new EntityMetadata(
72+
"@counter@c1", Instant.EPOCH, 0, null, null, false, dataConverter);
73+
74+
assertThrows(IllegalStateException.class, () -> metadata.readStateAs(Integer.class));
75+
}
76+
6977
@Test
7078
void lockedBy_nullWhenNotLocked() {
7179
EntityMetadata metadata = new EntityMetadata(
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
package com.microsoft.durabletask;
4+
5+
import org.junit.jupiter.api.Test;
6+
7+
import static org.junit.jupiter.api.Assertions.*;
8+
9+
/**
10+
* Unit tests for {@link TaskEntityState}.
11+
*/
12+
public class TaskEntityStateTest {
13+
14+
private static final DataConverter dataConverter = new JacksonDataConverter();
15+
16+
@Test
17+
void getState_returnsDeserializedValue() {
18+
TaskEntityState state = new TaskEntityState(dataConverter, "42");
19+
assertEquals(42, state.getState(Integer.class));
20+
}
21+
22+
@Test
23+
void getState_returnsNullWhenNoState() {
24+
TaskEntityState state = new TaskEntityState(dataConverter, null);
25+
assertNull(state.getState(Integer.class));
26+
}
27+
28+
@Test
29+
void getStateWithDefault_returnsValueWhenStateExists() {
30+
TaskEntityState state = new TaskEntityState(dataConverter, "42");
31+
assertEquals(42, state.getState(Integer.class, 0));
32+
}
33+
34+
@Test
35+
void getStateWithDefault_returnsDefaultWhenNoState() {
36+
TaskEntityState state = new TaskEntityState(dataConverter, null);
37+
assertEquals(0, state.getState(Integer.class, 0));
38+
}
39+
40+
@Test
41+
void getStateWithDefault_returnsDefaultAfterDelete() {
42+
TaskEntityState state = new TaskEntityState(dataConverter, "42");
43+
state.deleteState();
44+
assertEquals(99, state.getState(Integer.class, 99));
45+
}
46+
47+
@Test
48+
void hasState_trueWhenStateExists() {
49+
TaskEntityState state = new TaskEntityState(dataConverter, "\"hello\"");
50+
assertTrue(state.hasState());
51+
}
52+
53+
@Test
54+
void hasState_falseWhenNoState() {
55+
TaskEntityState state = new TaskEntityState(dataConverter, null);
56+
assertFalse(state.hasState());
57+
}
58+
59+
@Test
60+
void setState_updatesState() {
61+
TaskEntityState state = new TaskEntityState(dataConverter, null);
62+
state.setState(100);
63+
assertTrue(state.hasState());
64+
assertEquals(100, state.getState(Integer.class));
65+
}
66+
67+
@Test
68+
void deleteState_removesState() {
69+
TaskEntityState state = new TaskEntityState(dataConverter, "42");
70+
state.deleteState();
71+
assertFalse(state.hasState());
72+
assertNull(state.getState(Integer.class));
73+
}
74+
75+
@Test
76+
void rollback_restoresCommittedState() {
77+
TaskEntityState state = new TaskEntityState(dataConverter, "42");
78+
state.commit();
79+
state.setState(100);
80+
assertEquals(100, state.getState(Integer.class));
81+
state.rollback();
82+
assertEquals(42, state.getState(Integer.class));
83+
}
84+
}

client/src/test/java/com/microsoft/durabletask/TypedEntityMetadataTest.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,21 @@ void getState_deserializesStringState() {
3737
@Test
3838
void getState_nullWhenNoState() {
3939
EntityMetadata base = new EntityMetadata(
40-
"@counter@c1", Instant.EPOCH, 0, null, null, false, dataConverter);
40+
"@counter@c1", Instant.EPOCH, 0, null, null, true, dataConverter);
4141

4242
TypedEntityMetadata<Integer> typed = new TypedEntityMetadata<>(base, Integer.class);
4343
assertNull(typed.getState());
4444
}
4545

46+
@Test
47+
void getState_throwsWhenStateNotIncluded() {
48+
EntityMetadata base = new EntityMetadata(
49+
"@counter@c1", Instant.EPOCH, 0, null, null, false, dataConverter);
50+
51+
TypedEntityMetadata<Integer> typed = new TypedEntityMetadata<>(base, Integer.class);
52+
assertThrows(IllegalStateException.class, typed::getState);
53+
}
54+
4655
@Test
4756
void getStateType_returnsCorrectClass() {
4857
EntityMetadata base = new EntityMetadata(

0 commit comments

Comments
 (0)