diff --git a/examples/src/main/java/io/dapr/examples/actors/http/DemoActorImpl.java b/examples/src/main/java/io/dapr/examples/actors/http/DemoActorImpl.java index 50cea4574a..d99f5f5890 100644 --- a/examples/src/main/java/io/dapr/examples/actors/http/DemoActorImpl.java +++ b/examples/src/main/java/io/dapr/examples/actors/http/DemoActorImpl.java @@ -9,6 +9,7 @@ import io.dapr.actors.runtime.AbstractActor; import io.dapr.actors.runtime.ActorRuntimeContext; import io.dapr.actors.runtime.Remindable; +import io.dapr.utils.TypeRef; import reactor.core.publisher.Mono; import java.text.DateFormat; @@ -121,8 +122,8 @@ public void clock(String message) { * @return Class for reminder's state. */ @Override - public Class getStateType() { - return Integer.class; + public TypeRef getStateType() { + return TypeRef.INT; } /** diff --git a/sdk-actors/src/main/java/io/dapr/actors/client/ActorProxy.java b/sdk-actors/src/main/java/io/dapr/actors/client/ActorProxy.java index b29450d60e..c76b08bfc0 100644 --- a/sdk-actors/src/main/java/io/dapr/actors/client/ActorProxy.java +++ b/sdk-actors/src/main/java/io/dapr/actors/client/ActorProxy.java @@ -6,6 +6,7 @@ package io.dapr.actors.client; import io.dapr.actors.ActorId; +import io.dapr.utils.TypeRef; import reactor.core.publisher.Mono; /** @@ -27,6 +28,16 @@ public interface ActorProxy { */ String getActorType(); + /** + * Invokes an Actor method on Dapr. + * + * @param methodName Method name to invoke. + * @param type The type of the return class. + * @param The type to be returned. + * @return Asynchronous result with the Actor's response. + */ + Mono invokeActorMethod(String methodName, TypeRef type); + /** * Invokes an Actor method on Dapr. * @@ -37,6 +48,17 @@ public interface ActorProxy { */ Mono invokeActorMethod(String methodName, Class clazz); + /** + * Invokes an Actor method on Dapr. + * + * @param methodName Method name to invoke. + * @param data Object with the data. + * @param type The type of the return class. + * @param The type to be returned. + * @return Asynchronous result with the Actor's response. + */ + Mono invokeActorMethod(String methodName, Object data, TypeRef type); + /** * Invokes an Actor method on Dapr. * diff --git a/sdk-actors/src/main/java/io/dapr/actors/client/ActorProxyImpl.java b/sdk-actors/src/main/java/io/dapr/actors/client/ActorProxyImpl.java index 81d1b4071b..cd2e21e5c9 100644 --- a/sdk-actors/src/main/java/io/dapr/actors/client/ActorProxyImpl.java +++ b/sdk-actors/src/main/java/io/dapr/actors/client/ActorProxyImpl.java @@ -8,6 +8,7 @@ import io.dapr.actors.ActorId; import io.dapr.actors.ActorMethod; import io.dapr.serializer.DaprObjectSerializer; +import io.dapr.utils.TypeRef; import reactor.core.publisher.Mono; import java.io.IOException; @@ -72,20 +73,36 @@ public String getActorType() { * {@inheritDoc} */ @Override - public Mono invokeActorMethod(String methodName, Object data, Class clazz) { + public Mono invokeActorMethod(String methodName, Object data, TypeRef type) { return this.daprClient.invokeActorMethod(actorType, actorId.toString(), methodName, this.serialize(data)) .filter(s -> s.length > 0) - .map(s -> deserialize(s, clazz)); + .map(s -> deserialize(s, type)); } /** * {@inheritDoc} */ @Override - public Mono invokeActorMethod(String methodName, Class clazz) { + public Mono invokeActorMethod(String methodName, Object data, Class clazz) { + return this.invokeActorMethod(methodName, data, TypeRef.get(clazz)); + } + + /** + * {@inheritDoc} + */ + @Override + public Mono invokeActorMethod(String methodName, TypeRef type) { return this.daprClient.invokeActorMethod(actorType, actorId.toString(), methodName, null) .filter(s -> s.length > 0) - .map(s -> deserialize(s, clazz)); + .map(s -> deserialize(s, type)); + } + + /** + * {@inheritDoc} + */ + @Override + public Mono invokeActorMethod(String methodName, Class clazz) { + return this.invokeActorMethod(methodName, TypeRef.get(clazz)); } /** @@ -147,14 +164,14 @@ public Object invoke(Object proxy, Method method, Object[] args) { * Extracts the response object from the Actor's method result. * * @param response response returned by API. - * @param clazz Expected response class. + * @param type Expected response type. * @param Expected response type. * @return Response object or null. * @throws RuntimeException In case it cannot generate Object. */ - private T deserialize(final byte[] response, Class clazz) { + private T deserialize(final byte[] response, TypeRef type) { try { - return this.serializer.deserialize(response, clazz); + return this.serializer.deserialize(response, type); } catch (IOException e) { throw new RuntimeException(e); } diff --git a/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorManager.java b/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorManager.java index e0a2ec675b..eab5d83092 100644 --- a/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorManager.java +++ b/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorManager.java @@ -6,6 +6,7 @@ package io.dapr.actors.runtime; import io.dapr.actors.ActorId; +import io.dapr.utils.TypeRef; import reactor.core.publisher.Mono; import java.io.IOException; @@ -253,7 +254,7 @@ private Mono invokeMethod(ActorId actorId, ActorMethodContext context, S if (method.getParameterCount() == 1) { // Actor methods must have a one or no parameter, which is guaranteed at this point. Class inputClass = method.getParameterTypes()[0]; - input = this.runtimeContext.getObjectSerializer().deserialize(request, inputClass); + input = this.runtimeContext.getObjectSerializer().deserialize(request, TypeRef.get(inputClass)); } if (method.getReturnType().equals(Mono.class)) { diff --git a/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorStateManager.java b/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorStateManager.java index d6528cf6f4..04be1e0970 100644 --- a/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorStateManager.java +++ b/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorStateManager.java @@ -6,6 +6,7 @@ package io.dapr.actors.runtime; import io.dapr.actors.ActorId; +import io.dapr.utils.TypeRef; import reactor.core.publisher.Mono; import java.util.ArrayList; @@ -101,6 +102,18 @@ public Mono add(String stateName, T value) { * @return Asynchronous response with fetched object. */ public Mono get(String stateName, Class clazz) { + return this.get(stateName, TypeRef.get(clazz)); + } + + /** + * Fetches the most recent value for the given state, including cached value. + * + * @param stateName Name of the state. + * @param type Class type for the value being fetched. + * @param Type being fetched. + * @return Asynchronous response with fetched object. + */ + public Mono get(String stateName, TypeRef type) { return Mono.fromSupplier(() -> { if (stateName == null) { throw new IllegalArgumentException("State's name cannot be null."); @@ -118,7 +131,7 @@ public Mono get(String stateName, Class clazz) { return (T) null; }).switchIfEmpty( - this.stateProvider.load(this.actorTypeName, this.actorId, stateName, clazz) + this.stateProvider.load(this.actorTypeName, this.actorId, stateName, type) .switchIfEmpty(Mono.error(new NoSuchElementException("State not found: " + stateName))) .map(v -> { this.stateChangeTracker.put(stateName, new StateChangeMetadata(ActorStateChangeKind.NONE, v)); diff --git a/sdk-actors/src/main/java/io/dapr/actors/runtime/DaprStateAsyncProvider.java b/sdk-actors/src/main/java/io/dapr/actors/runtime/DaprStateAsyncProvider.java index 86f3c1e5e5..9614d56f64 100644 --- a/sdk-actors/src/main/java/io/dapr/actors/runtime/DaprStateAsyncProvider.java +++ b/sdk-actors/src/main/java/io/dapr/actors/runtime/DaprStateAsyncProvider.java @@ -11,6 +11,7 @@ import io.dapr.serializer.DaprObjectSerializer; import io.dapr.serializer.DefaultObjectSerializer; import io.dapr.utils.Properties; +import io.dapr.utils.TypeRef; import reactor.core.publisher.Mono; import java.io.ByteArrayOutputStream; @@ -59,12 +60,12 @@ class DaprStateAsyncProvider { this.isStateSerializerDefault = stateSerializer.getClass() == DefaultObjectSerializer.class; } - Mono load(String actorType, ActorId actorId, String stateName, Class clazz) { + Mono load(String actorType, ActorId actorId, String stateName, TypeRef type) { Mono result = this.daprClient.getActorState(actorType, actorId.toString(), stateName); return result.flatMap(s -> { try { - T response = this.stateSerializer.deserialize(s, clazz); + T response = this.stateSerializer.deserialize(s, type); if (response == null) { return Mono.empty(); } diff --git a/sdk-actors/src/main/java/io/dapr/actors/runtime/Remindable.java b/sdk-actors/src/main/java/io/dapr/actors/runtime/Remindable.java index de0584e404..961efed27a 100644 --- a/sdk-actors/src/main/java/io/dapr/actors/runtime/Remindable.java +++ b/sdk-actors/src/main/java/io/dapr/actors/runtime/Remindable.java @@ -5,6 +5,7 @@ package io.dapr.actors.runtime; +import io.dapr.utils.TypeRef; import reactor.core.publisher.Mono; import java.time.Duration; @@ -15,11 +16,11 @@ public interface Remindable { /** - * Gets the class for state object. + * Gets the type for state object. * * @return Class for state object. */ - Class getStateType(); + TypeRef getStateType(); /** * The reminder call back invoked when an actor reminder is triggered. diff --git a/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorManagerTest.java b/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorManagerTest.java index 3fc37c25a9..ee5a475adc 100644 --- a/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorManagerTest.java +++ b/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorManagerTest.java @@ -11,6 +11,8 @@ import java.io.IOException; import java.time.Duration; import java.util.concurrent.atomic.AtomicInteger; + +import io.dapr.utils.TypeRef; import org.junit.Assert; import org.junit.Test; import org.junit.jupiter.api.Assertions; @@ -96,8 +98,8 @@ public MyActorImpl(ActorRuntimeContext runtimeContext, ActorId id) { } @Override - public Class getStateType() { - return String.class; + public TypeRef getStateType() { + return TypeRef.STRING; } @Override @@ -124,8 +126,8 @@ public void activateThenInvoke() throws Exception { this.manager.activateActor(actorId).block(); byte[] response = this.manager.invokeMethod(actorId, "say", message).block(); Assert.assertEquals(executeSayMethod( - this.context.getObjectSerializer().deserialize(message, String.class)), - this.context.getObjectSerializer().deserialize(response, String.class)); + this.context.getObjectSerializer().deserialize(message, TypeRef.STRING)), + this.context.getObjectSerializer().deserialize(response, TypeRef.STRING)); } @Test @@ -192,8 +194,8 @@ public void activateInvokeDeactivateThenInvoke() throws Exception { this.manager.activateActor(actorId).block(); byte[] response = this.manager.invokeMethod(actorId, "say", message).block(); Assert.assertEquals(executeSayMethod( - this.context.getObjectSerializer().deserialize(message, String.class)), - this.context.getObjectSerializer().deserialize(response, String.class)); + this.context.getObjectSerializer().deserialize(message, TypeRef.STRING)), + this.context.getObjectSerializer().deserialize(response, TypeRef.STRING)); this.manager.deactivateActor(actorId).block(); this.manager.invokeMethod(actorId, "say", message).block(); diff --git a/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorStatefulTest.java b/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorStatefulTest.java index 71b2acd84a..afc4310869 100644 --- a/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorStatefulTest.java +++ b/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorStatefulTest.java @@ -11,6 +11,7 @@ import io.dapr.actors.client.ActorProxyForTestsImpl; import io.dapr.actors.client.DaprClientStub; import io.dapr.serializer.DefaultObjectSerializer; +import io.dapr.utils.TypeRef; import org.junit.Assert; import org.junit.Test; import reactor.core.publisher.Mono; @@ -247,9 +248,9 @@ public String getIdString() { } @Override - public Class getStateType() { + public TypeRef getStateType() { // Remindable type. - return String.class; + return TypeRef.STRING; } @Override diff --git a/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorTypeInformationTest.java b/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorTypeInformationTest.java index 484bb8ef43..c6d56ef187 100644 --- a/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorTypeInformationTest.java +++ b/sdk-actors/src/test/java/io/dapr/actors/runtime/ActorTypeInformationTest.java @@ -6,6 +6,7 @@ package io.dapr.actors.runtime; import io.dapr.actors.ActorType; +import io.dapr.utils.TypeRef; import org.junit.Assert; import org.junit.Test; import reactor.core.publisher.Mono; @@ -64,7 +65,7 @@ class A extends AbstractActor implements MyActor, Remindable { } @Override - public Class getStateType() { + public TypeRef getStateType() { return null; } diff --git a/sdk-actors/src/test/java/io/dapr/actors/runtime/DaprInMemoryStateProvider.java b/sdk-actors/src/test/java/io/dapr/actors/runtime/DaprInMemoryStateProvider.java index 9fde33352c..a824215c72 100644 --- a/sdk-actors/src/test/java/io/dapr/actors/runtime/DaprInMemoryStateProvider.java +++ b/sdk-actors/src/test/java/io/dapr/actors/runtime/DaprInMemoryStateProvider.java @@ -7,6 +7,7 @@ import io.dapr.actors.ActorId; import io.dapr.serializer.DaprObjectSerializer; +import io.dapr.utils.TypeRef; import reactor.core.publisher.Mono; import java.io.IOException; @@ -28,7 +29,7 @@ public class DaprInMemoryStateProvider extends DaprStateAsyncProvider { } @Override - Mono load(String actorType, ActorId actorId, String stateName, Class clazz) { + Mono load(String actorType, ActorId actorId, String stateName, TypeRef type) { return Mono.fromSupplier(() -> { try { String stateId = this.buildId(actorType, actorId, stateName); @@ -36,7 +37,7 @@ Mono load(String actorType, ActorId actorId, String stateName, Class c throw new IllegalStateException("State not found."); } - return this.serializer.deserialize(this.stateStore.get(stateId), clazz); + return this.serializer.deserialize(this.stateStore.get(stateId), type); } catch (IOException e) { throw new RuntimeException(e); } diff --git a/sdk-actors/src/test/java/io/dapr/actors/runtime/DaprStateAsyncProviderTest.java b/sdk-actors/src/test/java/io/dapr/actors/runtime/DaprStateAsyncProviderTest.java index 60237dd91c..828868c601 100644 --- a/sdk-actors/src/test/java/io/dapr/actors/runtime/DaprStateAsyncProviderTest.java +++ b/sdk-actors/src/test/java/io/dapr/actors/runtime/DaprStateAsyncProviderTest.java @@ -10,6 +10,7 @@ import io.dapr.actors.ActorId; import io.dapr.serializer.DaprObjectSerializer; import io.dapr.serializer.DefaultObjectSerializer; +import io.dapr.utils.TypeRef; import org.junit.Assert; import org.junit.Test; import reactor.core.publisher.Mono; @@ -170,23 +171,24 @@ public void happyCaseLoad() throws Exception { DaprStateAsyncProvider provider = new DaprStateAsyncProvider(daprClient, SERIALIZER); Assert.assertEquals("Jon Doe", - provider.load("MyActor", new ActorId("123"), "name", String.class).block()); + provider.load("MyActor", new ActorId("123"), "name", TypeRef.STRING).block()); Assert.assertEquals(98021, - (int)provider.load("MyActor", new ActorId("123"), "zipcode", int.class).block()); + (int)provider.load("MyActor", new ActorId("123"), "zipcode", TypeRef.INT).block()); Assert.assertEquals(98, - (int) provider.load("MyActor", new ActorId("123"), "goals", int.class).block()); + (int) provider.load("MyActor", new ActorId("123"), "goals", TypeRef.INT).block()); Assert.assertEquals(98, - (int) provider.load("MyActor", new ActorId("123"), "goals", int.class).block()); + (int) provider.load("MyActor", new ActorId("123"), "goals", TypeRef.INT).block()); Assert.assertEquals(46.55, - (double) provider.load("MyActor", new ActorId("123"), "balance", double.class).block(), + (double) provider.load("MyActor", new ActorId("123"), "balance", TypeRef.DOUBLE).block(), EPSILON); Assert.assertEquals(true, - (boolean) provider.load("MyActor", new ActorId("123"), "active", boolean.class).block()); + (boolean) provider.load("MyActor", new ActorId("123"), "active", TypeRef.BOOLEAN).block()); Assert.assertEquals(new Customer().setId(1000).setName("Roxane"), - provider.load("MyActor", new ActorId("123"), "customer", Customer.class).block()); + provider.load("MyActor", new ActorId("123"), "customer", TypeRef.get(Customer.class)).block()); Assert.assertNotEquals(new Customer().setId(1000).setName("Roxane"), - provider.load("MyActor", new ActorId("123"), "anotherCustomer", Customer.class).block()); - Assert.assertNull(provider.load("MyActor", new ActorId("123"), "nullCustomer", Customer.class).block()); + provider.load("MyActor", new ActorId("123"), "anotherCustomer", TypeRef.get(Customer.class)).block()); + Assert.assertNull( + provider.load("MyActor", new ActorId("123"), "nullCustomer", TypeRef.get(Customer.class)).block()); } @Test diff --git a/sdk-actors/src/test/java/io/dapr/actors/runtime/JavaSerializer.java b/sdk-actors/src/test/java/io/dapr/actors/runtime/JavaSerializer.java index b62afc0d05..bdbe002b01 100644 --- a/sdk-actors/src/test/java/io/dapr/actors/runtime/JavaSerializer.java +++ b/sdk-actors/src/test/java/io/dapr/actors/runtime/JavaSerializer.java @@ -6,6 +6,7 @@ package io.dapr.actors.runtime; import io.dapr.serializer.DaprObjectSerializer; +import io.dapr.utils.TypeRef; import java.io.*; @@ -32,7 +33,7 @@ public byte[] serialize(Object o) throws IOException { * {@inheritDoc} */ @Override - public T deserialize(byte[] data, Class clazz) throws IOException { + public T deserialize(byte[] data, TypeRef type) throws IOException { try (ByteArrayInputStream bis = new ByteArrayInputStream(data)) { try (ObjectInputStream ois = new ObjectInputStream(bis)) { try { diff --git a/sdk-tests/src/test/java/io/dapr/it/actors/app/MyActorImpl.java b/sdk-tests/src/test/java/io/dapr/it/actors/app/MyActorImpl.java index 002ca0f450..511f4c776c 100644 --- a/sdk-tests/src/test/java/io/dapr/it/actors/app/MyActorImpl.java +++ b/sdk-tests/src/test/java/io/dapr/it/actors/app/MyActorImpl.java @@ -11,6 +11,7 @@ import io.dapr.actors.runtime.ActorRuntimeContext; import io.dapr.actors.runtime.Remindable; import io.dapr.it.actors.MethodEntryTracker; +import io.dapr.utils.TypeRef; import reactor.core.publisher.Mono; import java.text.DateFormat; @@ -146,8 +147,8 @@ public Mono receiveReminder(String reminderName, String state, Duration du } @Override - public Class getStateType() { - return String.class; + public TypeRef getStateType() { + return TypeRef.STRING; } diff --git a/sdk/src/main/java/io/dapr/client/DaprClient.java b/sdk/src/main/java/io/dapr/client/DaprClient.java index afe6722a9e..6bd4f962df 100644 --- a/sdk/src/main/java/io/dapr/client/DaprClient.java +++ b/sdk/src/main/java/io/dapr/client/DaprClient.java @@ -5,12 +5,10 @@ package io.dapr.client; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.protobuf.ByteString; import io.dapr.client.domain.State; import io.dapr.client.domain.StateOptions; import io.dapr.client.domain.Verb; -import io.dapr.v1.DaprProtos; +import io.dapr.utils.TypeRef; import reactor.core.publisher.Mono; import java.util.List; @@ -45,14 +43,29 @@ public interface DaprClient { /** * Invoke a service with all possible parameters, using serialization. * - * @param verb The Verb to be used for HTTP will be the HTTP Verb, for GRPC is just a metadata value. - * @param appId The Application ID where the service is. - * @param method The actual Method to be call in the application. - * @param request The request to be sent to invoke the service, use byte[] to skip serialization. + * @param verb The Verb to be used for HTTP will be the HTTP Verb, for GRPC is just a metadata value. + * @param appId The Application ID where the service is. + * @param method The actual Method to be call in the application. + * @param request The request to be sent to invoke the service, use byte[] to skip serialization. * @param metadata Metadata (in GRPC) or headers (in HTTP) to be send in request. - * @param clazz the Type needed as return for the call. - * @param the Type of the return, use byte[] to skip serialization. - * @return A Mono Plan of type clazz. + * @param type The Type needed as return for the call. + * @param The Type of the return, use byte[] to skip serialization. + * @return A Mono Plan of type type . + */ + Mono invokeService( + Verb verb, String appId, String method, Object request, Map metadata, TypeRef type); + + /** + * Invoke a service with all possible parameters, using serialization. + * + * @param verb The Verb to be used for HTTP will be the HTTP Verb, for GRPC is just a metadata value. + * @param appId The Application ID where the service is. + * @param method The actual Method to be call in the application. + * @param request The request to be sent to invoke the service, use byte[] to skip serialization. + * @param metadata Metadata (in GRPC) or headers (in HTTP) to be send in request. + * @param clazz The type needed as return for the call. + * @param The type of the return, use byte[] to skip serialization. + * @return A Mono Plan of type type . */ Mono invokeService( Verb verb, String appId, String method, Object request, Map metadata, Class clazz); @@ -64,22 +77,48 @@ Mono invokeService( * @param appId The Application ID where the service is. * @param method The actual Method to be call in the application. * @param request The request to be sent to invoke the service, use byte[] to skip serialization. - * @param clazz the Type needed as return for the call. - * @param the Type of the return, use byte[] to skip serialization. - * @return A Mono Plan of type clazz. + * @param type The type needed as return for the call. + * @param The type of the return, use byte[] to skip serialization. + * @return A Mono Plan of type type . */ - Mono invokeService(Verb verb, String appId, String method, Object request, Class clazz); + Mono invokeService(Verb verb, String appId, String method, Object request, TypeRef type); /** - * Invoke a service without input, using serialization for response. + * Invoke a service without metadata, using serialization. * * @param verb The Verb to be used for HTTP will be the HTTP Verb, for GRPC is just a metadata value. * @param appId The Application ID where the service is. * @param method The actual Method to be call in the application. + * @param request The request to be sent to invoke the service, use byte[] to skip serialization. + * @param clazz The type needed as return for the call. + * @param The type of the return, use byte[] to skip serialization. + * @return A Mono Plan of type type . + */ + Mono invokeService(Verb verb, String appId, String method, Object request, Class clazz); + + /** + * Invoke a service without input, using serialization for response. + * + * @param verb The Verb to be used for HTTP will be the HTTP Verb, for GRPC is just a metadata value. + * @param appId The Application ID where the service is. + * @param method The actual Method to be call in the application. + * @param metadata Metadata (in GRPC) or headers (in HTTP) to be send in request. + * @param type The type needed as return for the call. + * @param The type of the return, use byte[] to skip serialization. + * @return A Mono plan of type type . + */ + Mono invokeService(Verb verb, String appId, String method, Map metadata, TypeRef type); + + /** + * Invoke a service without input, using serialization for response. + * + * @param verb The Verb to be used for HTTP will be the HTTP Verb, for GRPC is just a metadata value. + * @param appId The Application ID where the service is. + * @param method The actual Method to be call in the application. * @param metadata Metadata (in GRPC) or headers (in HTTP) to be send in request. - * @param clazz the Type needed as return for the call. - * @param the Type of the return, use byte[] to skip serialization. - * @return A Mono plan of type clazz. + * @param clazz The type needed as return for the call. + * @param The type of the return, use byte[] to skip serialization. + * @return A Mono plan of type type . */ Mono invokeService(Verb verb, String appId, String method, Map metadata, Class clazz); @@ -150,6 +189,18 @@ Mono invokeService( */ Mono invokeBinding(String name, String operation, byte[] data, Map metadata); + /** + * Invokes a Binding operation. + * + * @param name The name of the biding to call. + * @param operation The operation to be performed by the binding request processor. + * @param data The data to be processed, use byte[] to skip serialization. + * @param type The type being returned. + * @param The type of the return + * @return a Mono plan of type T. + */ + Mono invokeBinding(String name, String operation, Object data, TypeRef type); + /** * Invokes a Binding operation. * @@ -162,6 +213,19 @@ Mono invokeService( */ Mono invokeBinding(String name, String operation, Object data, Class clazz); + /** + * Invokes a Binding operation. + * + * @param name The name of the biding to call. + * @param operation The operation to be performed by the binding request processor. + * @param data The data to be processed, use byte[] to skip serialization. + * @param metadata The metadata map. + * @param type The type being returned. + * @param The type of the return + * @return a Mono plan of type T. + */ + Mono invokeBinding(String name, String operation, Object data, Map metadata, TypeRef type); + /** * Invokes a Binding operation. * @@ -180,8 +244,19 @@ Mono invokeService( * * @param stateStoreName The name of the state store. * @param state State to be re-retrieved. - * @param clazz The Type of State needed as return. - * @param The Type of the return. + * @param type The type of State needed as return. + * @param The type of the return. + * @return A Mono Plan for the requested State. + */ + Mono> getState(String stateStoreName, State state, TypeRef type); + + /** + * Retrieve a State based on their key. + * + * @param stateStoreName The name of the state store. + * @param state State to be re-retrieved. + * @param clazz The type of State needed as return. + * @param The type of the return. * @return A Mono Plan for the requested State. */ Mono> getState(String stateStoreName, State state, Class clazz); @@ -191,8 +266,19 @@ Mono invokeService( * * @param stateStoreName The name of the state store. * @param key The key of the State to be retrieved. - * @param clazz The Type of State needed as return. - * @param The Type of the return. + * @param type The type of State needed as return. + * @param The type of the return. + * @return A Mono Plan for the requested State. + */ + Mono> getState(String stateStoreName, String key, TypeRef type); + + /** + * Retrieve a State based on their key. + * + * @param stateStoreName The name of the state store. + * @param key The key of the State to be retrieved. + * @param clazz The type of State needed as return. + * @param The type of the return. * @return A Mono Plan for the requested State. */ Mono> getState(String stateStoreName, String key, Class clazz); @@ -204,10 +290,23 @@ Mono invokeService( * @param key The key of the State to be retrieved. * @param etag Optional etag for conditional get * @param options Optional settings for retrieve operation. - * @param clazz The Type of State needed as return. + * @param type The Type of State needed as return. * @param The Type of the return. * @return A Mono Plan for the requested State. */ + Mono> getState(String stateStoreName, String key, String etag, StateOptions options, TypeRef type); + + /** + * Retrieve a State based on their key. + * + * @param stateStoreName The name of the state store. + * @param key The key of the State to be retrieved. + * @param etag Optional etag for conditional get + * @param options Optional settings for retrieve operation. + * @param clazz The type of State needed as return. + * @param The type of the return. + * @return A Mono Plan for the requested State. + */ Mono> getState(String stateStoreName, String key, String etag, StateOptions options, Class clazz); /** diff --git a/sdk/src/main/java/io/dapr/client/DaprClientGrpc.java b/sdk/src/main/java/io/dapr/client/DaprClientGrpc.java index 02ef6c8914..af2fc68f54 100644 --- a/sdk/src/main/java/io/dapr/client/DaprClientGrpc.java +++ b/sdk/src/main/java/io/dapr/client/DaprClientGrpc.java @@ -14,6 +14,7 @@ import io.dapr.client.domain.StateOptions; import io.dapr.client.domain.Verb; import io.dapr.serializer.DaprObjectSerializer; +import io.dapr.utils.TypeRef; import io.dapr.v1.CommonProtos; import io.dapr.v1.DaprGrpc; import io.dapr.v1.DaprProtos; @@ -139,27 +140,58 @@ public Mono invokeService( String method, Object request, Map metadata, - Class clazz) { + TypeRef type) { try { DaprProtos.InvokeServiceRequest envelope = buildInvokeServiceRequest(verb.toString(), appId, method, request); return Mono.fromCallable(() -> { ListenableFuture futureResponse = client.invokeService(envelope); - return objectSerializer.deserialize(futureResponse.get().getData().getValue().toByteArray(), clazz); + return objectSerializer.deserialize(futureResponse.get().getData().getValue().toByteArray(), type); }); } catch (Exception ex) { return Mono.error(ex); } } + /** + * {@inheritDoc} + */ + @Override + public Mono invokeService( + Verb verb, + String appId, + String method, + Object request, + Map metadata, + Class clazz) { + return this.invokeService(verb, appId, method, request, metadata, TypeRef.get(clazz)); + } + + /** + * {@inheritDoc} + */ + @Override + public Mono invokeService( + Verb verb, String appId, String method, Map metadata, TypeRef type) { + return this.invokeService(verb, appId, method, null, metadata, type); + } + /** * {@inheritDoc} */ @Override public Mono invokeService( Verb verb, String appId, String method, Map metadata, Class clazz) { - return this.invokeService(verb, appId, method, null, metadata, clazz); + return this.invokeService(verb, appId, method, null, metadata, TypeRef.get(clazz)); + } + + /** + * {@inheritDoc} + */ + @Override + public Mono invokeService(Verb verb, String appId, String method, Object request, TypeRef type) { + return this.invokeService(verb, appId, method, request, null, type); } /** @@ -167,7 +199,7 @@ public Mono invokeService( */ @Override public Mono invokeService(Verb verb, String appId, String method, Object request, Class clazz) { - return this.invokeService(verb, appId, method, request, null, clazz); + return this.invokeService(verb, appId, method, request, null, TypeRef.get(clazz)); } /** @@ -175,7 +207,7 @@ public Mono invokeService(Verb verb, String appId, String method, Object */ @Override public Mono invokeService(Verb verb, String appId, String method, Object request) { - return this.invokeService(verb, appId, method, request, null, byte[].class).then(); + return this.invokeService(verb, appId, method, request, null, TypeRef.BYTE_ARRAY).then(); } /** @@ -184,7 +216,7 @@ public Mono invokeService(Verb verb, String appId, String method, Object r @Override public Mono invokeService( Verb verb, String appId, String method, Object request, Map metadata) { - return this.invokeService(verb, appId, method, request, metadata, byte[].class).then(); + return this.invokeService(verb, appId, method, request, metadata, TypeRef.BYTE_ARRAY).then(); } /** @@ -193,7 +225,7 @@ public Mono invokeService( @Override public Mono invokeService( Verb verb, String appId, String method, Map metadata) { - return this.invokeService(verb, appId, method, null, metadata, byte[].class).then(); + return this.invokeService(verb, appId, method, null, metadata, TypeRef.BYTE_ARRAY).then(); } /** @@ -202,7 +234,7 @@ public Mono invokeService( @Override public Mono invokeService( Verb verb, String appId, String method, byte[] request, Map metadata) { - return this.invokeService(verb, appId, method, request, metadata, byte[].class); + return this.invokeService(verb, appId, method, request, metadata, TypeRef.BYTE_ARRAY); } /** @@ -210,7 +242,7 @@ public Mono invokeService( */ @Override public Mono invokeBinding(String name, String operation, Object data) { - return this.invokeBinding(name, operation, data, null, byte[].class).then(); + return this.invokeBinding(name, operation, data, null, TypeRef.BYTE_ARRAY).then(); } /** @@ -218,7 +250,15 @@ public Mono invokeBinding(String name, String operation, Object data) { */ @Override public Mono invokeBinding(String name, String operation, byte[] data, Map metadata) { - return this.invokeBinding(name, operation, data, metadata, byte[].class); + return this.invokeBinding(name, operation, data, metadata, TypeRef.BYTE_ARRAY); + } + + /** + * {@inheritDoc} + */ + @Override + public Mono invokeBinding(String name, String operation, Object data, TypeRef type) { + return this.invokeBinding(name, operation, data, null, type); } /** @@ -226,7 +266,7 @@ public Mono invokeBinding(String name, String operation, byte[] data, Ma */ @Override public Mono invokeBinding(String name, String operation, Object data, Class clazz) { - return this.invokeBinding(name, operation, data, null, clazz); + return this.invokeBinding(name, operation, data, null, TypeRef.get(clazz)); } /** @@ -234,7 +274,7 @@ public Mono invokeBinding(String name, String operation, Object data, Cla */ @Override public Mono invokeBinding( - String name, String operation, Object data, Map metadata, Class clazz) { + String name, String operation, Object data, Map metadata, TypeRef type) { try { if (name == null || name.trim().isEmpty()) { throw new IllegalArgumentException("Binding name cannot be null or empty."); @@ -256,19 +296,44 @@ public Mono invokeBinding( DaprProtos.InvokeBindingRequest envelope = builder.build(); return Mono.fromCallable(() -> { ListenableFuture futureResponse = client.invokeBinding(envelope); - return objectSerializer.deserialize(futureResponse.get().getData().toByteArray(), clazz); + return objectSerializer.deserialize(futureResponse.get().getData().toByteArray(), type); }); } catch (Exception ex) { return Mono.error(ex); } } + /** + * {@inheritDoc} + */ + @Override + public Mono invokeBinding( + String name, String operation, Object data, Map metadata, Class clazz) { + return this.invokeBinding(name, operation, data, metadata, TypeRef.get(clazz)); + } + + /** + * {@inheritDoc} + */ + @Override + public Mono> getState(String stateStoreName, State state, TypeRef type) { + return this.getState(stateStoreName, state.getKey(), state.getEtag(), state.getOptions(), type); + } + /** * {@inheritDoc} */ @Override public Mono> getState(String stateStoreName, State state, Class clazz) { - return this.getState(stateStoreName, state.getKey(), state.getEtag(), state.getOptions(), clazz); + return this.getState(stateStoreName, state.getKey(), state.getEtag(), state.getOptions(), TypeRef.get(clazz)); + } + + /** + * {@inheritDoc} + */ + @Override + public Mono> getState(String stateStoreName, String key, TypeRef type) { + return this.getState(stateStoreName, key, null, null, type); } /** @@ -276,7 +341,7 @@ public Mono> getState(String stateStoreName, State state, Class< */ @Override public Mono> getState(String stateStoreName, String key, Class clazz) { - return this.getState(stateStoreName, key, null, null, clazz); + return this.getState(stateStoreName, key, null, null, TypeRef.get(clazz)); } /** @@ -284,7 +349,7 @@ public Mono> getState(String stateStoreName, String key, Class c */ @Override public Mono> getState( - String stateStoreName, String key, String etag, StateOptions options, Class clazz) { + String stateStoreName, String key, String etag, StateOptions options, TypeRef type) { try { if ((stateStoreName == null) || (stateStoreName.trim().isEmpty())) { throw new IllegalArgumentException("State store name cannot be null or empty."); @@ -308,21 +373,30 @@ public Mono> getState( } catch (NullPointerException npe) { return null; } - return buildStateKeyValue(response, key, options, clazz); + return buildStateKeyValue(response, key, options, type); }); } catch (Exception ex) { return Mono.error(ex); } } + /** + * {@inheritDoc} + */ + @Override + public Mono> getState( + String stateStoreName, String key, String etag, StateOptions options, Class clazz) { + return this.getState(stateStoreName, key, etag, options, TypeRef.get(clazz)); + } + private State buildStateKeyValue( DaprProtos.GetStateResponse response, String requestedKey, StateOptions stateOptions, - Class clazz) throws IOException { + TypeRef type) throws IOException { ByteString payload = response.getData(); byte[] data = payload == null ? null : payload.toByteArray(); - T value = stateSerializer.deserialize(data, clazz); + T value = stateSerializer.deserialize(data, type); String etag = response.getEtag(); String key = requestedKey; return new State<>(value, key, etag, stateOptions); diff --git a/sdk/src/main/java/io/dapr/client/DaprClientHttp.java b/sdk/src/main/java/io/dapr/client/DaprClientHttp.java index 7d9edf92ab..31d8e4b066 100644 --- a/sdk/src/main/java/io/dapr/client/DaprClientHttp.java +++ b/sdk/src/main/java/io/dapr/client/DaprClientHttp.java @@ -11,6 +11,7 @@ import io.dapr.serializer.DaprObjectSerializer; import io.dapr.serializer.DefaultObjectSerializer; import io.dapr.utils.Constants; +import io.dapr.utils.TypeRef; import reactor.core.publisher.Mono; import java.io.IOException; @@ -123,7 +124,7 @@ public Mono publishEvent(String topic, Object data, Map me */ @Override public Mono invokeService( - Verb verb, String appId, String method, Object request, Map metadata, Class clazz) { + Verb verb, String appId, String method, Object request, Map metadata, TypeRef type) { try { if (verb == null) { throw new IllegalArgumentException("Verb cannot be null."); @@ -140,7 +141,7 @@ public Mono invokeService( Mono response = this.client.invokeApi(httMethod, path, metadata, serializedRequestBody, null); return response.flatMap(r -> { try { - T object = objectSerializer.deserialize(r.getBody(), clazz); + T object = objectSerializer.deserialize(r.getBody(), type); if (object == null) { return Mono.empty(); } @@ -155,13 +156,44 @@ public Mono invokeService( } } + /** + * {@inheritDoc} + */ + @Override + public Mono invokeService( + Verb verb, + String appId, + String method, + Object request, + Map metadata, + Class clazz) { + return this.invokeService(verb, appId, method, request, metadata, TypeRef.get(clazz)); + } + + /** + * {@inheritDoc} + */ + @Override + public Mono invokeService( + Verb verb, String appId, String method, Map metadata, TypeRef type) { + return this.invokeService(verb, appId, method, null, metadata, type); + } + /** * {@inheritDoc} */ @Override public Mono invokeService( Verb verb, String appId, String method, Map metadata, Class clazz) { - return this.invokeService(verb, appId, method, null, metadata, clazz); + return this.invokeService(verb, appId, method, null, metadata, TypeRef.get(clazz)); + } + + /** + * {@inheritDoc} + */ + @Override + public Mono invokeService(Verb verb, String appId, String method, Object request, TypeRef type) { + return this.invokeService(verb, appId, method, request, null, type); } /** @@ -169,7 +201,7 @@ public Mono invokeService( */ @Override public Mono invokeService(Verb verb, String appId, String method, Object request, Class clazz) { - return this.invokeService(verb, appId, method, request, null, clazz); + return this.invokeService(verb, appId, method, request, null, TypeRef.get(clazz)); } /** @@ -177,7 +209,7 @@ public Mono invokeService(Verb verb, String appId, String method, Object */ @Override public Mono invokeService(Verb verb, String appId, String method, Object request) { - return this.invokeService(verb, appId, method, request, null, byte[].class).then(); + return this.invokeService(verb, appId, method, request, null, TypeRef.BYTE_ARRAY).then(); } /** @@ -186,7 +218,7 @@ public Mono invokeService(Verb verb, String appId, String method, Object r @Override public Mono invokeService( Verb verb, String appId, String method, Object request, Map metadata) { - return this.invokeService(verb, appId, method, request, metadata, byte[].class).then(); + return this.invokeService(verb, appId, method, request, metadata, TypeRef.BYTE_ARRAY).then(); } /** @@ -195,7 +227,7 @@ public Mono invokeService( @Override public Mono invokeService( Verb verb, String appId, String method, Map metadata) { - return this.invokeService(verb, appId, method, null, metadata, byte[].class).then(); + return this.invokeService(verb, appId, method, null, metadata, TypeRef.BYTE_ARRAY).then(); } /** @@ -204,7 +236,7 @@ public Mono invokeService( @Override public Mono invokeService( Verb verb, String appId, String method, byte[] request, Map metadata) { - return this.invokeService(verb, appId, method, request, metadata, byte[].class); + return this.invokeService(verb, appId, method, request, metadata, TypeRef.BYTE_ARRAY); } /** @@ -212,7 +244,7 @@ public Mono invokeService( */ @Override public Mono invokeBinding(String name, String operation, Object data) { - return this.invokeBinding(name, operation, data, null, byte[].class).then(); + return this.invokeBinding(name, operation, data, null, TypeRef.BYTE_ARRAY).then(); } /** @@ -220,7 +252,15 @@ public Mono invokeBinding(String name, String operation, Object data) { */ @Override public Mono invokeBinding(String name, String operation, byte[] data, Map metadata) { - return this.invokeBinding(name, operation, data, metadata, byte[].class); + return this.invokeBinding(name, operation, data, metadata, TypeRef.BYTE_ARRAY); + } + + /** + * {@inheritDoc} + */ + @Override + public Mono invokeBinding(String name, String operation, Object data, TypeRef type) { + return this.invokeBinding(name, operation, data, null, type); } /** @@ -228,7 +268,7 @@ public Mono invokeBinding(String name, String operation, byte[] data, Ma */ @Override public Mono invokeBinding(String name, String operation, Object data, Class clazz) { - return this.invokeBinding(name, operation, data, null, clazz); + return this.invokeBinding(name, operation, data, null, TypeRef.get(clazz)); } /** @@ -236,7 +276,7 @@ public Mono invokeBinding(String name, String operation, Object data, Cla */ @Override public Mono invokeBinding( - String name, String operation, Object data, Map metadata, Class clazz) { + String name, String operation, Object data, Map metadata, TypeRef type) { try { if (name == null || name.trim().isEmpty()) { throw new IllegalArgumentException("Binding name cannot be null or empty."); @@ -279,7 +319,7 @@ public Mono invokeBinding( Mono response = this.client.invokeApi(httpMethod, url.toString(), null, payload, null); return response.flatMap(r -> { try { - T object = objectSerializer.deserialize(r.getBody(), clazz); + T object = objectSerializer.deserialize(r.getBody(), type); if (object == null) { return Mono.empty(); } @@ -294,12 +334,38 @@ public Mono invokeBinding( } } + + /** + * {@inheritDoc} + */ + @Override + public Mono invokeBinding( + String name, String operation, Object data, Map metadata, Class clazz) { + return this.invokeBinding(name, operation, data, metadata, TypeRef.get(clazz)); + } + + /** + * {@inheritDoc} + */ + @Override + public Mono> getState(String stateStoreName, State state, TypeRef type) { + return this.getState(stateStoreName, state.getKey(), state.getEtag(), state.getOptions(), type); + } + /** * {@inheritDoc} */ @Override public Mono> getState(String stateStoreName, State state, Class clazz) { - return this.getState(stateStoreName, state.getKey(), state.getEtag(), state.getOptions(), clazz); + return this.getState(stateStoreName, state.getKey(), state.getEtag(), state.getOptions(), TypeRef.get(clazz)); + } + + /** + * {@inheritDoc} + */ + @Override + public Mono> getState(String stateStoreName, String key, TypeRef type) { + return this.getState(stateStoreName, key, null, null, type); } /** @@ -307,7 +373,7 @@ public Mono> getState(String stateStoreName, State state, Class< */ @Override public Mono> getState(String stateStoreName, String key, Class clazz) { - return this.getState(stateStoreName, key, null, null, clazz); + return this.getState(stateStoreName, key, null, null, TypeRef.get(clazz)); } /** @@ -315,7 +381,7 @@ public Mono> getState(String stateStoreName, String key, Class c */ @Override public Mono> getState( - String stateStoreName, String key, String etag, StateOptions options, Class clazz) { + String stateStoreName, String key, String etag, StateOptions options, TypeRef type) { try { if ((stateStoreName == null) || (stateStoreName.trim().isEmpty())) { throw new IllegalArgumentException("State store name cannot be null or empty."); @@ -341,7 +407,7 @@ public Mono> getState( .invokeApi(DaprHttp.HttpMethods.GET.name(), url.toString(), urlParameters, headers) .flatMap(s -> { try { - return Mono.just(buildStateKeyValue(s, key, options, clazz)); + return Mono.just(buildStateKeyValue(s, key, options, type)); } catch (Exception ex) { return Mono.error(ex); } @@ -351,6 +417,12 @@ public Mono> getState( } } + @Override + public Mono> getState( + String stateStoreName, String key, String etag, StateOptions options, Class clazz) { + return null; + } + /** * {@inheritDoc} */ @@ -453,15 +525,15 @@ public Mono deleteState(String stateStoreName, String key, String etag, St * * @param response The response of the HTTP Call * @param requestedKey The Key Requested. - * @param clazz The Class of the Value of the state + * @param type The Class of the Value of the state * @param The Type of the Value of the state * @return A StateKeyValue instance * @throws IOException If there's a issue deserialzing the response. */ private State buildStateKeyValue( - DaprHttp.Response response, String requestedKey, StateOptions stateOptions, Class clazz) throws IOException { + DaprHttp.Response response, String requestedKey, StateOptions stateOptions, TypeRef type) throws IOException { // The state is in the body directly, so we use the state serializer here. - T value = stateSerializer.deserialize(response.getBody(), clazz); + T value = stateSerializer.deserialize(response.getBody(), type); String key = requestedKey; String etag = null; if (response.getHeaders() != null && response.getHeaders().containsKey("Etag")) { diff --git a/sdk/src/main/java/io/dapr/client/ObjectSerializer.java b/sdk/src/main/java/io/dapr/client/ObjectSerializer.java index 520dc982d5..9d5e25d88d 100644 --- a/sdk/src/main/java/io/dapr/client/ObjectSerializer.java +++ b/sdk/src/main/java/io/dapr/client/ObjectSerializer.java @@ -7,10 +7,13 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import io.dapr.client.domain.CloudEvent; +import io.dapr.utils.TypeRef; import java.io.IOException; +import java.lang.reflect.Type; /** * Serializes and deserializes an internal object. @@ -55,6 +58,19 @@ public byte[] serialize(Object state) throws IOException { return OBJECT_MAPPER.writeValueAsBytes(state); } + /** + * Deserializes the byte array into the original object. + * + * @param content Content to be parsed. + * @param type Type of the object being deserialized. + * @param Generic type of the object being deserialized. + * @return Object of type T. + * @throws IOException In case content cannot be deserialized. + */ + public T deserialize(byte[] content, TypeRef type) throws IOException { + return deserialize(content, OBJECT_MAPPER.constructType(type.getType())); + } + /** * Deserializes the byte array into the original object. * @@ -65,12 +81,16 @@ public byte[] serialize(Object state) throws IOException { * @throws IOException In case content cannot be deserialized. */ public T deserialize(byte[] content, Class clazz) throws IOException { - if ((clazz == null) || (clazz == Void.class)) { + return deserialize(content, OBJECT_MAPPER.constructType(clazz)); + } + + private T deserialize(byte[] content, JavaType javaType) throws IOException { + if ((javaType == null) || javaType.isTypeOrSubTypeOf(Void.class)) { return null; } - if (clazz.isPrimitive()) { - return deserializePrimitives(content, clazz); + if (javaType.isPrimitive()) { + return deserializePrimitives(content, javaType); } if (content == null) { @@ -78,7 +98,7 @@ public T deserialize(byte[] content, Class clazz) throws IOException { } // Deserialization of GRPC response fails without this check since it does not come as base64 encoded byte[]. - if (clazz == byte[].class) { + if (javaType.hasRawClass(byte[].class)) { return (T) content; } @@ -86,59 +106,59 @@ public T deserialize(byte[] content, Class clazz) throws IOException { return (T) null; } - if (clazz == CloudEvent.class) { + if (javaType.hasRawClass(CloudEvent.class)) { return (T) CloudEvent.deserialize(content); } - return OBJECT_MAPPER.readValue(content, clazz); + return OBJECT_MAPPER.readValue(content, javaType); } /** * Parses a given String to the corresponding object defined by class. * - * @param content Value to be parsed. - * @param clazz Class of the expected result type. - * @param Result type. + * @param content Value to be parsed. + * @param javaType Type of the expected result type. + * @param Result type. * @return Result as corresponding type. * @throws Exception if cannot deserialize primitive time. */ - private static T deserializePrimitives(byte[] content, Class clazz) throws IOException { + private static T deserializePrimitives(byte[] content, JavaType javaType) throws IOException { if ((content == null) || (content.length == 0)) { - if (boolean.class == clazz) { + if (javaType.hasRawClass(boolean.class)) { return (T) Boolean.FALSE; } - if (byte.class == clazz) { + if (javaType.hasRawClass(byte.class)) { return (T) Byte.valueOf((byte) 0); } - if (short.class == clazz) { + if (javaType.hasRawClass(short.class)) { return (T) Short.valueOf((short) 0); } - if (int.class == clazz) { + if (javaType.hasRawClass(int.class)) { return (T) Integer.valueOf(0); } - if (long.class == clazz) { + if (javaType.hasRawClass(long.class)) { return (T) Long.valueOf(0L); } - if (float.class == clazz) { + if (javaType.hasRawClass(float.class)) { return (T) Float.valueOf(0); } - if (double.class == clazz) { + if (javaType.hasRawClass(double.class)) { return (T) Double.valueOf(0); } - if (char.class == clazz) { + if (javaType.hasRawClass(char.class)) { return (T) Character.valueOf(Character.MIN_VALUE); } return null; } - return OBJECT_MAPPER.readValue(content, clazz); + return OBJECT_MAPPER.readValue(content, javaType); } } diff --git a/sdk/src/main/java/io/dapr/serializer/DaprObjectSerializer.java b/sdk/src/main/java/io/dapr/serializer/DaprObjectSerializer.java index 9c11efd17c..a411811b23 100644 --- a/sdk/src/main/java/io/dapr/serializer/DaprObjectSerializer.java +++ b/sdk/src/main/java/io/dapr/serializer/DaprObjectSerializer.java @@ -5,6 +5,8 @@ package io.dapr.serializer; +import io.dapr.utils.TypeRef; + import java.io.IOException; /** @@ -13,7 +15,7 @@ public interface DaprObjectSerializer { /** - * Serializes the given object as a String to be saved. + * Serializes the given object as byte[]. * * @param o Object to be serialized. * @return Serialized object. @@ -22,13 +24,13 @@ public interface DaprObjectSerializer { byte[] serialize(Object o) throws IOException; /** - * Deserializes the given String into a object. + * Deserializes the given byte[] into a object. * * @param data Data to be deserialized. - * @param clazz Class of object to be deserialized. + * @param type Type of object to be deserialized. * @param Type of object to be deserialized. * @return Deserialized object. * @throws IOException If cannot deserialize object. */ - T deserialize(byte[] data, Class clazz) throws IOException; + T deserialize(byte[] data, TypeRef type) throws IOException; } diff --git a/sdk/src/main/java/io/dapr/serializer/DefaultObjectSerializer.java b/sdk/src/main/java/io/dapr/serializer/DefaultObjectSerializer.java index 5dfc2eeed8..6abef116b9 100644 --- a/sdk/src/main/java/io/dapr/serializer/DefaultObjectSerializer.java +++ b/sdk/src/main/java/io/dapr/serializer/DefaultObjectSerializer.java @@ -6,8 +6,10 @@ package io.dapr.serializer; import io.dapr.client.ObjectSerializer; +import io.dapr.utils.TypeRef; import java.io.IOException; +import java.lang.reflect.Type; /** * Default serializer/deserializer for request/response objects and for state objects too. @@ -26,7 +28,7 @@ public byte[] serialize(Object o) throws IOException { * {@inheritDoc} */ @Override - public T deserialize(byte[] data, Class clazz) throws IOException { - return super.deserialize(data, clazz); + public T deserialize(byte[] data, TypeRef type) throws IOException { + return super.deserialize(data, type); } } diff --git a/sdk/src/main/java/io/dapr/utils/TypeRef.java b/sdk/src/main/java/io/dapr/utils/TypeRef.java new file mode 100644 index 0000000000..2c4e5da3dd --- /dev/null +++ b/sdk/src/main/java/io/dapr/utils/TypeRef.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.utils; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +/** + * Used to reference a type. + * + *

Usage: new TypeRef<MyClass>(){}

+ * @param Type to be deserialized. + */ +public abstract class TypeRef { + + public static final TypeRef STRING = new TypeRef() {}; + + public static final TypeRef BOOLEAN = new TypeRef(boolean.class) {}; + + public static final TypeRef INT = new TypeRef(int.class) {}; + + public static final TypeRef LONG = new TypeRef(long.class) {}; + + public static final TypeRef CHAR = new TypeRef(char.class) {}; + + public static final TypeRef BYTE = new TypeRef(byte.class) {}; + + public static final TypeRef VOID = new TypeRef(void.class) {}; + + public static final TypeRef FLOAT = new TypeRef(float.class) {}; + + public static final TypeRef DOUBLE = new TypeRef(double.class) {}; + + public static final TypeRef BYTE_ARRAY = new TypeRef() {}; + + public static final TypeRef INT_ARRAY = new TypeRef() {}; + + public static final TypeRef STRING_ARRAY = new TypeRef() {}; + + private final Type type; + + /** + * Constructor. + */ + public TypeRef() { + Type superClass = this.getClass().getGenericSuperclass(); + if (superClass instanceof Class) { + throw new IllegalArgumentException("TypeReference requires type."); + } + + this.type = ((ParameterizedType)superClass).getActualTypeArguments()[0]; + } + + /** + * Constructor for reflection. + * + * @param clazz Class type to be referenced. + */ + private TypeRef(Class clazz) { + this.type = clazz; + } + + /** + * Gets the type referenced. + * + * @return type referenced. + */ + public Type getType() { + return this.type; + } + + /** + * Creates a reference to a given class type. + * @param clazz Class type to be referenced. + * @param Type to be referenced. + * @return Class type reference. + */ + public static TypeRef get(Class clazz) { + if (clazz == String.class) { + return (TypeRef) STRING; + } + if (clazz == boolean.class) { + return (TypeRef) BOOLEAN; + } + if (clazz == int.class) { + return (TypeRef) INT; + } + if (clazz == long.class) { + return (TypeRef) LONG; + } + if (clazz == char.class) { + return (TypeRef) CHAR; + } + if (clazz == byte.class) { + return (TypeRef) BYTE; + } + if (clazz == void.class) { + return (TypeRef) VOID; + } + if (clazz == float.class) { + return (TypeRef) FLOAT; + } + if (clazz == double.class) { + return (TypeRef) DOUBLE; + } + if (clazz == byte[].class) { + return (TypeRef) BYTE_ARRAY; + } + if (clazz == int[].class) { + return (TypeRef) INT_ARRAY; + } + if (clazz == String[].class) { + return (TypeRef) STRING_ARRAY; + } + + return new TypeRef(clazz) {}; + } +} diff --git a/sdk/src/test/java/io/dapr/client/DaprClientHttpTest.java b/sdk/src/test/java/io/dapr/client/DaprClientHttpTest.java index 9d85cce701..14e093bee9 100644 --- a/sdk/src/test/java/io/dapr/client/DaprClientHttpTest.java +++ b/sdk/src/test/java/io/dapr/client/DaprClientHttpTest.java @@ -99,7 +99,7 @@ public void invokeServiceVerbNull() { String event = "{ \"message\": \"This is a test\" }"; daprHttp = new DaprHttp(3000, okHttpClient); daprClientHttp = new DaprClientHttp(daprHttp); - Mono mono = daprClientHttp.invokeService(null, "", "", null, null, null); + Mono mono = daprClientHttp.invokeService(null, "", "", null, null, (Class)null); assertNull(mono.block()); } @@ -112,19 +112,19 @@ public void invokeServiceIllegalArgumentException() { daprHttp = new DaprHttp(3000, okHttpClient); daprClientHttp = new DaprClientHttp(daprHttp); assertThrows(IllegalArgumentException.class, () -> { - daprClientHttp.invokeService(null, "", "", null, null, null).block(); + daprClientHttp.invokeService(null, "", "", null, null, (Class)null).block(); }); assertThrows(IllegalArgumentException.class, () -> { - daprClientHttp.invokeService(Verb.POST, null, "", null, null, null).block(); + daprClientHttp.invokeService(Verb.POST, null, "", null, null, (Class)null).block(); }); assertThrows(IllegalArgumentException.class, () -> { - daprClientHttp.invokeService(Verb.POST, "", "", null, null, null).block(); + daprClientHttp.invokeService(Verb.POST, "", "", null, null, (Class)null).block(); }); assertThrows(IllegalArgumentException.class, () -> { - daprClientHttp.invokeService(Verb.POST, "1", null, null, null, null).block(); + daprClientHttp.invokeService(Verb.POST, "1", null, null, null, (Class)null).block(); }); assertThrows(IllegalArgumentException.class, () -> { - daprClientHttp.invokeService(Verb.POST, "1", "", null, null, null).block(); + daprClientHttp.invokeService(Verb.POST, "1", "", null, null, (Class)null).block(); }); } @@ -137,7 +137,7 @@ public void invokeServiceMethodNull() { String event = "{ \"message\": \"This is a test\" }"; daprHttp = new DaprHttp(3000, okHttpClient); daprClientHttp = new DaprClientHttp(daprHttp); - Mono mono = daprClientHttp.invokeService(Verb.POST, "1", "", null, null, null); + Mono mono = daprClientHttp.invokeService(Verb.POST, "1", "", null, null, (Class)null); assertNull(mono.block()); } @@ -237,6 +237,78 @@ public void invokeBindingResponseObject() { assertEquals("OK", mono.block()); } + @Test + public void invokeBindingResponseDouble() { + Map map = new HashMap<>(); + mockInterceptor.addRule() + .post("http://127.0.0.1:3000/v1.0/bindings/sample-topic") + .respond("1.5"); + daprHttp = new DaprHttp(3000, okHttpClient); + daprClientHttp = new DaprClientHttp(daprHttp); + Mono mono = daprClientHttp.invokeBinding("sample-topic", "myoperation", "", null, double.class); + assertEquals(1.5, mono.block(), 0.0001); + } + + @Test + public void invokeBindingResponseFloat() { + Map map = new HashMap<>(); + mockInterceptor.addRule() + .post("http://127.0.0.1:3000/v1.0/bindings/sample-topic") + .respond("1.5"); + daprHttp = new DaprHttp(3000, okHttpClient); + daprClientHttp = new DaprClientHttp(daprHttp); + Mono mono = daprClientHttp.invokeBinding("sample-topic", "myoperation", "", null, float.class); + assertEquals(1.5, mono.block(), 0.0001); + } + + @Test + public void invokeBindingResponseChar() { + Map map = new HashMap<>(); + mockInterceptor.addRule() + .post("http://127.0.0.1:3000/v1.0/bindings/sample-topic") + .respond("\"a\""); + daprHttp = new DaprHttp(3000, okHttpClient); + daprClientHttp = new DaprClientHttp(daprHttp); + Mono mono = daprClientHttp.invokeBinding("sample-topic", "myoperation", "", null, char.class); + assertEquals('a', (char)mono.block()); + } + + @Test + public void invokeBindingResponseByte() { + Map map = new HashMap<>(); + mockInterceptor.addRule() + .post("http://127.0.0.1:3000/v1.0/bindings/sample-topic") + .respond("\"2\""); + daprHttp = new DaprHttp(3000, okHttpClient); + daprClientHttp = new DaprClientHttp(daprHttp); + Mono mono = daprClientHttp.invokeBinding("sample-topic", "myoperation", "", null, byte.class); + assertEquals((byte)0x2, (byte)mono.block()); + } + + @Test + public void invokeBindingResponseLong() { + Map map = new HashMap<>(); + mockInterceptor.addRule() + .post("http://127.0.0.1:3000/v1.0/bindings/sample-topic") + .respond("1"); + daprHttp = new DaprHttp(3000, okHttpClient); + daprClientHttp = new DaprClientHttp(daprHttp); + Mono mono = daprClientHttp.invokeBinding("sample-topic", "myoperation", "", null, long.class); + assertEquals(1, (long)mono.block()); + } + + @Test + public void invokeBindingResponseInt() { + Map map = new HashMap<>(); + mockInterceptor.addRule() + .post("http://127.0.0.1:3000/v1.0/bindings/sample-topic") + .respond("1"); + daprHttp = new DaprHttp(3000, okHttpClient); + daprClientHttp = new DaprClientHttp(daprHttp); + Mono mono = daprClientHttp.invokeBinding("sample-topic", "myoperation", "", null, int.class); + assertEquals(1, (int)mono.block()); + } + @Test(expected = IllegalArgumentException.class) public void invokeBindingNullName() { Map map = new HashMap<>(); diff --git a/sdk/src/test/java/io/dapr/serializer/DefaultObjectSerializerTest.java b/sdk/src/test/java/io/dapr/serializer/DefaultObjectSerializerTest.java index 9948e9f67a..cf46104c39 100644 --- a/sdk/src/test/java/io/dapr/serializer/DefaultObjectSerializerTest.java +++ b/sdk/src/test/java/io/dapr/serializer/DefaultObjectSerializerTest.java @@ -5,15 +5,22 @@ package io.dapr.serializer; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import io.dapr.serializer.DefaultObjectSerializer; import io.dapr.client.domain.CloudEvent; +import io.dapr.utils.TypeRef; import org.junit.Assert; import org.junit.Test; import java.io.IOException; import java.io.Serializable; +import java.lang.ref.Reference; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; import java.util.Base64; +import java.util.List; import java.util.function.Function; import static org.junit.Assert.*; @@ -389,15 +396,38 @@ public void deserializeObjectTest() { expectedResult.setFloatValue(1.0f); expectedResult.setDoubleValue(1000.0); MyObjectTestToSerialize result; - + try { - result = SERIALIZER.deserialize(jsonToDeserialize.getBytes(), MyObjectTestToSerialize.class); + result = SERIALIZER.deserialize(jsonToDeserialize.getBytes(), TypeRef.get(MyObjectTestToSerialize.class)); assertEquals("The expected value is different than the actual result", expectedResult, result); } catch (IOException exception) { fail(exception.getMessage()); } } + @Test + public void deserializeArrayObjectTest() { + String jsonToDeserialize = "[{\"stringValue\":\"A String\",\"intValue\":2147483647,\"boolValue\":true,\"charValue\":\"a\",\"byteValue\":65,\"shortValue\":32767,\"longValue\":9223372036854775807,\"floatValue\":1.0,\"doubleValue\":1000.0}]"; + MyObjectTestToSerialize expectedResult = new MyObjectTestToSerialize(); + expectedResult.setStringValue("A String"); + expectedResult.setIntValue(2147483647); + expectedResult.setBoolValue(true); + expectedResult.setCharValue('a'); + expectedResult.setByteValue((byte) 65); + expectedResult.setShortValue((short) 32767); + expectedResult.setLongValue(9223372036854775807L); + expectedResult.setFloatValue(1.0f); + expectedResult.setDoubleValue(1000.0); + List result; + + try { + result = SERIALIZER.deserialize(jsonToDeserialize.getBytes(), new TypeRef>(){}); + assertEquals("The expected value is different than the actual result", expectedResult, result.get(0)); + } catch (IOException exception) { + fail(exception.getMessage()); + } + } + @Test public void deserializeBytesTest() { @@ -414,12 +444,10 @@ public void deserializeBytesTest() { public void deserializeNullObjectOrPrimitiveTest() { try { - MyObjectTestToSerialize expectedObj = null; MyObjectTestToSerialize objResult = SERIALIZER.deserialize(null, MyObjectTestToSerialize.class); - assertEquals(expectedObj, objResult); - boolean expectedBoolResutl = false; + assertNull(objResult); boolean boolResult = SERIALIZER.deserialize(null, boolean.class); - assertEquals(expectedBoolResutl, boolResult); + assertEquals(false, boolResult); byte expectedByteResult = Byte.valueOf((byte) 0); byte byteResult = SERIALIZER.deserialize(null, byte.class); assertEquals(expectedByteResult, byteResult); @@ -785,6 +813,17 @@ public void deserializeCloudEventEnvelopeData() throws Exception { deserializeData.apply(new ObjectMapper().writeValueAsString("{\"id\": \"123:\", \"name\": \"Jon Doe\"}"))); } + @Test + public void deserializeListOfString() throws IOException { + List r = SERIALIZER.deserialize("[\"1\", \"2\", \"3\"]".getBytes(), new ArrayList().getClass()); + + assertNotNull(r); + assertEquals(3, r.size()); + assertEquals("1", r.get(0)); + assertEquals("2", r.get(1)); + assertEquals("3", r.get(2)); + } + private static String quote(String content) { if (content == null) { return null;