Skip to content

Commit fe091a5

Browse files
Add logic to convert from Legacy Events to CloudEvents. (GoogleCloudPlatform#61)
* Add explanatory comments to the new code in BackgroundFunctionExecutor. * Add logic to convert from Legacy Events to CloudEvents. This allows received Legacy Events to be handled by function code that expects a CloudEvent input. The translation logic and tests are based closely on the corresponding code in https://github.com/GoogleCloudPlatform/functions-framework-dotnet. The new code also passes the relevant conformance tests in https://github.com/GoogleCloudPlatform/functions-framework-conformance, once GoogleCloudPlatform/functions-framework-conformance#30 is applied. For now I'm running those tests manually. I plan at least to commit the script and test functions that allow me to do so.
1 parent 3059d09 commit fe091a5

File tree

13 files changed

+626
-27
lines changed

13 files changed

+626
-27
lines changed

invoker/core/src/main/java/com/google/cloud/functions/invoker/BackgroundFunctionExecutor.java

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import io.cloudevents.core.message.impl.UnknownEncodingMessageReader;
3333
import java.io.BufferedReader;
3434
import java.io.IOException;
35+
import java.io.Reader;
3536
import java.lang.reflect.Type;
3637
import java.time.OffsetDateTime;
3738
import java.time.format.DateTimeFormatter;
@@ -171,17 +172,21 @@ static Optional<Type> backgroundFunctionTypeArgument(
171172

172173
private static Event parseLegacyEvent(HttpServletRequest req) throws IOException {
173174
try (BufferedReader bodyReader = req.getReader()) {
174-
// A Type Adapter is required to set the type of the JsonObject because CloudFunctionsContext
175-
// is abstract and Gson default behavior instantiates the type provided.
176-
TypeAdapter<CloudFunctionsContext> typeAdapter =
177-
CloudFunctionsContext.typeAdapter(new Gson());
178-
Gson gson = new GsonBuilder()
179-
.registerTypeAdapter(CloudFunctionsContext.class, typeAdapter)
180-
.registerTypeAdapter(Event.class, new Event.EventDeserializer())
181-
.create();
182-
return gson.fromJson(bodyReader, Event.class);
175+
return parseLegacyEvent(bodyReader);
183176
}
184177
}
178+
179+
static Event parseLegacyEvent(Reader reader) throws IOException {
180+
// A Type Adapter is required to set the type of the JsonObject because CloudFunctionsContext
181+
// is abstract and Gson default behavior instantiates the type provided.
182+
TypeAdapter<CloudFunctionsContext> typeAdapter =
183+
CloudFunctionsContext.typeAdapter(new Gson());
184+
Gson gson = new GsonBuilder()
185+
.registerTypeAdapter(CloudFunctionsContext.class, typeAdapter)
186+
.registerTypeAdapter(Event.class, new Event.EventDeserializer())
187+
.create();
188+
return gson.fromJson(reader, Event.class);
189+
}
185190

186191
private static Context contextFromCloudEvent(CloudEvent cloudEvent) {
187192
OffsetDateTime timestamp = Optional.ofNullable(cloudEvent.getTime()).orElse(OffsetDateTime.now());
@@ -291,7 +296,7 @@ void serviceCloudEvent(CloudEvent cloudEvent) throws Exception {
291296
}
292297
}
293298

294-
private static class CloudEventFunctionExecutor extends FunctionExecutor<Void>{
299+
private static class CloudEventFunctionExecutor extends FunctionExecutor<Void> {
295300
private final ExperimentalCloudEventsFunction function;
296301

297302
CloudEventFunctionExecutor(ExperimentalCloudEventsFunction function) {
@@ -301,8 +306,8 @@ private static class CloudEventFunctionExecutor extends FunctionExecutor<Void>{
301306

302307
@Override
303308
void serviceLegacyEvent(Event legacyEvent) throws Exception {
304-
throw new UnsupportedOperationException(
305-
"Conversion from legacy events to CloudEvents not yet implemented");
309+
CloudEvent cloudEvent = GcfEvents.convertToCloudEvent(legacyEvent);
310+
function.accept(cloudEvent);
306311
}
307312

308313
@Override

invoker/core/src/main/java/com/google/cloud/functions/invoker/CloudFunctionsContext.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import com.google.auto.value.AutoValue;
1818
import com.google.cloud.functions.Context;
1919
import com.google.gson.Gson;
20+
import com.google.gson.GsonBuilder;
2021
import com.google.gson.TypeAdapter;
2122
import java.lang.annotation.Retention;
2223
import java.lang.annotation.RetentionPolicy;
@@ -46,6 +47,9 @@ abstract class CloudFunctionsContext implements Context {
4647
@Nullable
4748
public abstract String resource();
4849

50+
// TODO: expose this in the Context interface (as a default method).
51+
abstract Map<String, String> params();
52+
4953
@Override
5054
public abstract Map<String, String> attributes();
5155

@@ -55,6 +59,7 @@ public static TypeAdapter<CloudFunctionsContext> typeAdapter(Gson gson) {
5559

5660
static Builder builder() {
5761
return new AutoValue_CloudFunctionsContext.Builder()
62+
.setParams(Collections.emptyMap())
5863
.setAttributes(Collections.emptyMap());
5964
}
6065

@@ -64,8 +69,51 @@ abstract static class Builder {
6469
abstract Builder setTimestamp(String x);
6570
abstract Builder setEventType(String x);
6671
abstract Builder setResource(String x);
72+
abstract Builder setParams(Map<String, String> x);
6773
abstract Builder setAttributes(Map<String, String> value);
6874

6975
abstract CloudFunctionsContext build();
7076
}
77+
78+
/**
79+
* Depending on the event type, the {@link Context#resource()} field is either a JSON string (complete
80+
* with encosing quotes) or a JSON object. This class allows us to redeserialize that JSON representation
81+
* into its components.
82+
*/
83+
@AutoValue
84+
abstract static class Resource {
85+
abstract @Nullable String service();
86+
abstract String name();
87+
abstract @Nullable String type();
88+
89+
static TypeAdapter<Resource> typeAdapter(Gson gson) {
90+
return new AutoValue_CloudFunctionsContext_Resource.GsonTypeAdapter(gson);
91+
}
92+
93+
static Resource from(String s) {
94+
Gson baseGson = new Gson();
95+
if (s.startsWith("\"") && s.endsWith("\"")) {
96+
String name = baseGson.fromJson(s, String.class);
97+
return builder().setName(name).build();
98+
}
99+
if (s.startsWith("{") && (s.endsWith("}") || s.endsWith("}\n"))) {
100+
TypeAdapter<Resource> typeAdapter = typeAdapter(baseGson);
101+
Gson gson = new GsonBuilder().registerTypeAdapter(Resource.class, typeAdapter).create();
102+
return gson.fromJson(s, Resource.class);
103+
}
104+
throw new IllegalArgumentException("Unexpected resource syntax: " + s);
105+
}
106+
107+
static Builder builder() {
108+
return new AutoValue_CloudFunctionsContext_Resource.Builder();
109+
}
110+
111+
@AutoValue.Builder
112+
abstract static class Builder {
113+
abstract Builder setService(String x);
114+
abstract Builder setName(String x);
115+
abstract Builder setType(String x);
116+
abstract Resource build();
117+
}
118+
}
71119
}

invoker/core/src/main/java/com/google/cloud/functions/invoker/Event.java

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
package com.google.cloud.functions.invoker;
1616

17+
import com.google.auto.value.AutoValue;
1718
import com.google.gson.JsonDeserializationContext;
1819
import com.google.gson.JsonDeserializer;
1920
import com.google.gson.JsonElement;
@@ -25,23 +26,15 @@
2526
* Represents an event that should be handled by a background function. This is an internal format
2627
* which is later converted to actual background function parameter types.
2728
*/
28-
class Event {
29-
30-
private JsonElement data;
31-
private CloudFunctionsContext context;
32-
33-
Event(JsonElement data, CloudFunctionsContext context) {
34-
this.data = data;
35-
this.context = context;
29+
@AutoValue
30+
abstract class Event {
31+
static Event of(JsonElement data, CloudFunctionsContext context) {
32+
return new AutoValue_Event(data, context);
3633
}
3734

38-
JsonElement getData() {
39-
return data;
40-
}
35+
abstract JsonElement getData();
4136

42-
CloudFunctionsContext getContext() {
43-
return context;
44-
}
37+
abstract CloudFunctionsContext getContext();
4538

4639
/** Custom deserializer that supports both GCF beta and GCF GA event formats. */
4740
static class EventDeserializer implements JsonDeserializer<Event> {
@@ -67,7 +60,7 @@ public Event deserialize(
6760
jsonDeserializationContext.deserialize(
6861
adjustContextResource(rootCopy), CloudFunctionsContext.class);
6962
}
70-
return new Event(data, context);
63+
return Event.of(data, context);
7164
}
7265

7366
/**

0 commit comments

Comments
 (0)