diff --git a/sdk/src/main/java/io/dapr/actors/DaprHttpAsyncClient.java b/sdk/src/main/java/io/dapr/actors/DaprHttpAsyncClient.java index e75435c19..838e13baa 100644 --- a/sdk/src/main/java/io/dapr/actors/DaprHttpAsyncClient.java +++ b/sdk/src/main/java/io/dapr/actors/DaprHttpAsyncClient.java @@ -171,6 +171,7 @@ class DaprHttpAsyncClient implements DaprAsyncClient { .addHeader(Constants.HEADER_DAPR_REQUEST_ID, requestId) .build(); + // TODO: make this call async as well. Response response = this.httpClient.newCall(request).execute(); if (!response.isSuccessful()) { diff --git a/sdk/src/main/java/io/dapr/actors/runtime/AbstractActor.java b/sdk/src/main/java/io/dapr/actors/runtime/AbstractActor.java new file mode 100644 index 000000000..81eeaac34 --- /dev/null +++ b/sdk/src/main/java/io/dapr/actors/runtime/AbstractActor.java @@ -0,0 +1,12 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.actors.runtime; + +/** + * TODO + */ +public abstract class AbstractActor { +} diff --git a/sdk/src/main/java/io/dapr/actors/runtime/Actor.java b/sdk/src/main/java/io/dapr/actors/runtime/Actor.java new file mode 100644 index 000000000..25c819325 --- /dev/null +++ b/sdk/src/main/java/io/dapr/actors/runtime/Actor.java @@ -0,0 +1,12 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.actors.runtime; + +/** + * TODO + */ +public interface Actor { +} diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorType.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorType.java new file mode 100644 index 000000000..fcedd667f --- /dev/null +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorType.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.actors.runtime; + +import java.lang.annotation.*; + +/** + * Annotation to override default behavior of Actor class. + */ +@Documented +@Target(ElementType.TYPE_USE) +@Retention(RetentionPolicy.RUNTIME) +public @interface ActorType { + + String Name(); + +} diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorTypeInformation.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorTypeInformation.java new file mode 100644 index 000000000..258ae1c39 --- /dev/null +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorTypeInformation.java @@ -0,0 +1,181 @@ +/* + * The MIT License + * + * Copyright 2019 Swen Schisler . + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package io.dapr.actors.runtime; + +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +/** + * Contains the information about the class implementing an actor. + * + * @author Swen Schisler + * @author Artur Souza + */ +public final class ActorTypeInformation { + + /** + * Actor type's name. + */ + private final String name; + + /** + * Actor's implementation class. + */ + private final Class implementationClass; + + /** + * Actor's immediate interfaces. + */ + private final Collection interfaces; + + /** + * Whether Actor type is abstract. + */ + private final boolean abstractClass; + + /** + * Whether Actor type is remindable. + */ + private final boolean remindable; + + /** + * Instantiates a new {@link ActorTypeInformation} + * @param name Actor type's name. + * @param implementationClass Actor's implementation class. + * @param interfaces Actor's immediate interfaces. + * @param abstractClass Whether Actor type is abstract. + * @param remindable Whether Actor type is remindable. + */ + private ActorTypeInformation(String name, + Class implementationClass, + Collection interfaces, + boolean abstractClass, + boolean remindable) { + this.name = name; + this.implementationClass = implementationClass; + this.interfaces = interfaces; + this.abstractClass = abstractClass; + this.remindable = remindable; + } + + /** + * Returns the name of this ActorType. + * @return ActorType's name. + */ + public String getName() { + return this.name; + } + + /** + * Gets the type of the class implementing the actor. + * + * @return The {@link Class} of implementing the actor. + */ + public Class getImplementationClass() { + return this.implementationClass; + } + + /** + * Gets the actor interfaces which derive from {@link Actor} and implemented by actor class. + * + * @return Collection of actor interfaces. + */ + public Collection getInterfaces() { + return Collections.unmodifiableCollection(this.interfaces); + } + + /** + * Gets a value indicating whether the class implementing actor is abstract. + * + * @return true if the class implementing actor is abstract, otherwise false. + */ + public boolean isAbstractClass() { + return this.abstractClass; + } + + /** + * Gets a value indicating whether the actor class implements {@link Remindable}. + * + * @return true if the actor class implements {@link Remindable}. + */ + public boolean isRemindable() { + return this.remindable; + } + + /** + * Creates the {@link ActorTypeInformation} from given Class. + * + * @param actorClass The type of class implementing the actor to create ActorTypeInformation for. + * @return ActorTypeInformation if successfully created for actorType or null. + */ + public static ActorTypeInformation tryCreate(Class actorClass) { + try { + return create(actorClass); + } catch (IllegalArgumentException e) { + return null; + } + } + + /** + * Creates an {@link #ActorTypeInformation} from actorType. + * + * @param actorClass The class implementing the actor to create ActorTypeInformation for. + * @return {@link #ActorTypeInformation} created from actorType. + */ + public static ActorTypeInformation create(Class actorClass) { + if (!ActorTypeUtilities.isActor(actorClass)) { + throw new IllegalArgumentException( + String.format( + "The type '%s' is not an Actor. An actor type must derive from '%s'.", + actorClass == null ? "" : actorClass.getCanonicalName(), + Actor.class.getCanonicalName())); + } + + // get all actor interfaces + Class[] actorInterfaces = actorClass.getInterfaces(); + + boolean isAbstract = Modifier.isAbstract(actorClass.getModifiers()); + // ensure that the if the actor type is not abstract it implements at least one actor interface + if ((actorInterfaces.length == 0) && !isAbstract) { + throw new IllegalArgumentException( + String.format( + "The actor type '%s' does not implement any actor interfaces or one of the " + + "interfaces implemented is not an actor interface. " + + "All interfaces(including its parent interface) implemented by actor type must " + + "be actor interface. An actor interface is the one that ultimately derives " + + "from '%s' type.", + actorClass == null ? "" : actorClass.getCanonicalName(), + Actor.class.getCanonicalName())); + } + + boolean isRemindable = ActorTypeUtilities.isRemindableActor(actorClass); + ActorType actorTypeAnnotation = (ActorType) actorClass.getAnnotation(ActorType.class); + String typeName = actorTypeAnnotation != null ? actorTypeAnnotation.Name() : actorClass.getSimpleName(); + + return new ActorTypeInformation(typeName, actorClass, Arrays.asList(actorInterfaces), isAbstract, isRemindable); + } + +} diff --git a/sdk/src/main/java/io/dapr/actors/runtime/ActorTypeUtilities.java b/sdk/src/main/java/io/dapr/actors/runtime/ActorTypeUtilities.java new file mode 100644 index 000000000..70ab9f3d5 --- /dev/null +++ b/sdk/src/main/java/io/dapr/actors/runtime/ActorTypeUtilities.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.actors.runtime; + +import java.util.Arrays; + +/** + * Utility class to extract information on Actor type. + */ +final class ActorTypeUtilities { + + /** + * Gets all interfaces that extend Actor. + * @param clazz Actor class. + * @return Array of Actor interfaces. + */ + public static Class[] getActorInterfaces(Class clazz) { + if (clazz == null) { + return new Class[0]; + } + + + return Arrays.stream(clazz.getInterfaces()) + .filter(t -> Actor.class.isAssignableFrom(t)) + .filter(t -> getNonActorParentClass(t) == null) + .toArray(Class[]::new); + } + + /** + * Determines if given class is an Actor interface. + * @param clazz Actor interface candidate. + * @return Whether this is an Actor interface. + */ + public static boolean isActorInterface(Class clazz) { + return (clazz != null) && clazz.isInterface() && (getNonActorParentClass(clazz) == null); + } + + /** + * Determines whether this is an Actor class. + * @param clazz Actor class candidate. + * @return Whether this is an Actor class. + */ + public static boolean isActor(Class clazz) { + if (clazz == null) { + return false; + } + + return AbstractActor.class.isAssignableFrom(clazz); + } + + /** + * Determines whether this is an remindable Actor. + * @param clazz Actor class. + * @return Whether this is an remindable Actor. + */ + public static boolean isRemindableActor(Class clazz) { + return (clazz != null) && isActor(clazz) && (Arrays.stream(clazz.getInterfaces()).filter(t -> t.equals(Remindable.class)).count() > 0); + } + + /** + * Returns the parent class if it is not the {@link AbstractActor} parent class. + * @param clazz Actor class. + * @return Parent class or null if it is {@link AbstractActor}. + */ + public static Class getNonActorParentClass(Class clazz) { + if (clazz == null) { + return null; + } + + Class[] items = Arrays.stream(clazz.getInterfaces()).filter(t -> !t.equals(Actor.class)).toArray(Class[]::new); + if (items.length == 0) { + return clazz; + } + + for (Class c : items) { + Class nonActorParent = getNonActorParentClass(c); + if (nonActorParent != null) { + return nonActorParent; + } + } + + return null; + } +} diff --git a/sdk/src/main/java/io/dapr/actors/runtime/Remindable.java b/sdk/src/main/java/io/dapr/actors/runtime/Remindable.java new file mode 100644 index 000000000..05adc464e --- /dev/null +++ b/sdk/src/main/java/io/dapr/actors/runtime/Remindable.java @@ -0,0 +1,12 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.actors.runtime; + +/** + * TODO + */ +public interface Remindable { +} diff --git a/sdk/src/test/java/io/dapr/actors/runtime/ActorTypeInformationTest.java b/sdk/src/test/java/io/dapr/actors/runtime/ActorTypeInformationTest.java new file mode 100644 index 000000000..a41df023e --- /dev/null +++ b/sdk/src/test/java/io/dapr/actors/runtime/ActorTypeInformationTest.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.actors.runtime; + +import org.junit.Assert; +import org.junit.Test; + +/** + * Unit tests for ActorTypeInformation. + */ +public class ActorTypeInformationTest { + + /** + * Actor interfaced used in this test only. + */ + private interface MyActor extends Actor { + } + + /** + * Checks information for a non-remindable actor. + */ + @Test + public void notRemindable() { + + class A extends AbstractActor implements MyActor { + } + + ActorTypeInformation info = ActorTypeInformation.create(A.class); + Assert.assertNotNull(info); + Assert.assertEquals("A", info.getName()); + Assert.assertEquals(A.class, info.getImplementationClass()); + Assert.assertFalse(info.isAbstractClass()); + Assert.assertFalse(info.isRemindable()); + Assert.assertEquals(1, info.getInterfaces().size()); + Assert.assertTrue(info.getInterfaces().contains(MyActor.class)); + } + + /** + * Checks information for a remindable actor. + */ + @Test + public void remindable() { + + class A extends AbstractActor implements MyActor, Remindable { + } + + ActorTypeInformation info = ActorTypeInformation.create(A.class); + Assert.assertNotNull(info); + Assert.assertEquals("A", info.getName()); + Assert.assertEquals(A.class, info.getImplementationClass()); + Assert.assertFalse(info.isAbstractClass()); + Assert.assertTrue(info.isRemindable()); + Assert.assertEquals(2, info.getInterfaces().size()); + Assert.assertTrue(info.getInterfaces().contains(Remindable.class)); + Assert.assertTrue(info.getInterfaces().contains(MyActor.class)); + } + + /** + * Checks information for an actor renamed via annotation. + */ + @Test + public void renamedWithAnnotation() { + @ActorType(Name = "B") + class A extends AbstractActor implements MyActor { + } + + ActorTypeInformation info = ActorTypeInformation.create(A.class); + Assert.assertNotNull(info); + Assert.assertEquals("B", info.getName()); + Assert.assertEquals(A.class, info.getImplementationClass()); + Assert.assertFalse(info.isAbstractClass()); + Assert.assertFalse(info.isRemindable()); + Assert.assertEquals(1, info.getInterfaces().size()); + Assert.assertTrue(info.getInterfaces().contains(MyActor.class)); + } + + /** + * Checks information for an actor is invalid due to an non-actor parent. + */ + @Test + public void nonActorParentClass() { + abstract class MyAbstractClass extends AbstractActor implements MyActor { + } + + class A extends MyAbstractClass { + } + + ActorTypeInformation info = ActorTypeInformation.tryCreate(A.class); + Assert.assertNull(info); + } +}