diff --git a/examples/pom.xml b/examples/pom.xml index 400b9e1df..94dec827c 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -7,38 +7,29 @@ io.dapr dapr-sdk-parent - 0.2.0-preview01 + 0.2.0 + io.dapr dapr-sdk-examples jar - 0.2.0-preview01 + 0.2.0 dapr-sdk-examples ${project.build.directory}/generated-sources - ${project.parent.basedir}/proto + ${project.basedir}/proto 11 ${java.version} ${java.version} - - io.undertow - undertow-servlet - 2.0.26.Final - commons-cli commons-cli 1.4 - - commons-io - commons-io - 2.6 - org.json json @@ -47,18 +38,27 @@ io.grpc grpc-protobuf + ${grpc.version} io.grpc grpc-stub + ${grpc.version} + + + io.grpc + grpc-api + ${grpc.version} javax.annotation javax.annotation-api + 1.3.2 io.grpc grpc-testing + ${grpc.version} test @@ -71,10 +71,25 @@ protoc-jar-maven-plugin 3.10.1 + + org.springframework.boot + spring-boot-starter-web + 2.2.2.RELEASE + + + org.springframework.boot + spring-boot-autoconfigure + 2.2.2.RELEASE + + + io.dapr + dapr-sdk-springboot + 0.2.0 + io.dapr dapr-sdk - 0.2.0-preview01 + 0.2.0 @@ -96,7 +111,7 @@ direct true - ${protobuf.input.directory}/examples + ${protobuf.input.directory} diff --git a/proto/examples/helloworld.proto b/examples/proto/helloworld.proto similarity index 100% rename from proto/examples/helloworld.proto rename to examples/proto/helloworld.proto diff --git a/examples/src/main/java/io/dapr/examples/actors/http/DemoActorService.java b/examples/src/main/java/io/dapr/examples/actors/http/DemoActorService.java index a5d39240c..83299d40f 100644 --- a/examples/src/main/java/io/dapr/examples/actors/http/DemoActorService.java +++ b/examples/src/main/java/io/dapr/examples/actors/http/DemoActorService.java @@ -5,29 +5,14 @@ package io.dapr.examples.actors.http; -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import io.dapr.actors.runtime.ActorRuntime; -import io.undertow.Undertow; -import io.undertow.server.HttpHandler; -import io.undertow.server.HttpServerExchange; -import io.undertow.server.RoutingHandler; -import io.undertow.util.Headers; +import io.dapr.springboot.DaprApplication; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.Options; -import org.apache.commons.io.IOUtils; - -import java.io.IOException; -import java.io.InputStream; -import java.io.StringWriter; -import java.io.Writer; -import java.nio.charset.StandardCharsets; -import java.util.Deque; -import java.util.Map; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; /** * Service for Actor runtime. @@ -36,193 +21,26 @@ import java.util.Map; * 2. Run the server: * dapr run --app-id demoactorservice --app-port 3000 --port 3005 -- mvn exec:java -pl=examples -Dexec.mainClass=io.dapr.examples.actors.http.DemoActorService -Dexec.args="-p 3000" */ +@SpringBootApplication public class DemoActorService { - private static final JsonFactory JSON_FACTORY = new JsonFactory(); - - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - - private static final HttpHandler ROUTES = new RoutingHandler() - .get("/", DemoActorService::handleDaprConfig) - .get("/dapr/config", DemoActorService::handleDaprConfig) - .post("/actors/{actorType}/{id}", DemoActorService::handleActorActivate) - .delete("/actors/{actorType}/{id}", DemoActorService::handleActorDeactivate) - .put("/actors/{actorType}/{id}/method/{methodName}", DemoActorService::handleActorInvoke) - .put("/actors/{actorType}/{id}/method/timer/{timerName}", DemoActorService::handleActorTimer) - .put("/actors/{actorType}/{id}/method/remind/{reminderName}", DemoActorService::handleActorReminder); - - private final int port; - - private final Undertow server; - - private DemoActorService(int port) { - this.port = port; - this.server = Undertow - .builder() - .addHttpListener(port, "localhost") - .setHandler(ROUTES) - .build(); - ActorRuntime.getInstance().registerActor(DemoActorImpl.class); - } - - private void start() { - // Now we handle ctrl+c (or any other JVM shutdown) - Runtime.getRuntime().addShutdownHook(new Thread() { - - @Override - public void run() { - System.out.println("Server: shutting down gracefully ..."); - DemoActorService.this.server.stop(); - System.out.println("Server: Bye."); - } - }); - - System.out.println(String.format("Server: listening on port %d ...", this.port)); - this.server.start(); - } - - private static void handleDaprConfig(HttpServerExchange exchange) throws IOException { - exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "application/json"); - String result = ""; - try (Writer writer = new StringWriter()) { - JsonGenerator generator = JSON_FACTORY.createGenerator(writer); - generator.writeStartObject(); - generator.writeArrayFieldStart("entities"); - for(String actorClass : ActorRuntime.getInstance().getRegisteredActorTypes()) { - generator.writeString(actorClass); - } - generator.writeEndArray(); - generator.writeStringField("actorIdleTimeout", "10s"); - generator.writeStringField("actorScanInterval", "1s"); - generator.writeStringField("drainOngoingCallTimeout", "1s"); - generator.writeBooleanField("drainBalancedActors", true); - generator.writeEndObject(); - generator.close(); - writer.flush(); - result = writer.toString(); - } - - exchange.getResponseSender().send(result); - } - - private static void handleActorActivate(HttpServerExchange exchange) { - if (exchange.isInIoThread()) { - exchange.dispatch(DemoActorService::handleActorActivate); - return; - } - - String actorType = findParamValueOrNull(exchange, "actorType"); - String actorId = findParamValueOrNull(exchange, "id"); - ActorRuntime.getInstance().activate(actorType, actorId).block(); - exchange.getResponseSender().send(""); - } - - private static void handleActorDeactivate(HttpServerExchange exchange) { - if (exchange.isInIoThread()) { - exchange.dispatch(DemoActorService::handleActorDeactivate); - return; - } - - String actorType = findParamValueOrNull(exchange, "actorType"); - String actorId = findParamValueOrNull(exchange, "id"); - ActorRuntime.getInstance().deactivate(actorType, actorId).block(); - } - - private static void handleActorInvoke(HttpServerExchange exchange) throws IOException { - if (exchange.isInIoThread()) { - exchange.dispatch(DemoActorService::handleActorInvoke); - return; - } - - String actorType = findParamValueOrNull(exchange, "actorType"); - String actorId = findParamValueOrNull(exchange, "id"); - String methodName = findParamValueOrNull(exchange, "methodName"); - exchange.startBlocking(); - String data = findMethodData(exchange.getInputStream()); - String result = ActorRuntime.getInstance().invoke(actorType, actorId, methodName, data).block(); - exchange.getResponseSender().send(buildResponse(result)); - } - - private static void handleActorTimer(HttpServerExchange exchange) throws IOException { - if (exchange.isInIoThread()) { - exchange.dispatch(DemoActorService::handleActorTimer); - return; - } - - String actorType = findParamValueOrNull(exchange, "actorType"); - String actorId = findParamValueOrNull(exchange, "id"); - String timerName = findParamValueOrNull(exchange, "timerName"); - ActorRuntime.getInstance().invokeTimer(actorType, actorId, timerName).block(); - exchange.getResponseSender().send(""); - } - - private static void handleActorReminder(HttpServerExchange exchange) throws IOException { - if (exchange.isInIoThread()) { - exchange.dispatch(DemoActorService::handleActorReminder); - return; - } - - String actorType = findParamValueOrNull(exchange, "actorType"); - String actorId = findParamValueOrNull(exchange, "id"); - String reminderName = findParamValueOrNull(exchange, "reminderName"); - exchange.startBlocking(); - String params = IOUtils.toString(exchange.getInputStream(), StandardCharsets.UTF_8); - ActorRuntime.getInstance().invokeReminder(actorType, actorId, reminderName, params).block(); - exchange.getResponseSender().send(""); - } - - private static String findParamValueOrNull(HttpServerExchange exchange, String name) { - Map> params = exchange.getQueryParameters(); - if (params == null) { - return null; - } - - Deque values = params.get(name); - if ((values == null) || (values.isEmpty())) { - return null; - } - - return values.getFirst(); - } - - private static String findMethodData(InputStream stream) throws IOException { - JsonNode root = OBJECT_MAPPER.readTree(stream); - if (root == null) { - return null; - } - - JsonNode dataNode = root.get("data"); - if (dataNode == null) { - return null; - } - - return new String(dataNode.binaryValue(), StandardCharsets.UTF_8); - } - - private static String buildResponse(String data) throws IOException { - try (Writer writer = new StringWriter()) { - JsonGenerator generator = JSON_FACTORY.createGenerator(writer); - generator.writeStartObject(); - if (data != null) { - generator.writeBinaryField("data", data.getBytes()); - } - generator.writeEndObject(); - generator.close(); - writer.flush(); - return writer.toString(); - } - } - public static void main(String[] args) throws Exception { Options options = new Options(); - options.addRequiredOption("p", "port", true, "Port to listen to."); + options.addRequiredOption("p", "port", true, "Port Dapr will listen to."); CommandLineParser parser = new DefaultParser(); CommandLine cmd = parser.parse(options, args); // If port string is not valid, it will throw an exception. int port = Integer.parseInt(cmd.getOptionValue("port")); - final DemoActorService service = new DemoActorService(port); - service.start(); + + // Register the Actor class. + ActorRuntime.getInstance().registerActor(DemoActorImpl.class); + + // Start Dapr's callback endpoint. + DaprApplication.start(port); + + // Start application's endpoint. + SpringApplication.run(DemoActorService.class); } } diff --git a/examples/src/main/java/io/dapr/examples/actors/http/HelloController.java b/examples/src/main/java/io/dapr/examples/actors/http/HelloController.java new file mode 100644 index 000000000..37eeec522 --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/actors/http/HelloController.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.examples.actors.http; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class HelloController { + + @RequestMapping("/") + public String index() { + return "Greetings from your Spring Boot Application!"; + } + +} diff --git a/pom.xml b/pom.xml index aae62bcc6..ee5be6777 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ io.dapr dapr-sdk-parent pom - 0.2.0-preview01 + 0.2.0 dapr-sdk-parent SDK for Dapr. https://dapr.io @@ -33,6 +33,11 @@ pom import + + io.grpc + grpc-api + ${grpc.version} + javax.annotation javax.annotation-api @@ -79,6 +84,7 @@ sdk-autogen sdk + sdk-springboot examples diff --git a/sdk-autogen/pom.xml b/sdk-autogen/pom.xml index 399a36cb4..f60417d9c 100644 --- a/sdk-autogen/pom.xml +++ b/sdk-autogen/pom.xml @@ -7,12 +7,12 @@ io.dapr dapr-sdk-parent - 0.2.0-preview01 + 0.2.0 dapr-sdk-autogen jar - 0.2.0-preview01 + 0.2.0 dapr-sdk-autogen Auto-generated SDK for Dapr diff --git a/sdk-springboot/pom.xml b/sdk-springboot/pom.xml new file mode 100644 index 000000000..ed1ae238c --- /dev/null +++ b/sdk-springboot/pom.xml @@ -0,0 +1,64 @@ + + 4.0.0 + + + io.dapr + dapr-sdk-parent + 0.2.0 + + + io.dapr + dapr-sdk-springboot + jar + 0.2.0 + dapr-sdk-springboot + + + 8 + ${java.version} + ${java.version} + + + + + javax.annotation + javax.annotation-api + 1.3.2 + + + org.springframework.boot + spring-boot + 2.2.2.RELEASE + + + org.springframework.boot + spring-boot-starter-web + 2.2.2.RELEASE + + + io.dapr + dapr-sdk + 0.2.0 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 8 + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/sdk-springboot/src/main/java/io/dapr/springboot/DaprApplication.java b/sdk-springboot/src/main/java/io/dapr/springboot/DaprApplication.java new file mode 100644 index 000000000..b8dd5fa27 --- /dev/null +++ b/sdk-springboot/src/main/java/io/dapr/springboot/DaprApplication.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.springboot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +import java.util.Properties; + +/** + * Dapr's callback implementation via SpringBoot. + */ +@SpringBootApplication +public class DaprApplication { + + /** + * Starts Dapr's callback in a given port. + * @param port Port to listen to. + */ + public static void start(int port) { + SpringApplication app = new SpringApplication(DaprApplication.class); + Properties properties = new Properties(); + properties.setProperty("server.port", Integer.toString(port)); + app.setDefaultProperties(properties); + app.run(); + } + + /** + * Main for SpringBoot requirements. + * @param args Command line arguments. + */ + public static void main(String[] args) { + SpringApplication.run(DaprApplication.class, args); + } + +} diff --git a/sdk-springboot/src/main/java/io/dapr/springboot/DaprController.java b/sdk-springboot/src/main/java/io/dapr/springboot/DaprController.java new file mode 100644 index 000000000..cb890b36d --- /dev/null +++ b/sdk-springboot/src/main/java/io/dapr/springboot/DaprController.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.springboot; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.dapr.actors.runtime.ActorRuntime; +import org.springframework.web.bind.annotation.*; +import reactor.core.publisher.Mono; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.nio.charset.StandardCharsets; + +/** + * SpringBoot Controller to handle callback APIs for Dapr. + * TODO: use POJOs instead of String when possible. + * TODO: JavaDocs. + */ +@RestController +public class DaprController { + + private static final JsonFactory JSON_FACTORY = new JsonFactory(); + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + @RequestMapping("/") + public String index() { + return "Greetings from Dapr!"; + } + + @RequestMapping("/dapr/config") + public String daprConfig() throws Exception { + try (Writer writer = new StringWriter()) { + JsonGenerator generator = JSON_FACTORY.createGenerator(writer); + generator.writeStartObject(); + generator.writeArrayFieldStart("entities"); + for (String actorClass : ActorRuntime.getInstance().getRegisteredActorTypes()) { + generator.writeString(actorClass); + } + generator.writeEndArray(); + generator.writeStringField("actorIdleTimeout", "10s"); + generator.writeStringField("actorScanInterval", "1s"); + generator.writeStringField("drainOngoingCallTimeout", "1s"); + generator.writeBooleanField("drainBalancedActors", true); + generator.writeEndObject(); + generator.close(); + writer.flush(); + return writer.toString(); + } + } + + @RequestMapping(method = RequestMethod.POST, path = "/actors/{type}/{id}") + public Mono activateActor(@PathVariable("type") String type, + @PathVariable("id") String id) throws Exception { + return ActorRuntime.getInstance().activate(type, id); + } + + @RequestMapping(method = RequestMethod.DELETE, path = "/actors/{type}/{id}") + public Mono deactivateActor(@PathVariable("type") String type, + @PathVariable("id") String id) throws Exception { + return ActorRuntime.getInstance().deactivate(type, id); + } + + @RequestMapping(method = RequestMethod.PUT, path = "/actors/{type}/{id}/method/{method}") + public Mono invokeActorMethod(@PathVariable("type") String type, + @PathVariable("id") String id, + @PathVariable("method") String method, + @RequestBody(required = false) String body) { + try { + String data = findMethodData(body); + return ActorRuntime.getInstance().invoke(type, id, method, data).map(r -> buildResponse(r)); + } catch (Exception e) { + return Mono.error(e); + } + } + + @RequestMapping(method = RequestMethod.PUT, path = "/actors/{type}/{id}/method/timer/{timer}") + public Mono invokeActorTimer(@PathVariable("type") String type, + @PathVariable("id") String id, + @PathVariable("timer") String timer) { + return ActorRuntime.getInstance().invokeTimer(type, id, timer); + } + + @RequestMapping(method = RequestMethod.PUT, path = "/actors/{type}/{id}/method/remind/{reminder}") + public Mono invokeActorReminder(@PathVariable("type") String type, + @PathVariable("id") String id, + @PathVariable("reminder") String reminder, + @RequestBody(required = false) String body) { + return ActorRuntime.getInstance().invokeReminder(type, id, reminder, body); + } + + private static String findMethodData(String body) throws IOException { + if (body == null) { + return null; + } + + JsonNode root = OBJECT_MAPPER.readTree(body); + if (root == null) { + return null; + } + + JsonNode dataNode = root.get("data"); + if (dataNode == null) { + return null; + } + + return new String(dataNode.binaryValue(), StandardCharsets.UTF_8); + } + + private static String buildResponse(String data) throws RuntimeException { + try { + try (Writer writer = new StringWriter()) { + JsonGenerator generator = JSON_FACTORY.createGenerator(writer); + generator.writeStartObject(); + if (data != null) { + generator.writeBinaryField("data", data.getBytes()); + } + generator.writeEndObject(); + generator.close(); + writer.flush(); + return writer.toString(); + } + } catch (IOException e) { + // Make Mono happy. + throw new RuntimeException(e); + } + } +} diff --git a/sdk/pom.xml b/sdk/pom.xml index 650e5ba59..e036c01d0 100644 --- a/sdk/pom.xml +++ b/sdk/pom.xml @@ -7,12 +7,12 @@ io.dapr dapr-sdk-parent - 0.2.0-preview01 + 0.2.0 dapr-sdk jar - 0.2.0-preview01 + 0.2.0 dapr-sdk SDK for Dapr @@ -20,7 +20,7 @@ io.dapr dapr-sdk-autogen - 0.2.0-preview01 + 0.2.0 com.fasterxml.jackson.core