Skip to content

Commit 405438b

Browse files
authored
Merge pull request #39444: Fix nullness for PubsubIO
2 parents 1d25d3b + fdac189 commit 405438b

23 files changed

Lines changed: 788 additions & 530 deletions

sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/pubsub/AddTimestampAttribute.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package org.apache.beam.sdk.io.gcp.pubsub;
1919

2020
import static org.apache.beam.sdk.io.gcp.pubsub.PubsubMessageToRow.TIMESTAMP_FIELD;
21+
import static org.apache.beam.sdk.util.Preconditions.checkArgumentNotNull;
2122

2223
import org.apache.beam.sdk.schemas.transforms.DropFields;
2324
import org.apache.beam.sdk.transforms.PTransform;
@@ -31,9 +32,6 @@
3132
* Adds a timestamp attribute if desired and filters it out of the underlying row if no timestamp
3233
* attribute exists.
3334
*/
34-
@SuppressWarnings({
35-
"nullness" // TODO(https://github.com/apache/beam/issues/20497)
36-
})
3735
class AddTimestampAttribute extends PTransform<PCollection<Row>, PCollection<Row>> {
3836
private static final Logger LOG = LoggerFactory.getLogger(AddTimestampAttribute.class);
3937
private final boolean useTimestampAttribute;
@@ -48,7 +46,14 @@ public PCollection<Row> expand(PCollection<Row> input) {
4846
// element's event time. PubSubIO will populate the attribute from there.
4947
PCollection<Row> withTimestamp =
5048
useTimestampAttribute
51-
? input.apply(WithTimestamps.of((row) -> row.getDateTime(TIMESTAMP_FIELD).toInstant()))
49+
? input.apply(
50+
WithTimestamps.of(
51+
(row) ->
52+
checkArgumentNotNull(
53+
row.getDateTime(TIMESTAMP_FIELD),
54+
"Field '%s' must be present and non-null in the input row when writing to PubSub with a timestamp attribute.",
55+
TIMESTAMP_FIELD)
56+
.toInstant()))
5257
: input;
5358

5459
PCollection<Row> rows;

sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/pubsub/ExternalWrite.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
*/
1818
package org.apache.beam.sdk.io.gcp.pubsub;
1919

20+
import static org.apache.beam.sdk.util.Preconditions.checkArgumentNotNull;
21+
2022
import com.google.auto.service.AutoService;
2123
import java.util.Map;
2224
import org.apache.beam.sdk.expansion.ExternalTransformRegistrar;
@@ -35,9 +37,6 @@
3537

3638
/** Exposes {@link PubsubIO.Write} as an external transform for cross-language usage. */
3739
@AutoService(ExternalTransformRegistrar.class)
38-
@SuppressWarnings({
39-
"nullness" // TODO(https://github.com/apache/beam/issues/20497)
40-
})
4140
public final class ExternalWrite implements ExternalTransformRegistrar {
4241
public ExternalWrite() {}
4342

@@ -50,7 +49,7 @@ public ExternalWrite() {}
5049

5150
/** Parameters class to expose the transform to an external SDK. */
5251
public static class Configuration {
53-
private String topic;
52+
private @Nullable String topic;
5453
private @Nullable String idAttribute;
5554
private @Nullable String timestampAttribute;
5655
private boolean publishWithOrderingKey = false;
@@ -80,8 +79,9 @@ public WriteBuilder() {}
8079
public PTransform<PCollection<byte[]>, PDone> buildExternal(Configuration config) {
8180
PubsubIO.Write.Builder<byte[]> writeBuilder =
8281
PubsubIO.Write.newBuilder(new ParsePubsubMessageProtoAsPayloadFromWindowedValue());
83-
if (config.topic != null) {
84-
StaticValueProvider<String> topic = StaticValueProvider.of(config.topic);
82+
String topicStr = config.topic;
83+
if (topicStr != null) {
84+
StaticValueProvider<String> topic = StaticValueProvider.of(topicStr);
8585
writeBuilder.setTopicProvider(NestedValueProvider.of(topic, PubsubTopic::fromPath));
8686
}
8787
if (config.idAttribute != null) {
@@ -102,7 +102,8 @@ public static class ParsePubsubMessageProtoAsPayloadFromWindowedValue
102102

103103
@Override
104104
public PubsubMessage apply(ValueInSingleWindow<byte[]> input) {
105-
return INNER.apply(input.getValue());
105+
return INNER.apply(
106+
checkArgumentNotNull(input.getValue(), "Windowed value input cannot be null"));
106107
}
107108
}
108109
}

sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/pubsub/NestedRowToMessage.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import static org.apache.beam.sdk.io.gcp.pubsub.PubsubSchemaIOProvider.ATTRIBUTE_ARRAY_FIELD_TYPE;
2323
import static org.apache.beam.sdk.io.gcp.pubsub.PubsubSchemaIOProvider.ATTRIBUTE_MAP_FIELD_TYPE;
2424
import static org.apache.beam.sdk.util.Preconditions.checkArgumentNotNull;
25+
import static org.apache.beam.sdk.util.Preconditions.checkStateNotNull;
2526
import static org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.base.Preconditions.checkArgument;
2627

2728
import java.util.Collection;
@@ -34,16 +35,17 @@
3435
import org.apache.beam.sdk.transforms.SimpleFunction;
3536
import org.apache.beam.sdk.values.Row;
3637
import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.ImmutableMap;
38+
import org.checkerframework.checker.nullness.qual.Nullable;
3739

3840
class NestedRowToMessage extends SimpleFunction<Row, PubsubMessage> {
3941
private static final long serialVersionUID = 65176815766314684L;
4042

41-
private final PayloadSerializer serializer;
43+
private final @Nullable PayloadSerializer serializer;
4244
private final SerializableFunction<Row, Map<String, String>> attributesExtractor;
4345
private final SerializableFunction<Row, byte[]> payloadExtractor;
4446

4547
@SuppressWarnings("methodref.receiver.bound")
46-
NestedRowToMessage(PayloadSerializer serializer, Schema schema) {
48+
NestedRowToMessage(@Nullable PayloadSerializer serializer, Schema schema) {
4749
this.serializer = serializer;
4850
if (schema.getField(ATTRIBUTES_FIELD).getType().equals(ATTRIBUTE_MAP_FIELD_TYPE)) {
4951
attributesExtractor = NestedRowToMessage::getAttributesFromMap;
@@ -81,7 +83,7 @@ private static byte[] getPayloadFromBytes(Row row) {
8183
}
8284

8385
private byte[] getPayloadFromNested(Row row) {
84-
return serializer.serialize(checkArgumentNotNull(row.getRow(PAYLOAD_FIELD)));
86+
return checkStateNotNull(serializer).serialize(checkArgumentNotNull(row.getRow(PAYLOAD_FIELD)));
8587
}
8688

8789
@Override

sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/pubsub/PubSubPayloadTranslation.java

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

2020
import static org.apache.beam.sdk.io.gcp.pubsub.PubsubIO.ENABLE_CUSTOM_PUBSUB_SINK;
2121
import static org.apache.beam.sdk.io.gcp.pubsub.PubsubIO.ENABLE_CUSTOM_PUBSUB_SOURCE;
22+
import static org.apache.beam.sdk.util.Preconditions.checkStateNotNull;
2223

2324
import com.google.auto.service.AutoService;
2425
import java.util.Collections;
@@ -37,16 +38,13 @@
3738
import org.apache.beam.sdk.options.ValueProvider.NestedValueProvider;
3839
import org.apache.beam.sdk.runners.AppliedPTransform;
3940
import org.apache.beam.sdk.transforms.PTransform;
40-
import org.apache.beam.sdk.util.Preconditions;
4141
import org.apache.beam.sdk.util.construction.PTransformTranslation;
4242
import org.apache.beam.sdk.util.construction.PTransformTranslation.TransformPayloadTranslator;
4343
import org.apache.beam.sdk.util.construction.SdkComponents;
4444
import org.apache.beam.sdk.util.construction.TransformPayloadTranslatorRegistrar;
4545
import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.ImmutableMap;
46+
import org.checkerframework.checker.nullness.qual.Nullable;
4647

47-
@SuppressWarnings({
48-
"nullness" // TODO(https://github.com/apache/beam/issues/20497)
49-
})
5048
/**
5149
* Utility methods for translating a {@link Unbounded} which reads from {@link
5250
* PubsubUnboundedSource} to {@link RunnerApi} representations.
@@ -61,7 +59,7 @@ public String getUrn() {
6159
}
6260

6361
@Override
64-
public RunnerApi.FunctionSpec translate(
62+
public RunnerApi.@Nullable FunctionSpec translate(
6563
AppliedPTransform<?, ?, Unbounded<?>> transform, SdkComponents components) {
6664
if (ExperimentalOptions.hasExperiment(
6765
transform.getPipeline().getOptions(), ENABLE_CUSTOM_PUBSUB_SOURCE)) {
@@ -93,11 +91,13 @@ public RunnerApi.FunctionSpec translate(
9391
}
9492
}
9593

96-
if (pubsubUnboundedSource.getTimestampAttribute() != null) {
97-
payloadBuilder.setTimestampAttribute(pubsubUnboundedSource.getTimestampAttribute());
94+
String timestampAttribute = pubsubUnboundedSource.getTimestampAttribute();
95+
if (timestampAttribute != null) {
96+
payloadBuilder.setTimestampAttribute(timestampAttribute);
9897
}
99-
if (pubsubUnboundedSource.getIdAttribute() != null) {
100-
payloadBuilder.setIdAttribute(pubsubUnboundedSource.getIdAttribute());
98+
String idAttribute = pubsubUnboundedSource.getIdAttribute();
99+
if (idAttribute != null) {
100+
payloadBuilder.setIdAttribute(idAttribute);
101101
}
102102
payloadBuilder.setWithAttributes(
103103
pubsubUnboundedSource.getNeedsAttributes() || pubsubUnboundedSource.getNeedsMessageId());
@@ -116,7 +116,7 @@ public String getUrn() {
116116
}
117117

118118
@Override
119-
public RunnerApi.FunctionSpec translate(
119+
public RunnerApi.@Nullable FunctionSpec translate(
120120
AppliedPTransform<?, ?, PubsubUnboundedSink.PubsubSink> transform,
121121
SdkComponents components) {
122122
if (ExperimentalOptions.hasExperiment(
@@ -125,19 +125,20 @@ public RunnerApi.FunctionSpec translate(
125125
}
126126
PubSubWritePayload.Builder payloadBuilder = PubSubWritePayload.newBuilder();
127127
ValueProvider<TopicPath> topicProvider =
128-
Preconditions.checkStateNotNull(transform.getTransform().outer.getTopicProvider());
128+
checkStateNotNull(transform.getTransform().outer.getTopicProvider());
129129
if (topicProvider.isAccessible()) {
130130
payloadBuilder.setTopic(topicProvider.get().getFullPath());
131131
} else {
132132
payloadBuilder.setTopicRuntimeOverridden(
133133
((NestedValueProvider) topicProvider).propertyName());
134134
}
135-
if (transform.getTransform().outer.getTimestampAttribute() != null) {
136-
payloadBuilder.setTimestampAttribute(
137-
transform.getTransform().outer.getTimestampAttribute());
135+
String timestampAttribute = transform.getTransform().outer.getTimestampAttribute();
136+
if (timestampAttribute != null) {
137+
payloadBuilder.setTimestampAttribute(timestampAttribute);
138138
}
139-
if (transform.getTransform().outer.getIdAttribute() != null) {
140-
payloadBuilder.setIdAttribute(transform.getTransform().outer.getIdAttribute());
139+
String idAttribute = transform.getTransform().outer.getIdAttribute();
140+
if (idAttribute != null) {
141+
payloadBuilder.setIdAttribute(idAttribute);
141142
}
142143
return FunctionSpec.newBuilder()
143144
.setUrn(getUrn(transform.getTransform()))
@@ -154,20 +155,21 @@ public String getUrn() {
154155
}
155156

156157
@Override
157-
public RunnerApi.FunctionSpec translate(
158+
public RunnerApi.@Nullable FunctionSpec translate(
158159
AppliedPTransform<?, ?, PubsubUnboundedSink.PubsubDynamicSink> transform,
159160
SdkComponents components) {
160161
if (ExperimentalOptions.hasExperiment(
161162
transform.getPipeline().getOptions(), ENABLE_CUSTOM_PUBSUB_SINK)) {
162163
return null;
163164
}
164165
PubSubWritePayload.Builder payloadBuilder = PubSubWritePayload.newBuilder();
165-
if (transform.getTransform().outer.getTimestampAttribute() != null) {
166-
payloadBuilder.setTimestampAttribute(
167-
transform.getTransform().outer.getTimestampAttribute());
166+
String timestampAttribute = transform.getTransform().outer.getTimestampAttribute();
167+
if (timestampAttribute != null) {
168+
payloadBuilder.setTimestampAttribute(timestampAttribute);
168169
}
169-
if (transform.getTransform().outer.getIdAttribute() != null) {
170-
payloadBuilder.setIdAttribute(transform.getTransform().outer.getIdAttribute());
170+
String idAttribute = transform.getTransform().outer.getIdAttribute();
171+
if (idAttribute != null) {
172+
payloadBuilder.setIdAttribute(idAttribute);
171173
}
172174
return FunctionSpec.newBuilder()
173175
.setUrn(getUrn(transform.getTransform()))

sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/pubsub/PubsubClient.java

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
*/
1818
package org.apache.beam.sdk.io.gcp.pubsub;
1919

20+
import static org.apache.beam.sdk.util.Preconditions.checkArgumentNotNull;
21+
import static org.apache.beam.sdk.util.Preconditions.checkStateNotNull;
2022
import static org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.base.Preconditions.checkArgument;
2123
import static org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.base.Preconditions.checkNotNull;
2224
import static org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.base.Preconditions.checkState;
@@ -40,13 +42,11 @@
4042
import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.ImmutableList;
4143
import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.ImmutableMap;
4244
import org.checkerframework.checker.nullness.qual.Nullable;
45+
import org.checkerframework.dataflow.qual.Pure;
4346
import org.slf4j.Logger;
4447
import org.slf4j.LoggerFactory;
4548

4649
/** An (abstract) helper class for talking to Pubsub via an underlying transport. */
47-
@SuppressWarnings({
48-
"nullness" // TODO(https://github.com/apache/beam/issues/20497)
49-
})
5050
public abstract class PubsubClient implements Closeable {
5151
private static final Logger LOG = LoggerFactory.getLogger(PubsubClient.class);
5252
private static final Map<String, SerializableFunction<String, Schema>>
@@ -82,7 +82,7 @@ PubsubClient newClient(
8282
* Return timestamp as ms-since-unix-epoch corresponding to {@code timestamp}. Throw {@link
8383
* IllegalArgumentException} if timestamp cannot be recognized.
8484
*/
85-
protected static Long parseTimestampAsMsSinceEpoch(String timestamp) {
85+
protected static long parseTimestampAsMsSinceEpoch(String timestamp) {
8686
if (timestamp.isEmpty()) {
8787
throw new IllegalArgumentException("Empty timestamp.");
8888
}
@@ -113,17 +113,12 @@ protected static long extractTimestampAttribute(
113113
String timestampAttribute, @Nullable Map<String, String> attributes) {
114114
Preconditions.checkState(!timestampAttribute.isEmpty());
115115
String value = attributes == null ? null : attributes.get(timestampAttribute);
116-
checkArgument(
117-
value != null,
118-
"PubSub message is missing a value for timestamp attribute %s",
119-
timestampAttribute);
120-
Long timestampMsSinceEpoch = parseTimestampAsMsSinceEpoch(value);
121-
checkArgument(
122-
timestampMsSinceEpoch != null,
123-
"Cannot interpret value of attribute %s as timestamp: %s",
124-
timestampAttribute,
125-
value);
126-
return timestampMsSinceEpoch;
116+
String nonNullValue =
117+
checkArgumentNotNull(
118+
value,
119+
"PubSub message is missing a value for timestamp attribute %s",
120+
timestampAttribute);
121+
return parseTimestampAsMsSinceEpoch(nonNullValue);
127122
}
128123

129124
/** Path representing a cloud project id. */
@@ -386,17 +381,21 @@ public static TopicPath topicPathFromName(String projectId, String topicName) {
386381
public abstract static class OutgoingMessage implements Serializable {
387382

388383
/** Underlying Message. May not have publish timestamp set. */
384+
@Pure
389385
public abstract PubsubMessage getMessage();
390386

391387
/** Timestamp for element (ms since epoch). */
388+
@Pure
392389
public abstract long getTimestampMsSinceEpoch();
393390

394391
/**
395392
* If using an id attribute, the record id to associate with this record's metadata so the
396393
* receiver can reject duplicates. Otherwise {@literal null}.
397394
*/
395+
@Pure
398396
public abstract @Nullable String recordId();
399397

398+
@Pure
400399
public abstract @Nullable String topic();
401400

402401
public static OutgoingMessage of(
@@ -415,11 +414,13 @@ public static OutgoingMessage of(
415414
@Nullable String topic) {
416415
PubsubMessage.Builder builder =
417416
PubsubMessage.newBuilder().setData(ByteString.copyFrom(message.getPayload()));
418-
if (message.getAttributeMap() != null) {
419-
builder.putAllAttributes(message.getAttributeMap());
417+
Map<String, String> attributeMap = message.getAttributeMap();
418+
if (attributeMap != null) {
419+
builder.putAllAttributes(attributeMap);
420420
}
421-
if (message.getOrderingKey() != null) {
422-
builder.setOrderingKey(message.getOrderingKey());
421+
String orderingKey = message.getOrderingKey();
422+
if (orderingKey != null) {
423+
builder.setOrderingKey(orderingKey);
423424
}
424425
return of(builder.build(), timestampMsSinceEpoch, recordId, topic);
425426
}
@@ -435,21 +436,23 @@ public static OutgoingMessage of(
435436
public abstract static class IncomingMessage implements Serializable {
436437

437438
/** Underlying Message. */
439+
@Pure
438440
public abstract PubsubMessage message();
439441

440-
/**
441-
* Timestamp for element (ms since epoch). Either Pubsub's processing time, or the custom
442-
* timestamp associated with the message.
443-
*/
442+
/** Either Pubsub's processing time, or the custom timestamp associated with the message. */
443+
@Pure
444444
public abstract long timestampMsSinceEpoch();
445445

446446
/** Timestamp (in system time) at which we requested the message (ms since epoch). */
447+
@Pure
447448
public abstract long requestTimeMsSinceEpoch();
448449

449450
/** Id to pass back to Pubsub to acknowledge receipt of this message. */
451+
@Pure
450452
public abstract String ackId();
451453

452454
/** Id to pass to the runner to distinguish this message from all others. */
455+
@Pure
453456
public abstract String recordId();
454457

455458
public static IncomingMessage of(
@@ -554,7 +557,7 @@ public abstract void createSchema(
554557
public abstract void deleteSchema(SchemaPath schemaPath) throws IOException;
555558

556559
/** Return {@link SchemaPath} from {@link TopicPath} if exists. */
557-
public abstract SchemaPath getSchemaPath(TopicPath topicPath) throws IOException;
560+
public abstract @Nullable SchemaPath getSchemaPath(TopicPath topicPath) throws IOException;
558561

559562
/** Return a Beam {@link Schema} from the Pub/Sub schema resource, if exists. */
560563
public abstract Schema getSchema(SchemaPath schemaPath) throws IOException;
@@ -567,8 +570,10 @@ static Schema fromPubsubSchema(com.google.api.services.pubsub.model.Schema pubsu
567570
"Pub/Sub schema type %s is not supported at this time", pubsubSchema.getType()));
568571
}
569572
SerializableFunction<String, Schema> definitionToSchemaFn =
570-
schemaTypeToConversionFnMap.get(pubsubSchema.getType());
571-
return definitionToSchemaFn.apply(pubsubSchema.getDefinition());
573+
checkStateNotNull(schemaTypeToConversionFnMap.get(pubsubSchema.getType()));
574+
String definition =
575+
checkNotNull(pubsubSchema.getDefinition(), "Pub/Sub schema definition is null");
576+
return definitionToSchemaFn.apply(definition);
572577
}
573578

574579
/** Convert a {@link com.google.pubsub.v1.Schema} to a Beam {@link Schema}. */
@@ -579,7 +584,7 @@ static Schema fromPubsubSchema(com.google.pubsub.v1.Schema pubsubSchema) {
579584
String.format("Pub/Sub schema type %s is not supported at this time", typeName));
580585
}
581586
SerializableFunction<String, Schema> definitionToSchemaFn =
582-
schemaTypeToConversionFnMap.get(typeName);
587+
checkStateNotNull(schemaTypeToConversionFnMap.get(typeName));
583588
return definitionToSchemaFn.apply(pubsubSchema.getDefinition());
584589
}
585590

0 commit comments

Comments
 (0)