Skip to content

Commit 47d881b

Browse files
authored
Deserialized Search Attributes API and full Test Server implementation for Search Attributes (#1067)
1 parent d980261 commit 47d881b

46 files changed

Lines changed: 1749 additions & 601 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

temporal-kotlin/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ buildscript {
77
plugins {
88
// id 'org.jetbrains.kotlin.jvm' version '1.4.32'
99
// id 'org.jetbrains.kotlin.jvm' version '1.5.32'
10-
// id 'org.jetbrains.kotlin.jvm' version '1.6.10'
10+
// id 'org.jetbrains.kotlin.jvm' version '1.6.20'
1111
id 'org.jetbrains.kotlin.jvm' version "${kotlinVersion}"
1212
id 'org.jlleitschuh.gradle.ktlint' version '10.2.1'
1313
}

temporal-sdk/src/main/java/io/temporal/client/WorkflowOptions.java

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import io.temporal.common.context.ContextPropagator;
2828
import io.temporal.internal.common.OptionsUtils;
2929
import java.time.Duration;
30+
import java.util.Collection;
3031
import java.util.List;
3132
import java.util.Map;
3233
import javax.annotation.Nullable;
@@ -92,7 +93,7 @@ public static final class Builder {
9293

9394
private Map<String, Object> memo;
9495

95-
private Map<String, Object> searchAttributes;
96+
private Map<String, ?> searchAttributes;
9697

9798
private List<ContextPropagator> contextPropagators;
9899

@@ -211,10 +212,26 @@ public Builder setMemo(Map<String, Object> memo) {
211212
}
212213

213214
/**
214-
* Specifies additional indexed information in result of list workflow. The type of value should
215-
* be basic type such as: String, Integer, Boolean, Double,LocalDateTime
215+
* Specifies Search Attributes map {@code searchAttributes} that will be attached to the
216+
* Workflow. Search Attributes are additional indexed information attributed to workflow and
217+
* used for search and visibility.
218+
*
219+
* <p>The search attributes can be used in query of List/Scan/Count workflow APIs. The key and
220+
* its value type must be registered on Temporal server side.
221+
*
222+
* <p>Supported Java types of the value:
223+
*
224+
* <ul>
225+
* <li>String
226+
* <li>Long, Integer, Short, Byte
227+
* <li>Boolean
228+
* <li>Double
229+
* <li>OffsetDateTime
230+
* <li>{@link Collection} of the types above
231+
* </ul>
216232
*/
217-
public Builder setSearchAttributes(Map<String, Object> searchAttributes) {
233+
// Workflow#upsertSearchAttributes docs needs to be kept in sync with this method
234+
public Builder setSearchAttributes(Map<String, ?> searchAttributes) {
218235
this.searchAttributes = searchAttributes;
219236
return this;
220237
}
@@ -285,7 +302,7 @@ public WorkflowOptions validateBuildWithDefaults() {
285302

286303
private final Map<String, Object> memo;
287304

288-
private final Map<String, Object> searchAttributes;
305+
private final Map<String, ?> searchAttributes;
289306

290307
private final List<ContextPropagator> contextPropagators;
291308

@@ -299,7 +316,7 @@ private WorkflowOptions(
299316
RetryOptions retryOptions,
300317
String cronSchedule,
301318
Map<String, Object> memo,
302-
Map<String, Object> searchAttributes,
319+
Map<String, ?> searchAttributes,
303320
List<ContextPropagator> contextPropagators) {
304321
this.workflowId = workflowId;
305322
this.workflowIdReusePolicy = workflowIdReusePolicy;
@@ -350,7 +367,7 @@ public Map<String, Object> getMemo() {
350367
return memo;
351368
}
352369

353-
public Map<String, Object> getSearchAttributes() {
370+
public Map<String, ?> getSearchAttributes() {
354371
return searchAttributes;
355372
}
356373

temporal-sdk/src/test/java/io/temporal/internal/common/SearchAttributesUtil.java renamed to temporal-sdk/src/main/java/io/temporal/common/SearchAttribute.java

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,17 @@
1717
* permissions and limitations under the License.
1818
*/
1919

20-
package io.temporal.internal.common;
20+
package io.temporal.common;
2121

22-
import io.temporal.api.common.v1.SearchAttributes;
23-
import io.temporal.common.converter.DataConverter;
22+
import java.util.Collections;
23+
import java.util.List;
24+
import java.util.Map;
2425

25-
public class SearchAttributesUtil {
26-
private static final DataConverter jsonConverter = DataConverter.getDefaultInstance();
27-
28-
public static <T> T getValueFromSearchAttributes(
29-
SearchAttributes searchAttributes, String key, Class<T> classType) {
30-
if (searchAttributes == null || key == null || key.isEmpty()) {
31-
return null;
32-
}
33-
return jsonConverter.fromPayload(
34-
searchAttributes.getIndexedFieldsOrThrow(key), classType, classType);
35-
}
26+
public class SearchAttribute {
27+
/**
28+
* Passing this value as a search attribute value into {@link
29+
* io.temporal.workflow.Workflow#upsertSearchAttributes(Map)} will lead to unsetting the search
30+
* attribute with the corresponded name if any present.
31+
*/
32+
public static final List<Object> UNSET_VALUE = Collections.emptyList();
3633
}

temporal-sdk/src/main/java/io/temporal/common/converter/ByteArrayPayloadConverter.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ public Optional<Payload> toData(Object value) throws DataConverterException {
4343
}
4444

4545
@Override
46-
@SuppressWarnings("unchecked")
4746
public <T> T fromData(Payload content, Class<T> valueClass, Type valueType)
4847
throws DataConverterException {
4948
ByteString data = content.getData();

temporal-sdk/src/main/java/io/temporal/common/converter/JacksonJsonPayloadConverter.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import com.fasterxml.jackson.annotation.JsonAutoDetect;
2323
import com.fasterxml.jackson.annotation.PropertyAccessor;
2424
import com.fasterxml.jackson.core.JsonProcessingException;
25+
import com.fasterxml.jackson.databind.DeserializationFeature;
2526
import com.fasterxml.jackson.databind.JavaType;
2627
import com.fasterxml.jackson.databind.ObjectMapper;
2728
import com.fasterxml.jackson.databind.SerializationFeature;
@@ -39,6 +40,10 @@ public class JacksonJsonPayloadConverter implements PayloadConverter {
3940

4041
public JacksonJsonPayloadConverter() {
4142
mapper = new ObjectMapper();
43+
// preserve the original value of timezone coming from the server in Payload
44+
// without adjusting to the host timezone
45+
// may be important if the replay is happening on a host in another timezone
46+
mapper.configure(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE, false);
4247
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
4348
mapper.registerModule(new JavaTimeModule());
4449
mapper.registerModule(new Jdk8Module());

temporal-sdk/src/main/java/io/temporal/common/interceptors/WorkflowOutboundCallsInterceptor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,7 @@ <R> R mutableSideEffect(
486486

487487
UUID randomUUID();
488488

489-
void upsertSearchAttributes(Map<String, Object> searchAttributes);
489+
void upsertSearchAttributes(Map<String, ?> searchAttributes);
490490

491491
/**
492492
* Intercepts creation of the workflow child thread.

temporal-sdk/src/main/java/io/temporal/common/interceptors/WorkflowOutboundCallsInterceptorBase.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ public UUID randomUUID() {
135135
}
136136

137137
@Override
138-
public void upsertSearchAttributes(Map<String, Object> searchAttributes) {
138+
public void upsertSearchAttributes(Map<String, ?> searchAttributes) {
139139
next.upsertSearchAttributes(searchAttributes);
140140
}
141141

temporal-sdk/src/main/java/io/temporal/failure/ServerFailure.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,17 @@
1919

2020
package io.temporal.failure;
2121

22+
import javax.annotation.Nullable;
23+
2224
/** Exceptions originated at the Temporal service. */
2325
public final class ServerFailure extends TemporalFailure {
2426
private final boolean nonRetryable;
2527

26-
public ServerFailure(String message, boolean nonRetryable, Throwable cause) {
28+
public ServerFailure(String message, boolean nonRetryable) {
29+
this(message, nonRetryable, null);
30+
}
31+
32+
public ServerFailure(String message, boolean nonRetryable, @Nullable Throwable cause) {
2733
super(message, message, cause);
2834
this.nonRetryable = nonRetryable;
2935
}

temporal-sdk/src/main/java/io/temporal/internal/client/RootWorkflowClientHelper.java

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@
3535
import io.temporal.client.WorkflowOptions;
3636
import io.temporal.common.RetryOptions;
3737
import io.temporal.common.context.ContextPropagator;
38-
import io.temporal.common.converter.DataConverter;
3938
import io.temporal.common.interceptors.WorkflowClientCallsInterceptor;
4039
import io.temporal.internal.common.ProtobufTimeUtils;
40+
import io.temporal.internal.common.SearchAttributesUtil;
4141
import java.util.*;
4242

4343
final class RootWorkflowClientHelper {
@@ -91,12 +91,8 @@ StartWorkflowExecutionRequest newStartWorkflowExecutionRequest(
9191
Memo.newBuilder()
9292
.putAllFields(intoPayloadMap(clientOptions.getDataConverter(), options.getMemo())));
9393
}
94-
if (options.getSearchAttributes() != null) {
95-
request.setSearchAttributes(
96-
SearchAttributes.newBuilder()
97-
.putAllIndexedFields(
98-
intoPayloadMap(
99-
DataConverter.getDefaultInstance(), options.getSearchAttributes())));
94+
if (options.getSearchAttributes() != null && !options.getSearchAttributes().isEmpty()) {
95+
request.setSearchAttributes(SearchAttributesUtil.encode(options.getSearchAttributes()));
10096
}
10197

10298
Header grpcHeader =

temporal-sdk/src/main/java/io/temporal/internal/common/HistoryJsonUtils.java

Lines changed: 2 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919

2020
package io.temporal.internal.common;
2121

22-
import com.google.common.base.CaseFormat;
2322
import com.jayway.jsonpath.Configuration;
2423
import com.jayway.jsonpath.DocumentContext;
2524
import com.jayway.jsonpath.JsonPath;
@@ -30,10 +29,6 @@
3029
* Helper methods supporting transformation of History's "Proto Json" compatible format, which is
3130
* supported by {@link com.google.protobuf.util.JsonFormat} to the format of Temporal history
3231
* supported by tctl and back.
33-
*
34-
* @see <a
35-
* href="https://github.com/temporalio/gogo-protobuf/commit/b38fb010909b8f81e2e600dc6f04925fc71d6a5e">
36-
* Related commit to Go Proto module</>
3732
*/
3833
class HistoryJsonUtils {
3934
private static final Configuration JSON_PATH_CONFIGURATION =
@@ -63,11 +58,11 @@ private enum EnumValueConversionPolicy {
6358
}
6459

6560
public static String protoJsonToHistoryFormatJson(String protoJson) {
66-
return convertEnumValues(protoJson, HistoryJsonUtils::enumProtoToHistory);
61+
return convertEnumValues(protoJson, ProtoEnumNameUtils::uniqueToSimplifiedName);
6762
}
6863

6964
public static String historyFormatJsonToProtoJson(String historyFormatJson) {
70-
return convertEnumValues(historyFormatJson, HistoryJsonUtils::enumHistoryToProto);
65+
return convertEnumValues(historyFormatJson, ProtoEnumNameUtils::simplifiedToUniqueName);
7166
}
7267

7368
private static String convertEnumValues(
@@ -83,26 +78,4 @@ private static String convertEnumValues(
8378
}
8479
return parsed.jsonString();
8580
}
86-
87-
private static String enumProtoToHistory(String protoEnumValue, String prefix) {
88-
if (!protoEnumValue.startsWith(prefix)) {
89-
throw new IllegalArgumentException("protoEnumValue should start with " + prefix + " prefix");
90-
}
91-
protoEnumValue = protoEnumValue.substring(prefix.length());
92-
return screamingCaseEventTypeToCamelCase(protoEnumValue);
93-
}
94-
95-
private static String enumHistoryToProto(String historyEnumValue, String prefix) {
96-
return prefix + camelCaseToScreamingCase(historyEnumValue);
97-
}
98-
99-
// https://github.com/temporalio/gogo-protobuf/commit/b38fb010909b8f81e2e600dc6f04925fc71d6a5e
100-
private static String camelCaseToScreamingCase(String camel) {
101-
return CaseFormat.UPPER_CAMEL.converterTo(CaseFormat.UPPER_UNDERSCORE).convert(camel);
102-
}
103-
104-
// https://github.com/temporalio/gogo-protobuf/commit/b38fb010909b8f81e2e600dc6f04925fc71d6a5e
105-
private static String screamingCaseEventTypeToCamelCase(String screaming) {
106-
return CaseFormat.UPPER_UNDERSCORE.converterTo(CaseFormat.UPPER_CAMEL).convert(screaming);
107-
}
10881
}

0 commit comments

Comments
 (0)