Merge pull request #25 from matzew/json_api

Refactoring, and adding better JSON API access
This commit is contained in:
Matthias Wessendorf 2018-12-12 13:53:17 +01:00 committed by GitHub
commit 419769e7a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 321 additions and 105 deletions

View File

@ -15,6 +15,9 @@
*/
package io.cloudevents;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import io.cloudevents.impl.DefaultCloudEventImpl;
import java.net.URI;
import java.time.ZonedDateTime;
import java.util.List;
@ -24,6 +27,7 @@ import java.util.Optional;
* An abstract event envelope, representing the 0.2 version of the <a href="https://github.com/cloudevents/spec/blob/master/spec.md">CNCF CloudEvent spec</a>.
*
*/
@JsonDeserialize(as = DefaultCloudEventImpl.class)
public interface CloudEvent<T> {
/**

View File

@ -20,6 +20,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import io.cloudevents.CloudEvent;
import io.cloudevents.Extension;
import io.cloudevents.json.ZonedDateTimeDeserializer;
import java.io.Serializable;
import java.net.URI;

View File

@ -0,0 +1,113 @@
/**
* Copyright 2018 The CloudEvents Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudevents.json;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import io.cloudevents.CloudEvent;
import io.cloudevents.impl.DefaultCloudEventImpl;
import java.io.InputStream;
import java.time.ZonedDateTime;
public final class Json {
public static final ObjectMapper MAPPER = new ObjectMapper();
static {
// add Jackson datatype for ZonedDateTime
MAPPER.registerModule(new Jdk8Module());
final SimpleModule module = new SimpleModule();
module.addSerializer(ZonedDateTime.class, new ZonedDateTimeSerializer());
module.addDeserializer(ZonedDateTime.class, new ZonedDateTimeDeserializer());
MAPPER.registerModule(module);
}
/**
* Encode a POJO to JSON using the underlying Jackson mapper.
*
* @param obj a POJO
* @return a String containing the JSON representation of the given POJO.
* @throws IllegalStateException if a property cannot be encoded.
*/
public static String encode(final Object obj) throws IllegalStateException {
try {
return MAPPER.writeValueAsString(obj);
} catch (Exception e) {
throw new IllegalStateException("Failed to encode as JSON: " + e.getMessage());
}
}
public static CloudEvent fromInputStream(final InputStream inputStream) {
try {
return MAPPER.readValue(inputStream, DefaultCloudEventImpl.class);
} catch (Exception e) {
throw new IllegalStateException("Failed to encode as JSON: " + e.getMessage());
}
}
/**
* Decode a given JSON string to a CloudEvent .
*
* @param str the JSON string.
* @return an instance of CloudEvent
* @throws IllegalStateException when there is a parsing or invalid mapping.
*/
public static DefaultCloudEventImpl decodeCloudEvent(final String str) throws IllegalStateException {
return decodeValue(str, DefaultCloudEventImpl.class);
}
/**
* Decode a given JSON string to a POJO of the given class type.
*
* @param str the JSON string.
* @param clazz the class to map to.
* @param <T> the generic type.
* @return an instance of T
* @throws IllegalStateException when there is a parsing or invalid mapping.
*/
protected static <T> T decodeValue(final String str, final Class<T> clazz) throws IllegalStateException {
try {
return MAPPER.readValue(str, clazz);
} catch (Exception e) {
throw new IllegalStateException("Failed to decode: " + e.getMessage());
}
}
/**
* Decode a given JSON string to a POJO of the given type.
*
* @param str the JSON string.
* @param type the type to map to.
* @param <T> the generic type.
* @return an instance of T
* @throws IllegalStateException when there is a parsing or invalid mapping.
*/
public static <T> T decodeValue(final String str, final TypeReference<T> type) throws IllegalStateException {
try {
return MAPPER.readValue(str, type);
} catch (Exception e) {
throw new IllegalStateException("Failed to decode: " + e.getMessage(), e);
}
}
private Json() {
// no-op
}
}

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudevents.impl;
package io.cloudevents.json;
import java.io.IOException;
import java.time.DateTimeException;

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudevents.impl;
package io.cloudevents.json;
import java.io.IOException;
import java.time.ZonedDateTime;

View File

@ -15,7 +15,7 @@
*/
package io.cloudevents;
import io.cloudevents.util.JacksonMapper;
import io.cloudevents.json.Json;
import org.junit.Test;
import java.net.URI;
@ -29,14 +29,14 @@ public class CloudEventJacksonTest {
@Test
public void testParseAzure01JSON() {
CloudEvent<Map<String, ?>> ce = JacksonMapper.fromInputStream(Thread.currentThread().getContextClassLoader().getResourceAsStream("01_azure.json"));
CloudEvent<Map<String, ?>> ce = Json.fromInputStream(Thread.currentThread().getContextClassLoader().getResourceAsStream("01_azure.json"));
assertThat(ce.getSpecVersion()).isEqualTo(SpecVersion.V_01.toString());
assertAzureCloudEvent(ce);
}
@Test
public void testParseAzure02JSON() {
CloudEvent<Map<String, ?>> ce = JacksonMapper.fromInputStream(Thread.currentThread().getContextClassLoader().getResourceAsStream("02_azure.json"));
CloudEvent<Map<String, ?>> ce = Json.fromInputStream(Thread.currentThread().getContextClassLoader().getResourceAsStream("02_azure.json"));
assertThat(ce.getSpecVersion()).isEqualTo(SpecVersion.V_02.toString());
assertAzureCloudEvent(ce);
}
@ -56,13 +56,13 @@ public class CloudEventJacksonTest {
@Test
public void testParseAmazon01JSON() {
CloudEvent ce = JacksonMapper.fromInputStream(Thread.currentThread().getContextClassLoader().getResourceAsStream("01_aws.json"));
CloudEvent ce = Json.fromInputStream(Thread.currentThread().getContextClassLoader().getResourceAsStream("01_aws.json"));
assertAmazonCloudEvent(ce);
}
@Test
public void testParseAmazon02JSON() {
CloudEvent ce = JacksonMapper.fromInputStream(Thread.currentThread().getContextClassLoader().getResourceAsStream("02_aws.json"));
CloudEvent ce = Json.fromInputStream(Thread.currentThread().getContextClassLoader().getResourceAsStream("02_aws.json"));
assertAmazonCloudEvent(ce);
}

View File

@ -0,0 +1,56 @@
/**
* Copyright 2018 The CloudEvents Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudevents.json;
import com.fasterxml.jackson.core.type.TypeReference;
import io.cloudevents.CloudEvent;
import io.cloudevents.CloudEventBuilder;
import io.cloudevents.json.types.GlusterVolumeClaim;
import org.junit.Test;
import java.io.IOException;
import java.net.URI;
import java.util.Map;
import java.util.UUID;
import static io.cloudevents.json.Json.MAPPER;
import static org.assertj.core.api.Assertions.assertThat;
public class CustomEventTypesTest {
@Test
public void testBinding() throws IOException {
// given
final Map<String, Object> storagePayload = (MAPPER.readValue(Thread.currentThread().getContextClassLoader().getResourceAsStream("pvc.json"), Map.class));
final CloudEvent<Map<String, Object>> storageCloudEventWrapper = new CloudEventBuilder<Map<String, Object>>()
.type("ProvisioningSucceeded")
.source(URI.create("/scheduler"))
.id(UUID.randomUUID().toString())
.data(storagePayload)
.build();
// when
final String httpSerializedPayload = MAPPER.writeValueAsString(storageCloudEventWrapper);
assertThat(httpSerializedPayload).contains("PersistentVolumeClaim");
//PARSE into real object, on the other side
final CloudEvent<GlusterVolumeClaim> event = Json.decodeValue(httpSerializedPayload, new TypeReference<CloudEvent<GlusterVolumeClaim>>() {});
// then
assertThat(event.getData().get()).isNotNull();
assertThat(event.getData().get().getSpec().getCapacity().get("storage")).isEqualTo("2Gi");
assertThat(event.getData().get().getSpec().getAccessModes()).containsExactly("ReadWriteMany");
}
}

View File

@ -0,0 +1,121 @@
/**
* Copyright 2018 The CloudEvents Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudevents.json.types;
import java.util.List;
import java.util.Map;
public class GlusterVolumeClaim {
private String apiVersion = null;
private String kind = null;
private Map<String, String> metadata = null;
private PVCSpec spec = null;
public String getApiVersion() {
return apiVersion;
}
public void setApiVersion(String apiVersion) {
this.apiVersion = apiVersion;
}
public String getKind() {
return kind;
}
public void setKind(String kind) {
this.kind = kind;
}
public Map<String, String> getMetadata() {
return metadata;
}
public void setMetadata(Map<String, String> metadata) {
this.metadata = metadata;
}
public PVCSpec getSpec() {
return spec;
}
public void setSpec(PVCSpec spec) {
this.spec = spec;
}
public static class PVCSpec {
public PVCSpec() {
}
private Map<String, String> capacity;
private List<String> accessModes;
private GlusterFS glusterfs;
public Map<String, String> getCapacity() {
return capacity;
}
public void setCapacity(Map<String, String> capacity) {
this.capacity = capacity;
}
public List<String> getAccessModes() {
return accessModes;
}
public void setAccessModes(List<String> accessModes) {
this.accessModes = accessModes;
}
public GlusterFS getGlusterfs() {
return glusterfs;
}
public void setGlusterfs(GlusterFS glusterfs) {
this.glusterfs = glusterfs;
}
};
public static class GlusterFS {
public GlusterFS() {
}
private String endpoints;
private String path;
public String getEndpoints() {
return endpoints;
}
public void setEndpoints(String endpoint) {
this.endpoints = endpoint;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
}
}

View File

@ -1,79 +0,0 @@
/**
* Copyright 2018 The CloudEvents Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudevents.util;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import io.cloudevents.CloudEvent;
import io.cloudevents.impl.DefaultCloudEventImpl;
import java.io.IOException;
import java.io.InputStream;
import java.time.DateTimeException;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.logging.Logger;
public final class JacksonMapper {
private static final ObjectMapper MAPPER = new ObjectMapper();
private static final Logger LOGGER = Logger.getLogger(JacksonMapper.class.getName());
static {
MAPPER.registerModule(new Jdk8Module());
SimpleModule module = new SimpleModule();
// custom types
module.addDeserializer(ZonedDateTime.class, new TestZonedDateTimeDeserializer());
MAPPER.registerModule(module);
}
public static CloudEvent fromInputStream(final InputStream inputStream) {
try {
return MAPPER.readValue(inputStream, DefaultCloudEventImpl.class);
} catch (IOException e) {
LOGGER.severe(e.getMessage());
throw new IllegalStateException("input was not parseable", e);
}
}
private static class TestZonedDateTimeDeserializer extends StdDeserializer<ZonedDateTime> {
public TestZonedDateTimeDeserializer() {
this(null);
}
public TestZonedDateTimeDeserializer(Class<?> vc) {
super(vc);
}
@Override
public ZonedDateTime deserialize(JsonParser jsonparser, DeserializationContext ctxt) throws IOException {
// not serializing timezone data yet
try {
return ZonedDateTime.parse(jsonparser.getText(), DateTimeFormatter.ISO_OFFSET_DATE_TIME);
} catch (DateTimeException e) {
throw new IllegalArgumentException("could not parse");
}
}
}
}

View File

@ -0,0 +1,17 @@
{
"apiVersion": "v1",
"kind": "PersistentVolumeClaim",
"metadata": {
"name": "gluster-default-volume"
},
"spec": {
"capacity": {
"storage": "2Gi"
},
"accessModes": [ "ReadWriteMany" ],
"glusterfs": {
"endpoints": "glusterfs-cluster",
"path": "myVol1"
}
}
}

View File

@ -15,7 +15,6 @@
*/
package io.cloudevents;
import io.cloudevents.impl.DefaultCloudEventImpl;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
@ -25,7 +24,6 @@ public abstract class AbstractTestBase {
public static JavaArchive createFrameworkDeployment() {
return ShrinkWrap.create(JavaArchive.class)
.addPackage(DefaultCloudEventImpl.class.getPackage())
.addPackage(MockProvider.class.getPackage())
.addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml");
}

View File

@ -15,8 +15,6 @@
*/
package io.cloudevents.http.vertx.impl;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import io.cloudevents.CloudEvent;
import io.cloudevents.CloudEventBuilder;
import io.cloudevents.Extension;
@ -24,9 +22,7 @@ import io.cloudevents.SpecVersion;
import io.cloudevents.http.HttpTransportAttributes;
import io.cloudevents.http.V02HttpTransportMappers;
import io.cloudevents.http.vertx.VertxCloudEvents;
import io.cloudevents.impl.DefaultCloudEventImpl;
import io.cloudevents.impl.ZonedDateTimeDeserializer;
import io.cloudevents.impl.ZonedDateTimeSerializer;
import io.cloudevents.json.Json;
import io.vertx.core.AsyncResult;
import io.vertx.core.Future;
import io.vertx.core.Handler;
@ -35,7 +31,6 @@ import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpClientRequest;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.json.Json;
import io.vertx.core.json.JsonObject;
import java.lang.reflect.Field;
@ -49,16 +44,6 @@ public final class VertxCloudEventsImpl implements VertxCloudEvents {
private final static CharSequence BINARY_TYPE = HttpHeaders.createOptimized("application/json");
private final static CharSequence STRUCTURED_TYPE = HttpHeaders.createOptimized("application/cloudevents+json");
static {
// add Jackson datatype for ZonedDateTime
Json.mapper.registerModule(new Jdk8Module());
final SimpleModule module = new SimpleModule();
module.addSerializer(ZonedDateTime.class, new ZonedDateTimeSerializer());
module.addDeserializer(ZonedDateTime.class, new ZonedDateTimeDeserializer());
Json.mapper.registerModule(module);
}
private static String readRequiredHeaderValue(final MultiMap headers, final String headerName) {
return requireNonNull(headers.get(headerName));
}
@ -155,7 +140,7 @@ public final class VertxCloudEventsImpl implements VertxCloudEvents {
request.bodyHandler((Buffer buff) -> {
if (buff.length()>0) {
resultHandler.handle(Future.succeededFuture(Json.decodeValue(buff.toString(), DefaultCloudEventImpl.class)));
resultHandler.handle(Future.succeededFuture(Json.decodeCloudEvent(buff.toString())));
} else {
throw new IllegalArgumentException("no cloudevent body");
}