From cf68f242890d50d4a995f0b7c36f596ddaa8dea0 Mon Sep 17 00:00:00 2001 From: Artur Souza Date: Fri, 24 Jan 2020 12:49:56 -0800 Subject: [PATCH] Java SDK with all features implemented for milestone 0.4 (#154) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Basic Dapr HTTP Client for Actors. * ActorTypeInformation with parsing logic + some stubs. * Fixing license. * Adding MethodContext + StateSerializer. * Add ActorRuntime (#44) * Add ActorRuntime * split DaprAsyncClient hierarchy into 2 hierarchies for the different directions of communication * Rename DaprClientBase to AbstractDaprClient * Add ActorId * Encapsulating async call in AbstractDaprClient as a single method. (#46) * Add ActorMethodInfoMap (#47) * Add ActorMethodInfoMap * Add ConverterUtils class (#49) * Add ConverterUtils + unit test * Improved async call to okhttp. Former solution just wrapped a blocking call, now we using asynch call and collect the results via callbacks. Mono's are created based on the result. This make more efficient use of the resources since nothing is blocking now. Reformatted according new checkstyle (#48) * More reformatting to 2 spaces indentation. (#50) * Add ActorTimer and related (#51) * Add ActorTimer and related * cr * ActorService + DaprStateProvider + some more. (#53) * Faster JSON building + separate auto-gen jar + tests. (#55) * closed the response objects and returning a string (#56) * Add ReminderInfo (#54) * Add ReminderInfo * remove extra var * More Actors Stuff (#57) * ActorManager * More work done. * Adding example for actor runtime service. * Implements ActorStateManager + fixes + javadocs. * ActorProxy + some refactoring (#62) * ActorManager * More work done. * Adding example for actor runtime service. * Implements ActorStateManager + fixes + javadocs. * Fix OrderManager example in order to process http 201 Change the implement the calls to DAPR using the class AbstractDaprClient because before the change the class always return Mono.Empty Implementation of the ActorAsyncProxy Change the name of the Actor Dapr Http Async Client * Update code with the changes proposed by Artur in the code review * Changes to support ActorProxy + Fixes. Co-authored-by: Juan Jose Herrera <35985447+JuanJose-Herrera@users.noreply.github.com> * Adding new methods for Http Client (#60) * Adding new methods for Http Client * Finishing implementation for HttpClient * Adding JavaDoc and Changing return Type to new Methods * Using ObjectSerializer and changing Constants values * Adding gRPC client adapter, to encapsulate gRPC logic from the user (#61) * Adding a common adapter to be exposed to users, for calling either the gRPC or the HTTP Clients. Implementing gRPC adapter based on interface. Moving ActorStateSerializer and renamed to be used as a generic utility for Serializing objects * Creating a builder for the GrpcClient Abstracting generic serialize and deserialize methods into a generic base class while leaving Actor specific stuff into the ActorStateSerializer class Adding Javadocs * Unit tests for ActorManager + fixes for Timer and Reminder. (#63) * Add UnitTest to ActorProxyImpl and JaCoCo (coverage tool) implementation (#67) * Change from unwrapMethodResponse to deserialize, because the response from an actor method is not wrapped in "Data" object Add Unit test for the ActorProxy class * #24 Implement more testing to the ActorProxyImpl and refactor how we manage the errors with Mono #20 Add the coverage tool JaCoCo, the rules are commented at this moment in order to allow to execute the examples at this moment, if we enable the rules, the compilation fails. * Enable rules for unit test coverage with a minimum of 0 in order to allow the developers to success compile, the minimum should be set to .8 before the projects ends * Use the unwrapMethodResponse of the serilizer in order to deserilize actor responses. * Reverting ActorProxyImpl.java There is not need to change ActorProxyImpl since the previous change to not wrap it with the "data" structure was incorrect. Co-authored-by: Artur Souza * Starting package to handle SpringBoot integration. (#69) * Request body must be null on get for okhttp, empty body does not work… (#70) * Request body must be null on get for okhttp, empty body does not work. Also, expose the ActorStateManager methods that ought to be exposed to user. * Make ctor package private and use equals for string compare * Refactoring HTTP Client and creating Client HTTP Adapter (#68) * Refactoring HTTP Client to follow same pattern as the GRPC Client. Creating DaprClientHttpAdapter to be exposed to the users. Refactor all uses of the http Client to use the adapter instead following composition rather than inheritance. Dealing with conflicts * Renaming AppToDaprHttpAsync to AppToDaprHttpAsyncClient changed previously by mistake * Refactor Adapters to centralize all generic and actor specific functionality in the same place, having a single entry point for all communications to DAPR. Leaving GRPC Adapter implemented, but without the possibility to create an instance of it. Fixing Test cases * Addressing PR comments * PubSub + related fixes. (#71) * Fixing issue regarding GET and DELETE method (#76) * Unit tests for PubSub + JavaDocs. (#75) * Invoke service + unit tests. (#78) * Java sdk wip (#77) * Fixing issue regarding GET and DELETE method * Adding unit test for DaprHttp.java * Adding test scope * Renaming a property and fixing conflict to merge. Co-authored-by: Artur Souza * pubsub sample needs update for listener interface (#80) * Fix bindings + add samples (#81) * Fix binding and add samples * Fix sample command line * update comment for name change * lowercase d matches runtime, though it looks like it is case insensitive * Spliting Actor client and runtime into a separate jar. (#83) * Unit testing + bug fixing in GRPC Adapter (#79) * Adding mockito plugin to be able to mock final classes Increasing test coverage for ObjectSerializer Fixing bug in GRPC Adapter while creating the envelopes, found during unit testing. * First step into returning Http Headers as part of the response for the DaprClientHttpAdapter Updating State object to match the API. Fixing Broken Unit Tests and increasing coverage for DaprClientGrpcAdapter * Adding documentation, fixing typos and renaming support method to be more descriptive. * Addressing PR comments Increasing test coverage Fixing Merge conflicts * Addressing PR comments * Addressing comments regarding where to build the query parameters forthe HTTP Client calls. (#84) * Addressing comments regarding where to build the query parameters for the HTTP Client calls. * Making map immutable while also preventing a NPE * Fixing DaprHttp and its tests. (#93) * Increasing test coverage for DaprClientGrpcAdapter (#94) * Upload jacoco test coverage report to artifact storage (#95) * set jacoco report output dir * upload test report * add dapr version info * #26 Integretion Testing Initial Example (#74) * #26 Add Hello World Integration Testing working on Windows, need work to work on MAC and Linux * #26 Add new Integration Test to test DAPR state functionality * #26 Add Hello World Integration Testing working on Windows, need work to work on MAC and Linux * Update Integration Testing getting free ports automatically * #26 Refractor to use a base class for all the integration tests * #26 Make StateOptions as optional in order to not throw a null pointer exception * #26 Remove empty lines and correct the ident * Adding license to DaprIntegrationTestingRunner Co-authored-by: Young Bu Park Co-authored-by: Artur Souza * Fixing and finishing unit tests (#97) * Fixing and finishing unit tests * Fixing styling Issues and adding new test * Unit tests for stateful actors + fixes. (#104) * Receiving the StateOptions on save as part of the State, to comply with the DAPR API (#105) * Receiving the StateOptions on save as part of the State, to comply with the DAPR API * #26 Add integration test for Concurrency funtionality in the states module Co-authored-by: Juan Jose Herrera <35985447+JuanJose-Herrera@users.noreply.github.com> Co-authored-by: Artur Souza * Deploy Jar packages to Nexus via CI (#106) * Add SNAPSHOT suffix to version * set deployment setup * add ossrh repostiory * OSSRH setting * add deployment steps * exclude examples jar * remove condition * exclude spring boot pkg Co-authored-by: Artur Souza * deactivate should not call save (#108) Co-authored-by: Artur Souza * Fixing bug, where call should be deferred instead of async (#102) * Fixing bug, where call should be deferred instead of async * Removing unused pool property Returning null as response body if empty response is found. Adding Test case for validating callback is executed when expected. * Fixing assertion based on previous changes * Adding test case to verify that the call to grpc only happens when the requestor block the thread, instead of in parallel immediately after calling the methods in the adapter. Documenting complex test case * Fixing merge conflicts * Documenting complex test case * Disable GPG sign by default (#111) * [CD] Fix gpg private key import (#113) * fix gpg key import process * add GPG_TTY env setting * revert * rename dapr-sdk to dapr-actors * fix * Fix GPG plugin * [CD] Conditional SNAPSHOT and final release (#115) * Release pkg based on tag and branch * Update build.yml * Update build.yml * Creating unit tests for HttpAdapter + Adding junit 5 dependency + Reformating code (#112) Co-authored-by: Artur Souza * Removing springboot jar and adding some unit tests for ActorRuntime. (#114) * Release SNAPSHOT pkg for every build (#118) * Release SNAPSHOT build every build * fix * Fix sample (#119) Co-authored-by: Artur Souza * Unit tests for actors, no state. (#117) * actor unit tests, no state clean up a bit * Rename class * Fix merge Co-authored-by: Artur Souza * Moving io.dapr.runtime to test + fixing examples accordingly. (#121) * mvn integration-test doesn't work w/o this plugin (#122) * Resolving conflict for PR #110 (#130) * Renaming State domain object. Removing duplicate use of State Options. Adding custom serialize and deserialize classes for StateOptions objects. * #26 Refractor StateOptions to clean unused code * Fixing merge issues. * Fixing unit test for DaprClientHttpAdapterTest after refactoring. Co-authored-by: Juan Jose Herrera <35985447+JuanJose-Herrera@users.noreply.github.com> * Refactor StateOptions and add IT consistency testing (#110) * Renaming State domain object. Removing duplicate use of State Options. Adding custom serialize and deserialize classes for StateOptions objects. * #26 Refractor StateOptions to clean unused code * Fixing merge issues. * #26 Add integration test for states using Retry policies Co-authored-by: Artur Souza * Fix IT issues, to run the integration test rember to add the parameter skipITs=false (#131) * Addresses multiple items from design review (#124) * Use byte[] for customer data and serialized data. Fixes for actor demo. * Simplifying ObjectSerializer + handling CloudEvent properly. * Split DaprClient into 3 parts to hide actor APIs. * Remove Actor "dummy" interface. * Supports custom serializer. * Change seriaizer to receive Object directly. * Handling custom serialization in State APIs + enhancements to the state APIs. * Addressing small comment on Mono chain in ActorManager. * Make serializer mandatory, throwing exception when null. * Fix arg parsing problem -Dexec.args= seems to not allow hyphens inside (#136) * Fix bug in Actor due to timer serialization. (#139) * Missing block (#140) * Install local test kafka before running build and tests (#142) * local test kafka docker-compose yaml * add kafka install step * Separate serializer for state. (#135) * Add gRPC State Integration Test (#138) * Add GRP State Integration Test * Explain ignored test cases for GRPC * Explain ignored test cases for GRPC * Update DaprClientTestBuilder.java Co-authored-by: Artur Souza * Actor Activation / Deactivation Integartion Test (#144) * Adding common setup for starting dapr applications in Integration Tests. Adding integration test for Actor Activation and Reactivation. * Changing the way parameters are sent to the application running under Dapr in ITs to fix issue found in Ubuntu about wrong parsing. Moving logic of the ActivationClient into the IT class * Fixing compilation issues in test due to rebase from upstream. * Addressing PR comments * Adding more unit test to reach 80% Coverage (#149) * Adding checkstyle + fix style in sdk-actos. (#146) * Update debug instructions since LB is not needed. (#150) * Add E2E testing for bindings (#148) * Add GRP State Integration Test * Explain ignored test cases for GRPC * Explain ignored test cases for GRPC * Update DaprClientTestBuilder.java * Add binding E2E testing. * Add fix binding E2E testing after merge with new changes * Remove example comments Co-authored-by: Artur Souza * Adding documentation for examples (#153) Co-authored-by: Leon Mai Co-authored-by: ji11er <57505038+ji11er@users.noreply.github.com> Co-authored-by: Juan Jose Herrera <35985447+JuanJose-Herrera@users.noreply.github.com> Co-authored-by: mestizoLopez Co-authored-by: Andres Robles <15348598+AndresRoblesMX@users.noreply.github.com> Co-authored-by: Young Bu Park Co-authored-by: Marcos Reyes <59033058+marcosreyes05@users.noreply.github.com> --- .github/workflows/build.yml | 41 +- .gitignore | 1 + README.md | 27 +- checkstyle.xml | 268 + examples/components/kafka_bindings.yaml | 20 + examples/pom.xml | 47 +- .../proto}/helloworld.proto | 0 .../dapr/examples/actors/http/DemoActor.java | 22 + .../examples/actors/http/DemoActorClient.java | 75 + .../examples/actors/http/DemoActorImpl.java | 103 + .../actors/http/DemoActorService.java | 48 + .../examples/actors/http/HelloController.java | 19 + .../bindings/InputBindingController.java | 25 + .../bindings/InputBindingExample.java | 37 + .../bindings/OutputBindingExample.java | 56 + .../java/io/dapr/examples/bindings/README.md | 142 + .../bindings/docker-compose-single-kafka.yml | 14 + .../invoke/grpc/HelloWorldClient.java | 183 +- .../invoke/grpc/HelloWorldService.java | 196 +- .../examples/invoke/http/DemoService.java | 39 + .../invoke/http/DemoServiceController.java | 61 + .../examples/invoke/http/InvokeClient.java | 36 + .../io/dapr/examples/invoke/http/README.md | 130 + .../dapr/examples/pubsub/http/Publisher.java | 55 + .../io/dapr/examples/pubsub/http/README.md | 140 + .../dapr/examples/pubsub/http/Subscriber.java | 36 + .../pubsub/http/SubscriberController.java | 47 + .../io/dapr/examples/state/grpc/Example.java | 80 +- .../examples/state/http/OrderManager.java | 174 +- .../io/dapr/springboot/DaprApplication.java | 26 + .../io/dapr/springboot/DaprController.java | 63 + .../main/resources/img/exposer-service.png | Bin 0 -> 131379 bytes .../src/main/resources/img/inputbinding.png | Bin 0 -> 34461 bytes .../src/main/resources/img/outputbinding.png | Bin 0 -> 33313 bytes examples/src/main/resources/img/publisher.png | Bin 0 -> 414404 bytes .../src/main/resources/img/subscriber.png | Bin 0 -> 348756 bytes pom.xml | 111 +- sdk-actors/pom.xml | 158 + .../src/main/java/io/dapr/actors/ActorId.java | 129 + .../main/java/io/dapr/actors/ActorTrace.java | 91 + .../io/dapr/actors/client/ActorProxy.java | 63 + .../dapr/actors/client/ActorProxyBuilder.java | 63 + .../io/dapr/actors/client/ActorProxyImpl.java | 136 + .../io/dapr/actors/client/DaprClient.java | 26 + .../io/dapr/actors/client/DaprHttpClient.java | 46 + .../io/dapr/actors/runtime/AbstractActor.java | 349 + .../io/dapr/actors/runtime/ActorCallType.java | 26 + .../io/dapr/actors/runtime/ActorFactory.java | 26 + .../io/dapr/actors/runtime/ActorManager.java | 342 + .../actors/runtime/ActorMethodContext.java | 81 + .../actors/runtime/ActorMethodInfoMap.java | 56 + .../actors/runtime/ActorReminderParams.java | 104 + .../io/dapr/actors/runtime/ActorRuntime.java | 279 + .../actors/runtime/ActorRuntimeConfig.java | 34 + .../actors/runtime/ActorRuntimeContext.java | 140 + .../dapr/actors/runtime/ActorStateChange.java | 68 + .../actors/runtime/ActorStateChangeKind.java | 56 + .../actors/runtime/ActorStateManager.java | 311 + .../io/dapr/actors/runtime/ActorTimer.java | 116 + .../io/dapr/actors/runtime/ActorType.java | 29 + .../actors/runtime/ActorTypeInformation.java | 152 + .../actors/runtime/ActorTypeUtilities.java | 96 + .../io/dapr/actors/runtime/DaprClient.java | 76 + .../dapr/actors/runtime/DaprHttpClient.java | 88 + .../runtime/DaprStateAsyncProvider.java | 167 + .../actors/runtime/DefaultActorFactory.java | 48 + .../dapr/actors/runtime/ObjectSerializer.java | 210 + .../io/dapr/actors/runtime/Remindable.java | 36 + .../test/java/io/dapr/actors/ActorIdTest.java | 96 + .../actors/client/ActorProxyBuilderTest.java | 48 + .../actors/client/ActorProxyForTestsImpl.java | 16 + .../actors/client/ActorProxyImplTest.java | 293 + .../io/dapr/actors/client/DaprClientStub.java | 17 + .../actors/client/DaprHttpClientTest.java | 48 + .../test/java/io/dapr/actors/it/BaseIT.java | 64 + .../it/DaprIntegrationTestingRunner.java | 168 + .../it/actors/ActivationDeactiviationIT.java | 105 + .../it/services/springboot/ActorService.java | 26 + .../services/springboot/DaprApplication.java | 21 + .../services/springboot/DaprController.java | 68 + .../it/services/springboot/DemoActor.java | 9 + .../it/services/springboot/DemoActorImpl.java | 56 + .../it/services/springboot/EmptyService.java | 14 + .../runtime/ActorCustomSerializerTest.java | 176 + .../dapr/actors/runtime/ActorManagerTest.java | 217 + .../runtime/ActorMethodInfoMapTest.java | 53 + .../dapr/actors/runtime/ActorNoStateTest.java | 219 + .../runtime/ActorReminderParamsTest.java | 63 + .../dapr/actors/runtime/ActorRuntimeTest.java | 151 + .../actors/runtime/ActorStatefulTest.java | 665 + .../dapr/actors/runtime/ActorTimerTest.java | 63 + .../runtime/ActorTypeInformationTest.java | 116 + .../actors/runtime/DaprHttpClientTest.java | 105 + .../runtime/DaprInMemoryStateProvider.java | 78 + .../runtime/DaprStateAsyncProviderTest.java | 251 + .../runtime/DefaultActorFactoryTest.java | 82 + .../dapr/actors/runtime/DerivedActorTest.java | 374 + .../dapr/actors/runtime/JavaSerializer.java | 46 + .../ThrowFromPreAndPostActorMethodsTest.java | 193 + .../java/io/dapr/client/DaprHttpProxy.java | 16 + sdk-autogen/pom.xml | 114 + sdk-autogen/src/main/java/.keepme | 0 sdk-autogen/src/test/java/.keepme | 0 sdk/pom.xml | 175 +- sdk/src/main/java/io/dapr/DaprClientGrpc.java | 590 - .../main/java/io/dapr/DaprClientProtos.java | 9676 ----------- sdk/src/main/java/io/dapr/DaprGrpc.java | 664 - sdk/src/main/java/io/dapr/DaprProtos.java | 13828 ---------------- .../main/java/io/dapr/client/DaprClient.java | 194 + .../io/dapr/client/DaprClientBuilder.java | 122 + .../io/dapr/client/DaprClientGrpcAdapter.java | 412 + .../io/dapr/client/DaprClientHttpAdapter.java | 356 + .../main/java/io/dapr/client/DaprHttp.java | 204 + .../java/io/dapr/client/DaprHttpBuilder.java | 84 + .../java/io/dapr/client/ObjectSerializer.java | 130 + .../io/dapr/client/domain/CloudEvent.java | 202 + .../java/io/dapr/client/domain/State.java | 125 + .../io/dapr/client/domain/StateOptions.java | 203 + .../main/java/io/dapr/client/domain/Verb.java | 15 + .../java/io/dapr/exceptions/DaprError.java | 62 + .../io/dapr/exceptions/DaprException.java | 71 + .../dapr/serializer/DaprObjectSerializer.java | 34 + .../serializer/DefaultObjectSerializer.java | 33 + .../io/dapr/serializer/StringContentType.java | 19 + .../main/java/io/dapr/utils/Constants.java | 106 + .../java/io/dapr/utils/DurationUtils.java | 142 + .../io/dapr/client/DaprClientBuilderTest.java | 27 + .../client/DaprClientGrpcAdapterTest.java | 1037 ++ .../client/DaprClientHttpAdapterTest.java | 376 + .../io/dapr/client/DaprClientTestBuilder.java | 39 + .../io/dapr/client/DaprHttpBuilderTest.java | 19 + .../java/io/dapr/client/DaprHttpStub.java | 54 + .../java/io/dapr/client/DaprHttpTest.java | 201 + sdk/src/test/java/io/dapr/it/BaseIT.java | 84 + .../dapr/it/DaprIntegrationTestingRunner.java | 158 + .../it/binding/http/OutputBindingExample.java | 96 + .../io/dapr/it/deploy/local-test-kafka.yml | 14 + .../io/dapr/it/services/EmptyService.java | 19 + .../services/HelloWorldGrpcStateService.java | 51 + .../it/services/InputBindingController.java | 41 + .../dapr/it/services/InputBindingExample.java | 30 + .../io/dapr/it/state/GRPCStateClientIT.java | 546 + .../io/dapr/it/state/HelloWorldClientIT.java | 76 + .../io/dapr/it/state/HttpStateClientIT.java | 549 + sdk/src/test/java/io/dapr/runtime/Dapr.java | 233 + .../java/io/dapr/runtime/DaprRuntime.java | 66 + .../java/io/dapr/runtime/DaprRuntimeTest.java | 331 + .../java/io/dapr/runtime/MethodListener.java | 27 + .../java/io/dapr/runtime/TopicListener.java | 27 + .../DefaultObjectSerializerTest.java | 794 + .../java/io/dapr/utils/DurationUtilsTest.java | 101 + .../org.mockito.plugins.MockMaker | 1 + settings.xml | 26 + 153 files changed, 17252 insertions(+), 25173 deletions(-) create mode 100644 checkstyle.xml create mode 100644 examples/components/kafka_bindings.yaml rename {proto/examples => examples/proto}/helloworld.proto (100%) create mode 100644 examples/src/main/java/io/dapr/examples/actors/http/DemoActor.java create mode 100644 examples/src/main/java/io/dapr/examples/actors/http/DemoActorClient.java create mode 100644 examples/src/main/java/io/dapr/examples/actors/http/DemoActorImpl.java create mode 100644 examples/src/main/java/io/dapr/examples/actors/http/DemoActorService.java create mode 100644 examples/src/main/java/io/dapr/examples/actors/http/HelloController.java create mode 100644 examples/src/main/java/io/dapr/examples/bindings/InputBindingController.java create mode 100644 examples/src/main/java/io/dapr/examples/bindings/InputBindingExample.java create mode 100644 examples/src/main/java/io/dapr/examples/bindings/OutputBindingExample.java create mode 100644 examples/src/main/java/io/dapr/examples/bindings/README.md create mode 100644 examples/src/main/java/io/dapr/examples/bindings/docker-compose-single-kafka.yml create mode 100644 examples/src/main/java/io/dapr/examples/invoke/http/DemoService.java create mode 100644 examples/src/main/java/io/dapr/examples/invoke/http/DemoServiceController.java create mode 100644 examples/src/main/java/io/dapr/examples/invoke/http/InvokeClient.java create mode 100644 examples/src/main/java/io/dapr/examples/invoke/http/README.md create mode 100644 examples/src/main/java/io/dapr/examples/pubsub/http/Publisher.java create mode 100644 examples/src/main/java/io/dapr/examples/pubsub/http/README.md create mode 100644 examples/src/main/java/io/dapr/examples/pubsub/http/Subscriber.java create mode 100644 examples/src/main/java/io/dapr/examples/pubsub/http/SubscriberController.java create mode 100644 examples/src/main/java/io/dapr/springboot/DaprApplication.java create mode 100644 examples/src/main/java/io/dapr/springboot/DaprController.java create mode 100644 examples/src/main/resources/img/exposer-service.png create mode 100644 examples/src/main/resources/img/inputbinding.png create mode 100644 examples/src/main/resources/img/outputbinding.png create mode 100644 examples/src/main/resources/img/publisher.png create mode 100644 examples/src/main/resources/img/subscriber.png create mode 100644 sdk-actors/pom.xml create mode 100644 sdk-actors/src/main/java/io/dapr/actors/ActorId.java create mode 100644 sdk-actors/src/main/java/io/dapr/actors/ActorTrace.java create mode 100644 sdk-actors/src/main/java/io/dapr/actors/client/ActorProxy.java create mode 100644 sdk-actors/src/main/java/io/dapr/actors/client/ActorProxyBuilder.java create mode 100644 sdk-actors/src/main/java/io/dapr/actors/client/ActorProxyImpl.java create mode 100644 sdk-actors/src/main/java/io/dapr/actors/client/DaprClient.java create mode 100644 sdk-actors/src/main/java/io/dapr/actors/client/DaprHttpClient.java create mode 100644 sdk-actors/src/main/java/io/dapr/actors/runtime/AbstractActor.java create mode 100644 sdk-actors/src/main/java/io/dapr/actors/runtime/ActorCallType.java create mode 100644 sdk-actors/src/main/java/io/dapr/actors/runtime/ActorFactory.java create mode 100644 sdk-actors/src/main/java/io/dapr/actors/runtime/ActorManager.java create mode 100644 sdk-actors/src/main/java/io/dapr/actors/runtime/ActorMethodContext.java create mode 100644 sdk-actors/src/main/java/io/dapr/actors/runtime/ActorMethodInfoMap.java create mode 100644 sdk-actors/src/main/java/io/dapr/actors/runtime/ActorReminderParams.java create mode 100644 sdk-actors/src/main/java/io/dapr/actors/runtime/ActorRuntime.java create mode 100644 sdk-actors/src/main/java/io/dapr/actors/runtime/ActorRuntimeConfig.java create mode 100644 sdk-actors/src/main/java/io/dapr/actors/runtime/ActorRuntimeContext.java create mode 100644 sdk-actors/src/main/java/io/dapr/actors/runtime/ActorStateChange.java create mode 100644 sdk-actors/src/main/java/io/dapr/actors/runtime/ActorStateChangeKind.java create mode 100644 sdk-actors/src/main/java/io/dapr/actors/runtime/ActorStateManager.java create mode 100644 sdk-actors/src/main/java/io/dapr/actors/runtime/ActorTimer.java create mode 100644 sdk-actors/src/main/java/io/dapr/actors/runtime/ActorType.java create mode 100644 sdk-actors/src/main/java/io/dapr/actors/runtime/ActorTypeInformation.java create mode 100644 sdk-actors/src/main/java/io/dapr/actors/runtime/ActorTypeUtilities.java create mode 100644 sdk-actors/src/main/java/io/dapr/actors/runtime/DaprClient.java create mode 100644 sdk-actors/src/main/java/io/dapr/actors/runtime/DaprHttpClient.java create mode 100644 sdk-actors/src/main/java/io/dapr/actors/runtime/DaprStateAsyncProvider.java create mode 100644 sdk-actors/src/main/java/io/dapr/actors/runtime/DefaultActorFactory.java create mode 100644 sdk-actors/src/main/java/io/dapr/actors/runtime/ObjectSerializer.java create mode 100644 sdk-actors/src/main/java/io/dapr/actors/runtime/Remindable.java create mode 100644 sdk-actors/src/test/java/io/dapr/actors/ActorIdTest.java create mode 100644 sdk-actors/src/test/java/io/dapr/actors/client/ActorProxyBuilderTest.java create mode 100644 sdk-actors/src/test/java/io/dapr/actors/client/ActorProxyForTestsImpl.java create mode 100644 sdk-actors/src/test/java/io/dapr/actors/client/ActorProxyImplTest.java create mode 100644 sdk-actors/src/test/java/io/dapr/actors/client/DaprClientStub.java create mode 100644 sdk-actors/src/test/java/io/dapr/actors/client/DaprHttpClientTest.java create mode 100644 sdk-actors/src/test/java/io/dapr/actors/it/BaseIT.java create mode 100644 sdk-actors/src/test/java/io/dapr/actors/it/DaprIntegrationTestingRunner.java create mode 100644 sdk-actors/src/test/java/io/dapr/actors/it/actors/ActivationDeactiviationIT.java create mode 100644 sdk-actors/src/test/java/io/dapr/actors/it/services/springboot/ActorService.java create mode 100644 sdk-actors/src/test/java/io/dapr/actors/it/services/springboot/DaprApplication.java create mode 100644 sdk-actors/src/test/java/io/dapr/actors/it/services/springboot/DaprController.java create mode 100644 sdk-actors/src/test/java/io/dapr/actors/it/services/springboot/DemoActor.java create mode 100644 sdk-actors/src/test/java/io/dapr/actors/it/services/springboot/DemoActorImpl.java create mode 100644 sdk-actors/src/test/java/io/dapr/actors/it/services/springboot/EmptyService.java create mode 100644 sdk-actors/src/test/java/io/dapr/actors/runtime/ActorCustomSerializerTest.java create mode 100644 sdk-actors/src/test/java/io/dapr/actors/runtime/ActorManagerTest.java create mode 100644 sdk-actors/src/test/java/io/dapr/actors/runtime/ActorMethodInfoMapTest.java create mode 100644 sdk-actors/src/test/java/io/dapr/actors/runtime/ActorNoStateTest.java create mode 100644 sdk-actors/src/test/java/io/dapr/actors/runtime/ActorReminderParamsTest.java create mode 100644 sdk-actors/src/test/java/io/dapr/actors/runtime/ActorRuntimeTest.java create mode 100644 sdk-actors/src/test/java/io/dapr/actors/runtime/ActorStatefulTest.java create mode 100644 sdk-actors/src/test/java/io/dapr/actors/runtime/ActorTimerTest.java create mode 100644 sdk-actors/src/test/java/io/dapr/actors/runtime/ActorTypeInformationTest.java create mode 100644 sdk-actors/src/test/java/io/dapr/actors/runtime/DaprHttpClientTest.java create mode 100644 sdk-actors/src/test/java/io/dapr/actors/runtime/DaprInMemoryStateProvider.java create mode 100644 sdk-actors/src/test/java/io/dapr/actors/runtime/DaprStateAsyncProviderTest.java create mode 100644 sdk-actors/src/test/java/io/dapr/actors/runtime/DefaultActorFactoryTest.java create mode 100644 sdk-actors/src/test/java/io/dapr/actors/runtime/DerivedActorTest.java create mode 100644 sdk-actors/src/test/java/io/dapr/actors/runtime/JavaSerializer.java create mode 100644 sdk-actors/src/test/java/io/dapr/actors/runtime/ThrowFromPreAndPostActorMethodsTest.java create mode 100644 sdk-actors/src/test/java/io/dapr/client/DaprHttpProxy.java create mode 100644 sdk-autogen/pom.xml create mode 100644 sdk-autogen/src/main/java/.keepme create mode 100644 sdk-autogen/src/test/java/.keepme delete mode 100644 sdk/src/main/java/io/dapr/DaprClientGrpc.java delete mode 100644 sdk/src/main/java/io/dapr/DaprClientProtos.java delete mode 100644 sdk/src/main/java/io/dapr/DaprGrpc.java delete mode 100644 sdk/src/main/java/io/dapr/DaprProtos.java create mode 100644 sdk/src/main/java/io/dapr/client/DaprClient.java create mode 100644 sdk/src/main/java/io/dapr/client/DaprClientBuilder.java create mode 100644 sdk/src/main/java/io/dapr/client/DaprClientGrpcAdapter.java create mode 100644 sdk/src/main/java/io/dapr/client/DaprClientHttpAdapter.java create mode 100644 sdk/src/main/java/io/dapr/client/DaprHttp.java create mode 100644 sdk/src/main/java/io/dapr/client/DaprHttpBuilder.java create mode 100644 sdk/src/main/java/io/dapr/client/ObjectSerializer.java create mode 100644 sdk/src/main/java/io/dapr/client/domain/CloudEvent.java create mode 100644 sdk/src/main/java/io/dapr/client/domain/State.java create mode 100644 sdk/src/main/java/io/dapr/client/domain/StateOptions.java create mode 100644 sdk/src/main/java/io/dapr/client/domain/Verb.java create mode 100644 sdk/src/main/java/io/dapr/exceptions/DaprError.java create mode 100644 sdk/src/main/java/io/dapr/exceptions/DaprException.java create mode 100644 sdk/src/main/java/io/dapr/serializer/DaprObjectSerializer.java create mode 100644 sdk/src/main/java/io/dapr/serializer/DefaultObjectSerializer.java create mode 100644 sdk/src/main/java/io/dapr/serializer/StringContentType.java create mode 100644 sdk/src/main/java/io/dapr/utils/Constants.java create mode 100644 sdk/src/main/java/io/dapr/utils/DurationUtils.java create mode 100644 sdk/src/test/java/io/dapr/client/DaprClientBuilderTest.java create mode 100644 sdk/src/test/java/io/dapr/client/DaprClientGrpcAdapterTest.java create mode 100644 sdk/src/test/java/io/dapr/client/DaprClientHttpAdapterTest.java create mode 100644 sdk/src/test/java/io/dapr/client/DaprClientTestBuilder.java create mode 100644 sdk/src/test/java/io/dapr/client/DaprHttpBuilderTest.java create mode 100644 sdk/src/test/java/io/dapr/client/DaprHttpStub.java create mode 100644 sdk/src/test/java/io/dapr/client/DaprHttpTest.java create mode 100644 sdk/src/test/java/io/dapr/it/BaseIT.java create mode 100644 sdk/src/test/java/io/dapr/it/DaprIntegrationTestingRunner.java create mode 100644 sdk/src/test/java/io/dapr/it/binding/http/OutputBindingExample.java create mode 100644 sdk/src/test/java/io/dapr/it/deploy/local-test-kafka.yml create mode 100644 sdk/src/test/java/io/dapr/it/services/EmptyService.java create mode 100644 sdk/src/test/java/io/dapr/it/services/HelloWorldGrpcStateService.java create mode 100644 sdk/src/test/java/io/dapr/it/services/InputBindingController.java create mode 100644 sdk/src/test/java/io/dapr/it/services/InputBindingExample.java create mode 100644 sdk/src/test/java/io/dapr/it/state/GRPCStateClientIT.java create mode 100644 sdk/src/test/java/io/dapr/it/state/HelloWorldClientIT.java create mode 100644 sdk/src/test/java/io/dapr/it/state/HttpStateClientIT.java create mode 100644 sdk/src/test/java/io/dapr/runtime/Dapr.java create mode 100644 sdk/src/test/java/io/dapr/runtime/DaprRuntime.java create mode 100644 sdk/src/test/java/io/dapr/runtime/DaprRuntimeTest.java create mode 100644 sdk/src/test/java/io/dapr/runtime/MethodListener.java create mode 100644 sdk/src/test/java/io/dapr/runtime/TopicListener.java create mode 100644 sdk/src/test/java/io/dapr/serializer/DefaultObjectSerializerTest.java create mode 100644 sdk/src/test/java/io/dapr/utils/DurationUtilsTest.java create mode 100644 sdk/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker create mode 100644 settings.xml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7bdfd80b2..2208f3804 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,19 +21,27 @@ jobs: env: JDK_VER: 13.0.x DAPR_RUNTIME_VER: 0.3.0 + OSSRH_USER_TOKEN: ${{ secrets.OSSRH_USER_TOKEN }} + OSSRH_PWD_TOKEN: ${{ secrets.OSSRH_PWD_TOKEN }} + GPG_KEY: ${{ secrets.GPG_KEY }} + GPG_PWD: ${{ secrets.GPG_PWD }} steps: - uses: actions/checkout@v1 - - name: Set up ${{ env.JDK_VER }} + - name: Set up OpenJDK ${{ env.JDK_VER }} uses: actions/setup-java@v1 with: java-version: ${{ env.JDK_VER }} - name: Set up Dapr CLI run: wget -q https://raw.githubusercontent.com/dapr/cli/master/install/install.sh -O - | /bin/bash - - name: Initialize Dapr runtime as a standalone mode + - name: Initialize Dapr runtime ${{ env.DAPR_RUNTIME_VER }} run: | sudo dapr init --runtime-version ${{ env.DAPR_RUNTIME_VER }} echo "Showing dapr version..." dapr --version + - name: Install Local kafka using docker-compose + run: | + docker-compose -f ./sdk/src/test/java/io/dapr/it/deploy/local-test-kafka.yml up -d + docker ps - name: Clean up files run: mvn clean - name: Build sdk @@ -42,3 +50,32 @@ jobs: run: mvn test - name: Integration-test run: mvn integration-test + - name: Upload test report for sdk + uses: actions/upload-artifact@master + with: + name: report-dapr-java-sdk + path: sdk/target/jacoco-report/ + - name: Upload test report for sdk-actors + uses: actions/upload-artifact@master + with: + name: report-dapr-java-sdk-actors + path: sdk-actors/target/jacoco-report/ + - name: Packaging jars + run: mvn package + - name: Get pom parent version + run: | + PARENT_VERSION=$(mvn -q -Dexec.executable=echo -Dexec.args='${project.version}' --non-recursive exec:exec) + echo "##[set-env name=PARENT_VERSION;]$PARENT_VERSION" + - name: Is SNAPSHOT release ? + if: contains(github.ref, 'master') && contains(env.PARENT_VERSION, '-SNAPSHOT') + run: echo "##[set-env name=DEPLOY_OSSRH;]true" + - name: Is Release or RC version ? + if: startswith(github.ref, 'refs/tags/v') && !contains(env.PARENT_VERSION, '-SNAPSHOT') + run: echo "##[set-env name=DEPLOY_OSSRH;]true" + - name: Publish to ossrh + if: env.DEPLOY_OSSRH == 'true' + run: | + echo ${{ secrets.GPG_PRIVATE_KEY }} | base64 -d > private-key.gpg + export GPG_TTY=$(tty) + gpg --batch --import private-key.gpg + mvn -V -B -Dgpg.skip=false -s settings.xml deploy diff --git a/.gitignore b/.gitignore index e4b7f0fd4..21ff40244 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ # Log file *.log +/syslog.txt # BlueJ files *.ctxt diff --git a/README.md b/README.md index 319491294..f6bf9cf37 100644 --- a/README.md +++ b/README.md @@ -48,27 +48,28 @@ mvn versions:commit ### Debug Java application or Dapr's Java SDK -If you have a Java application or an issue on this SDK that needs to be debugged, follow the steps below: +If you have a Java application or an issue on this SDK that needs to be debugged, run Dapr using a dummy command and start the application from your IDE (IntelliJ, for example). +For Linux and MacOS: -Install [Pen Load Balancer](https://sourceforge.net/projects/penloadbalancer/) or your preferred load balancer utility: ```sh -sudo apt-get install pen -``` -Note: Pen is also available on Windows in the link above. For MacOS, you will need to [build it from source code](https://github.com/UlricE/pen/wiki/Building-Pen-from-Git). - -Then run Dapr with the load balancer process listening on port 3001 and forwarding to port 3000. For Pen Load Balancer, it would be: -```sh -dapr run --app-id testapp --app-port 3001 --port 3500 -- pen -b 99999999 -f localhost:3001 localhost:3000 +dapr run --app-id testapp --app-port 3000 --port 3500 --grpc-port 5001-- cat ``` -The command below will start a load balancer listening on port `3001` that forwards connections to port `3000`, while Dapr's app identifier is `testapp` and listening port is `3500`. If you try to make a HTTP call to any URL on `localhost:3001`, it will fail until you have an application listening on `localhost:3000`. +For Windows: +```sh +dapr run --app-id testapp --app-port 3000 --port 3500 --grpc-port 5001 -- waitfor FOREVER +``` + +When running your Java application from IDE, make sure the following environment variables are set, so the Java SDK knows how to connect to Dapr's sidecar: +``` +DAPR_HTTP_PORT=3500 +DAPR_GRPC_PORT=5001 +``` Now you can go to your IDE (like IntelliJ, for example) and debug your Java application, using port `3500` to call Dapr while also listening to port `3000` to expose Dapr's callback endpoint. Calls to Dapr's APIs on `http://localhost:3500/*` should work now and trigger breakpoints in your code. -**If your application needs to suscribe to topics or register Actors in Dapr, for example, then start debugging your app first and run dapr with load balancer last.** - -Reminder: for Dapr, your application is listening to port `3001` and not `3000` since it can only see the load balancer's port. +**If your application needs to suscribe to topics or register Actors in Dapr, for example, then start debugging your app first and run dapr with dummy command last.** **If using Visual Studio Code, also consider [this solution as well](https://github.com/dapr/docs/tree/master/howto/vscode-debugging-daprd).** diff --git a/checkstyle.xml b/checkstyle.xml new file mode 100644 index 000000000..642023427 --- /dev/null +++ b/checkstyle.xml @@ -0,0 +1,268 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/components/kafka_bindings.yaml b/examples/components/kafka_bindings.yaml new file mode 100644 index 000000000..280e241e1 --- /dev/null +++ b/examples/components/kafka_bindings.yaml @@ -0,0 +1,20 @@ +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: sample123 +spec: + type: bindings.kafka + metadata: + # Kafka broker connection setting + - name: brokers + value: localhost:9092 + # consumer configuration: topic and consumer group + - name: topics + value: sample + - name: consumerGroup + value: group1 + # publisher configuration: topic + - name: publishTopic + value: sample + - name: authRequired + value: "false" diff --git a/examples/pom.xml b/examples/pom.xml index 0c48a705a..6fc3a8c8a 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -7,17 +7,22 @@ io.dapr dapr-sdk-parent - 0.3.0-alpha + 0.2.0-SNAPSHOT + io.dapr dapr-sdk-examples jar - 0.3.0-alpha + 0.2.0-SNAPSHOT dapr-sdk-examples ${project.build.directory}/generated-sources - ${project.parent.basedir}/proto + ${project.basedir}/proto + 11 + ${java.version} + ${java.version} + true @@ -34,18 +39,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 @@ -58,10 +72,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-actors + ${project.version} + io.dapr dapr-sdk - 0.3.0-alpha + ${project.version} @@ -83,7 +112,7 @@ direct true - ${protobuf.input.directory}/examples + ${protobuf.input.directory} @@ -100,6 +129,14 @@ + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 11 + + 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/DemoActor.java b/examples/src/main/java/io/dapr/examples/actors/http/DemoActor.java new file mode 100644 index 000000000..6cf19f784 --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/actors/http/DemoActor.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.examples.actors.http; + +import reactor.core.publisher.Mono; + +/** + * Example of implementation of an Actor. + */ +public interface DemoActor { + + void registerReminder(); + + String say(String something); + + void clock(String message); + + Mono incrementAndGet(int delta); +} diff --git a/examples/src/main/java/io/dapr/examples/actors/http/DemoActorClient.java b/examples/src/main/java/io/dapr/examples/actors/http/DemoActorClient.java new file mode 100644 index 000000000..a3f370c57 --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/actors/http/DemoActorClient.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.examples.actors.http; + +import io.dapr.actors.ActorId; +import io.dapr.actors.client.ActorProxy; +import io.dapr.actors.client.ActorProxyBuilder; +import io.dapr.serializer.DefaultObjectSerializer; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +/** + * Client for Actor runtime. + * 1. Build and install jars: + * mvn clean install + * 2. Run the client: + * dapr run --app-id demoactorclient --port 3006 -- mvn exec:java -pl=examples -Dexec.mainClass=io.dapr.examples.actors.http.DemoActorClient + */ +public class DemoActorClient { + + private static final int NUM_ACTORS = 3; + + private static final int NUM_MESSAGES_PER_ACTOR = 10; + + private static final String METHOD_NAME = "say"; + + private static final ExecutorService POOL = Executors.newFixedThreadPool(NUM_ACTORS); + + public static void main(String[] args) throws Exception { + ActorProxyBuilder builder = new ActorProxyBuilder("DemoActor", new DefaultObjectSerializer()); + + List> futures = new ArrayList<>(NUM_ACTORS); + + for (int i = 0; i < NUM_ACTORS; i++) { + ActorProxy actor = builder.build(ActorId.createRandom()); + futures.add(callActorNTimes(actor)); + } + + futures.forEach(CompletableFuture::join); + POOL.shutdown(); + POOL.awaitTermination(1, TimeUnit.MINUTES); + + System.out.println("Done."); + } + + private static final CompletableFuture callActorNTimes(ActorProxy actor) { + return CompletableFuture.runAsync(() -> { + actor.invokeActorMethod("registerReminder").block(); + for (int i = 0; i < NUM_MESSAGES_PER_ACTOR; i++) { + actor.invokeActorMethod("incrementAndGet", 1).block(); + String result = actor.invokeActorMethod(METHOD_NAME, + String.format("Actor %s said message #%d", actor.getActorId().toString(), i), String.class).block(); + System.out.println(String.format("Actor %s got a reply: %s", actor.getActorId().toString(), result)); + try { + Thread.sleep((long)(1000 * Math.random())); + } catch (InterruptedException e) { + e.printStackTrace(); + Thread.currentThread().interrupt(); + return; + } + } + + System.out.println( + "Messages sent: " + actor.invokeActorMethod("incrementAndGet", 0, int.class).block()); + }, POOL); + } +} 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 new file mode 100644 index 000000000..870ea9b40 --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/actors/http/DemoActorImpl.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.examples.actors.http; + +import io.dapr.actors.ActorId; +import io.dapr.actors.runtime.AbstractActor; +import io.dapr.actors.runtime.ActorRuntimeContext; +import io.dapr.actors.runtime.ActorType; +import io.dapr.actors.runtime.Remindable; +import reactor.core.publisher.Mono; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.time.Duration; +import java.util.Calendar; +import java.util.TimeZone; + +/** + * Implementation of the DemoActor for the server side. + */ +@ActorType(name = "DemoActor") +public class DemoActorImpl extends AbstractActor implements DemoActor, Remindable { + + /** + * Format to output date and time. + */ + private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + + public DemoActorImpl(ActorRuntimeContext runtimeContext, ActorId id) { + super(runtimeContext, id); + + super.registerActorTimer( + null, + "clock", + "ping!", + Duration.ofSeconds(2), + Duration.ofSeconds(1)).block(); + } + + @Override + public void registerReminder() { + super.registerReminder( + "myremind", + (int)(Integer.MAX_VALUE * Math.random()), + Duration.ofSeconds(5), + Duration.ofSeconds(2)).block(); + } + + @Override + public String say(String something) { + Calendar utcNow = Calendar.getInstance(TimeZone.getTimeZone("GMT")); + String utcNowAsString = DATE_FORMAT.format(utcNow.getTime()); + + // Handles the request by printing message. + System.out.println("Server say method for actor " + + super.getId() + ": " + + (something == null ? "" : something + " @ " + utcNowAsString)); + + super.getActorStateManager().set("lastmessage", something).block(); + + // Now respond with current timestamp. + return utcNowAsString; + } + + @Override + public Mono incrementAndGet(int delta) { + return super.getActorStateManager().contains("counter") + .flatMap(exists -> exists ? super.getActorStateManager().get("counter", int.class) : Mono.just(0)) + .map(c -> c + delta) + .flatMap(c -> super.getActorStateManager().set("counter", c).thenReturn(c)); + } + + @Override + public void clock(String message) { + Calendar utcNow = Calendar.getInstance(TimeZone.getTimeZone("GMT")); + String utcNowAsString = DATE_FORMAT.format(utcNow.getTime()); + + // Handles the request by printing message. + System.out.println("Server timer for actor " + + super.getId() + ": " + + (message == null ? "" : message + " @ " + utcNowAsString)); + } + + @Override + public Class getStateType() { + return Integer.class; + } + + @Override + public Mono receiveReminder(String reminderName, Integer state, Duration dueTime, Duration period) { + Calendar utcNow = Calendar.getInstance(TimeZone.getTimeZone("GMT")); + String utcNowAsString = DATE_FORMAT.format(utcNow.getTime()); + + // Handles the request by printing message. + System.out.println(String.format( + "Server reminded actor %s of: %s for %d @ %s", + this.getId(), reminderName, state, utcNowAsString)); + return Mono.empty(); + } +} 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 new file mode 100644 index 000000000..031be77fc --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/actors/http/DemoActorService.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.examples.actors.http; + +import io.dapr.actors.runtime.ActorRuntime; +import io.dapr.serializer.DefaultObjectSerializer; +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.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * Service for Actor runtime. + * 1. Build and install jars: + * mvn clean install + * 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 { + + public static void main(String[] args) throws Exception { + Options options = new Options(); + 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")); + + // Register the Actor class. + ActorRuntime.getInstance().registerActor( + DemoActorImpl.class, new DefaultObjectSerializer(), new DefaultObjectSerializer()); + + // 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/examples/src/main/java/io/dapr/examples/bindings/InputBindingController.java b/examples/src/main/java/io/dapr/examples/bindings/InputBindingController.java new file mode 100644 index 000000000..564fdab06 --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/bindings/InputBindingController.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.examples.bindings; + +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Mono; + +/** + * SpringBoot Controller to handle input binding. + */ +@RestController +public class InputBindingController { + + @PostMapping(path = "/bindingSample") + public Mono handleInputBinding(@RequestBody(required = false) byte[] body) { + return Mono.fromRunnable(() -> + System.out.println("Received message through binding: " + (body == null ? "" : new String(body)))); + } + +} diff --git a/examples/src/main/java/io/dapr/examples/bindings/InputBindingExample.java b/examples/src/main/java/io/dapr/examples/bindings/InputBindingExample.java new file mode 100644 index 000000000..594aeee8e --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/bindings/InputBindingExample.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.examples.bindings; + +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; + +/** + * Service for input binding example. + * 1. From your repo root, build and install jars: + * mvn clean install + * 2. cd to [repo-root]/examples + * 3. Run : + * dapr run --app-id inputbinding --app-port 3000 --port 3005 -- mvn exec:java -D exec.mainClass=io.dapr.examples.bindings.InputBindingExample -D exec.args="-p 3000" + */ +public class InputBindingExample { + + public static void main(String[] args) throws Exception { + Options options = new Options(); + 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")); + + // Start Dapr's callback endpoint. + DaprApplication.start(port); + } +} \ No newline at end of file diff --git a/examples/src/main/java/io/dapr/examples/bindings/OutputBindingExample.java b/examples/src/main/java/io/dapr/examples/bindings/OutputBindingExample.java new file mode 100644 index 000000000..aefd1dea3 --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/bindings/OutputBindingExample.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.examples.bindings; + +import io.dapr.client.DaprClient; +import io.dapr.client.DaprClientBuilder; +import io.dapr.serializer.DefaultObjectSerializer; + +/** + * Service for output binding example. + * 1. From your repo root, build and install jars: + * mvn clean install + * 2. cd to [repo-root]/examples + * 3. Run the program: + * dapr run --app-id outputbinding --port 3006 -- mvn exec:java -D exec.mainClass=io.dapr.examples.bindings.OutputBindingExample + */ +public class OutputBindingExample { + + public static class MyClass { + public MyClass(){} + public String message; + } + + public static void main(String[] args) { + DaprClient client = new DaprClientBuilder(new DefaultObjectSerializer(), new DefaultObjectSerializer()).build(); + + final String BINDING_NAME = "bindingSample"; + + // This is an example of sending data in a user-defined object. The input binding will receive: + // {"message":"hello"} + MyClass myClass = new MyClass(); + myClass.message = "hello"; + + System.out.println("sending a class with message: " + myClass.message); + client.invokeBinding(BINDING_NAME, myClass).block(); + + // This is an example of sending a plain string. The input binding will receive: + // "cat" + final String m = "cat"; + System.out.println("sending a plain string: " + m); + client.invokeBinding(BINDING_NAME, m).block(); + + try { + Thread.sleep((long) (10000 * Math.random())); + } catch (InterruptedException e) { + e.printStackTrace(); + Thread.currentThread().interrupt(); + return; + } + + System.out.println("Done."); + } +} \ No newline at end of file diff --git a/examples/src/main/java/io/dapr/examples/bindings/README.md b/examples/src/main/java/io/dapr/examples/bindings/README.md new file mode 100644 index 000000000..a69ea1488 --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/bindings/README.md @@ -0,0 +1,142 @@ +# Dapr Bindings Sample + +In this sample, we'll create two java applications: an output binding application and an input binding application, using Dapr Java SDK. +This sample includes two applications: + +* InputBindingExample (Initializes the Dapr Spring boot application client) +* OutputBindingExample (pushes the event message) + +Visit [this](https://github.com/dapr/docs/tree/master/concepts/bindings) link for more information about Dapr and bindings concepts. + +## Binding sample using the Java-SDK + +In this example, the component used is Kafka but others are also available. + +Visit [this](https://github.com/dapr/components-contrib/tree/master/bindings) link for more information about binding implementations. + + +## Pre-requisites + +* [Dapr and Dapr Cli](https://github.com/dapr/docs/blob/master/getting-started/environment-setup.md#environment-setup). +* Java JDK 11 (or greater): [Oracle JDK](https://www.oracle.com/technetwork/java/javase/downloads/index.html#JDK11) or [OpenJDK](https://jdk.java.net/13/). +* [Apache Maven](https://maven.apache.org/install.html) version 3.x. + +### Checking out the code + +Clone this repository: + +```sh +git clone https://github.com/dapr/java-sdk.git +cd java-sdk +``` + +Then build the Maven project: + +```sh +# make sure you are in the `java-sdk` directory. +mvn install +``` +### Setting Kafka locally + +Before getting into the application code, follow these steps in order to setup a local instance of Kafka. This is needed for the local instances. Steps are: + +1. navigate to the [repo-root]/examples/src/main/java/io/dapr/examples/bindings +2. Run `docker-compose -f ./docker-compose-single-kafka.yml up -d` to run the container locally +3. Run `docker ps` to see the container running locally: + +```bash +342d3522ca14 kafka-docker_kafka "start-kafka.sh" 14 hours ago Up About +a minute 0.0.0.0:9092->9092/tcp kafka-docker_kafka_1 +0cd69dbe5e65 wurstmeister/zookeeper "/bin/sh -c '/usr/sb…" 8 days ago Up About +a minute 22/tcp, 2888/tcp, 3888/tcp, 0.0.0.0:2181->2181/tcp kafka-docker_zookeeper_1 +``` +Click [here](https://github.com/wurstmeister/kafka-docker) for more information about the kafka broker server. + +### Running the Input binding sample + +The input binding sample uses the Spring Boot´s DaprApplication class for initializing the `InputBindingController`. In `InputBindingExample.java` file, you will find the `InputBindingExample` class and the `main` method. See the code snippet below: + +```java +public class InputBindingExample { + + public static void main(String[] args) throws Exception { + ///.. + // If port string is not valid, it will throw an exception. + int port = Integer.parseInt(cmd.getOptionValue("port")); + + // Start Dapr's callback endpoint. + DaprApplication.start(port); + } +///... +} +``` + +`DaprApplication.start()` Method will run an Spring Boot application that registers the `InputBindingController`, which exposes the actual handling of the event message as a POST request. The Dapr's sidecar is the one that performs the actual call to this controller, based on the binding features and the output binding action. + +```java +@RestController +public class InputBindingController { + + @PostMapping(path = "/bindingSample") + public Mono handleInputBinding(@RequestBody(required = false) byte[] body) { + return Mono.fromRunnable(() -> + System.out.println("Received message through binding: " + (body == null ? "" : new String(body)))); + } + +} +``` + + Execute the follow script in order to run the Input Binding example: +```sh +cd to [repo-root]/examples +dapr run --app-id inputbinding --app-port 3000 --port 3005 -- mvn exec:java -D exec.mainClass=io.dapr.examples.bindings.InputBindingExample -D exec.args="-p 3000" +``` + +### Running the Output binding sample + +The output binding application is a simple java class with a main method that uses the Dapr Client to invoke binding. + +In the `OutputBindingExample.java` file, you will find the `OutputBindingExample` class, containing the main method. The main method declares a Dapr Client using the `DaprClientBuilder` class. Notice that this builder gets two serializer implementations in the constructor: One is for Dapr's sent and recieved objects, and second is for objects to be persisted. The client publishes events using `invokeBinding` method. See the code snippet below: +```java +public class OutputBindingExample { +///... + public static void main(String[] args) throws Exception { + DaprClient client = new DaprClientBuilder(new DefaultObjectSerializer(), new DefaultObjectSerializer()).build(); + final String BINDING_NAME = "bindingSample"; + ///... + MyClass myClass = new MyClass(); + myClass.message = "hello"; + + System.out.println("sending an object instance with message: " + myClass.message); + client.invokeBinding(BINDING_NAME, myClass); //Binding a data object + ///.. + final String m = "cat"; + System.out.println("sending a plain string: " + m); + client.invokeBinding(BINDING_NAME, m); //Binding a plain string text + } +///... +} +``` + +This example binds two events: A user-defined data object (using the `myClass` object as parameter) and a simple string using the same `invokeBinding` method. + +Use the follow command to execute the Output Binding example: + +```sh +cd to [repo-root]/examples +dapr run --app-id outputbinding --port 3006 -- mvn exec:java -D exec.mainClass=io.dapr.examples.bindings.OutputBindingExample +``` + +Once running, the OutputBindingExample should print the output as follows: + +![publisheroutput](../../../../../resources/img/outputbinding.png) + +Events have been sent. + +Once running, the InputBindingExample should print the output as follows: + +![publisheroutput](../../../../../resources/img/inputbinding.png) + +Events have been retrieved from the binding. + +For more details on Dapr Spring Boot integration, please refer to [Dapr Spring Boot](../../springboot/DaprApplication.java) Application implementation. \ No newline at end of file diff --git a/examples/src/main/java/io/dapr/examples/bindings/docker-compose-single-kafka.yml b/examples/src/main/java/io/dapr/examples/bindings/docker-compose-single-kafka.yml new file mode 100644 index 000000000..16f9e4801 --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/bindings/docker-compose-single-kafka.yml @@ -0,0 +1,14 @@ +version: '2' +services: + zookeeper: + image: wurstmeister/zookeeper:latest + ports: + - "2181:2181" + kafka: + image: wurstmeister/kafka:latest + ports: + - "9092:9092" + environment: + KAFKA_ADVERTISED_HOST_NAME: 127.0.0.1 + KAFKA_CREATE_TOPICS: "sample:1:1" + KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 \ No newline at end of file diff --git a/examples/src/main/java/io/dapr/examples/invoke/grpc/HelloWorldClient.java b/examples/src/main/java/io/dapr/examples/invoke/grpc/HelloWorldClient.java index 45afaaa52..0115d2daf 100644 --- a/examples/src/main/java/io/dapr/examples/invoke/grpc/HelloWorldClient.java +++ b/examples/src/main/java/io/dapr/examples/invoke/grpc/HelloWorldClient.java @@ -23,105 +23,108 @@ import static io.dapr.examples.DaprExamplesProtos.SayResponse; /** * 1. Build and install jars: - * mvn clean install + * mvn clean install * 2. Send messages to the server: - * dapr run --protocol grpc --grpc-port 50001 -- mvn exec:java -pl=examples -Dexec.mainClass=io.dapr.examples.invoke.grpc.HelloWorldClient -Dexec.args="-p 50001 'message one' 'message two'" + * dapr run --protocol grpc --grpc-port 50001 -- mvn exec:java -pl=examples -Dexec.mainClass=io.dapr.examples.invoke.grpc.HelloWorldClient -Dexec.args="-p 50001 'message one' 'message two'" */ public class HelloWorldClient { + /** + * Client mode: class representing a client-side logic for calling HelloWorld over Dapr. + */ + private static class GrpcHelloWorldDaprClient { + /** - * Client mode: class representing a client-side logic for calling HelloWorld over Dapr. + * Client communication channel: host, port and tls(on/off) */ - private static class GrpcHelloWorldDaprClient { + private final ManagedChannel channel; - /** - * Client communication channel: host, port and tls(on/off) - */ - private final ManagedChannel channel; - - /** - * Calls will be done asynchronously. - */ - private final DaprGrpc.DaprFutureStub client; - - /** - * Creates a Grpc client for the DaprGrpc service. - * @param host host for the remote service endpoint - * @param port port for the remote service endpoint - */ - public GrpcHelloWorldDaprClient(String host, int port) { - this(ManagedChannelBuilder - .forAddress("localhost", port) - .usePlaintext() // SSL/TLS is default, we turn it off just because this is a sample and not prod. - .build()); - } - - /** - * Helper constructor to build client from channel. - * @param channel - */ - private GrpcHelloWorldDaprClient(ManagedChannel channel) { - this.channel = channel; - this.client = DaprGrpc.newFutureStub(channel); - } - - /** - * Client mode: sends messages, one per second. - * @param messages - */ - private void sendMessages(String... messages) throws ExecutionException, InterruptedException, InvalidProtocolBufferException { - List> futureResponses = new ArrayList<>(); - for (String message : messages) - { - SayRequest request = SayRequest - .newBuilder() - .setMessage(message) - .build(); - - // Now, wrap the request with Dapr's envelope. - InvokeServiceEnvelope requestEnvelope = InvokeServiceEnvelope - .newBuilder() - .setId("hellogrpc") // Service's identifier. - .setData(Any.pack(request)) - .setMethod("say") // The service's method to be invoked by Dapr. - .build(); - - futureResponses.add(client.invokeService(requestEnvelope)); - System.out.println("Client: sent => " + message); - Thread.sleep(TimeUnit.SECONDS.toMillis(10)); - } - - for (ListenableFuture future : futureResponses) { - Any data = future.get().getData(); // Blocks waiting for response. - // IMPORTANT: do not use Any.unpack(), use Type.ParseFrom() instead. - SayResponse response = SayResponse.parseFrom(data.getValue()); - System.out.println("Client: got response => " + response.getTimestamp()); - } - } - - /** - * Client mode: gracefully shutdown client within 1 min, otherwise force it. - * @throws InterruptedException Propagated interrupted exception. - */ - private void shutdown() throws InterruptedException { - this.channel.shutdown().awaitTermination(1, TimeUnit.MINUTES); - System.out.println("Client: Bye."); - } + /** + * Calls will be done asynchronously. + */ + private final DaprGrpc.DaprFutureStub client; + /** + * Creates a Grpc client for the DaprGrpc service. + * + * @param host host for the remote service endpoint + * @param port port for the remote service endpoint + */ + public GrpcHelloWorldDaprClient(String host, int port) { + this(ManagedChannelBuilder + .forAddress("localhost", port) + .usePlaintext() // SSL/TLS is default, we turn it off just because this is a sample and not prod. + .build()); } - public static void main(String[] args) throws Exception { - Options options = new Options(); - options.addRequiredOption("p", "port", true, "Port to listen or send event 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")); - - GrpcHelloWorldDaprClient helloWorldClient = new GrpcHelloWorldDaprClient("localhost", port); - helloWorldClient.sendMessages(cmd.getArgs()); - helloWorldClient.shutdown(); + /** + * Helper constructor to build client from channel. + * + * @param channel + */ + private GrpcHelloWorldDaprClient(ManagedChannel channel) { + this.channel = channel; + this.client = DaprGrpc.newFutureStub(channel); } + + /** + * Client mode: sends messages, one per second. + * + * @param messages + */ + private void sendMessages(String... messages) throws ExecutionException, InterruptedException, InvalidProtocolBufferException { + List> futureResponses = new ArrayList<>(); + for (String message : messages) { + SayRequest request = SayRequest + .newBuilder() + .setMessage(message) + .build(); + + // Now, wrap the request with Dapr's envelope. + InvokeServiceEnvelope requestEnvelope = InvokeServiceEnvelope + .newBuilder() + .setId("hellogrpc") // Service's identifier. + .setData(Any.pack(request)) + .setMethod("say") // The service's method to be invoked by Dapr. + .build(); + + futureResponses.add(client.invokeService(requestEnvelope)); + System.out.println("Client: sent => " + message); + Thread.sleep(TimeUnit.SECONDS.toMillis(10)); + } + + for (ListenableFuture future : futureResponses) { + Any data = future.get().getData(); // Blocks waiting for response. + // IMPORTANT: do not use Any.unpack(), use Type.ParseFrom() instead. + SayResponse response = SayResponse.parseFrom(data.getValue()); + System.out.println("Client: got response => " + response.getTimestamp()); + } + } + + /** + * Client mode: gracefully shutdown client within 1 min, otherwise force it. + * + * @throws InterruptedException Propagated interrupted exception. + */ + private void shutdown() throws InterruptedException { + this.channel.shutdown().awaitTermination(1, TimeUnit.MINUTES); + System.out.println("Client: Bye."); + } + + } + + public static void main(String[] args) throws Exception { + Options options = new Options(); + options.addRequiredOption("p", "port", true, "Port to listen or send event 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")); + + GrpcHelloWorldDaprClient helloWorldClient = new GrpcHelloWorldDaprClient("localhost", port); + helloWorldClient.sendMessages(cmd.getArgs()); + helloWorldClient.shutdown(); + } } diff --git a/examples/src/main/java/io/dapr/examples/invoke/grpc/HelloWorldService.java b/examples/src/main/java/io/dapr/examples/invoke/grpc/HelloWorldService.java index 9e61368a7..f674c6430 100644 --- a/examples/src/main/java/io/dapr/examples/invoke/grpc/HelloWorldService.java +++ b/examples/src/main/java/io/dapr/examples/invoke/grpc/HelloWorldService.java @@ -25,116 +25,116 @@ import static io.dapr.examples.DaprExamplesProtos.SayResponse; * 1. Build and install jars: * mvn clean install * 2. Run in server mode: - * dapr run --app-id hellogrpc --app-port 5000 --protocol grpc -- mvn exec:java -pl=examples -Dexec.mainClass=io.dapr.examples.invoke.grpc.HelloWorldService -Dexec.args="-p 5000" + * dapr run --app-id hellogrpc --app-port 5000 --protocol grpc -- mvn exec:java -pl=examples -Dexec.mainClass=io.dapr.examples.invoke.grpc.HelloWorldService -Dexec.args="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5009" */ public class HelloWorldService { + /** + * Server mode: class that encapsulates all server-side logic for Grpc. + */ + private static class GrpcHelloWorldDaprService extends DaprClientGrpc.DaprClientImplBase { + /** - * Server mode: class that encapsulates all server-side logic for Grpc. + * Format to output date and time. */ - private static class GrpcHelloWorldDaprService extends DaprClientGrpc.DaprClientImplBase { + private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); - /** - * Format to output date and time. - */ - private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + /** + * Server mode: Grpc server. + */ + private Server server; - /** - * Server mode: Grpc server. - */ - private Server server; + /** + * Server mode: starts listening on given port. + * + * @param port Port to listen on. + * @throws IOException Errors while trying to start service. + */ + private void start(int port) throws IOException { + this.server = ServerBuilder + .forPort(port) + .addService(this) + .build() + .start(); + System.out.printf("Server: started listening on port %d\n", port); - /** - * Server mode: starts listening on given port. - * - * @param port Port to listen on. - * @throws IOException Errors while trying to start service. - */ - private void start(int port) throws IOException { - this.server = ServerBuilder - .forPort(port) - .addService(this) - .build() - .start(); - System.out.printf("Server: started listening on port %d\n", port); + // Now we handle ctrl+c (or any other JVM shutdown) + Runtime.getRuntime().addShutdownHook(new Thread() { - // 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 ..."); - GrpcHelloWorldDaprService.this.server.shutdown(); - System.out.println("Server: Bye."); - } - }); - } - - /** - * Server mode: waits for shutdown trigger. - * - * @throws InterruptedException Propagated interrupted exception. - */ - private void awaitTermination() throws InterruptedException { - if (this.server != null) { - this.server.awaitTermination(); - } - } - - /** - * Server mode: this is the Dapr method to receive Invoke operations via Grpc. - * - * @param request Dapr envelope request, - * @param responseObserver Dapr envelope response. - */ @Override - public void onInvoke(DaprClientProtos.InvokeEnvelope request, StreamObserver responseObserver) { - try { - if ("say".equals(request.getMethod())) { - // IMPORTANT: do not use Any.unpack(), use Type.ParseFrom() instead. - SayRequest sayRequest = SayRequest.parseFrom(request.getData().getValue()); - SayResponse sayResponse = this.say(sayRequest); - responseObserver.onNext(Any.pack(sayResponse)); - } - } catch (InvalidProtocolBufferException e) { - e.printStackTrace(); - responseObserver.onError(e); - } finally { - responseObserver.onCompleted(); - } - } - - /** - * Handling of the 'say' method. - * - * @param request Request to say something. - * @return Response with when it was said. - */ - public SayResponse say(SayRequest request) { - Calendar utcNow = Calendar.getInstance(TimeZone.getTimeZone("GMT")); - String utcNowAsString = DATE_FORMAT.format(utcNow.getTime()); - - // Handles the request by printing message. - System.out.println("Server: " + request.getMessage() + " @ " + utcNowAsString); - - // Now respond with current timestamp. - SayResponse.Builder responseBuilder = SayResponse.newBuilder(); - return responseBuilder.setTimestamp(utcNowAsString).build(); + public void run() { + System.out.println("Server: shutting down gracefully ..."); + GrpcHelloWorldDaprService.this.server.shutdown(); + System.out.println("Server: Bye."); } + }); } - public static void main(String[] args) throws Exception { - Options options = new Options(); - options.addRequiredOption("p", "port", true, "Port to listen or send event 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 GrpcHelloWorldDaprService service = new GrpcHelloWorldDaprService(); - service.start(port); - service.awaitTermination(); + /** + * Server mode: waits for shutdown trigger. + * + * @throws InterruptedException Propagated interrupted exception. + */ + private void awaitTermination() throws InterruptedException { + if (this.server != null) { + this.server.awaitTermination(); + } } + + /** + * Server mode: this is the Dapr method to receive Invoke operations via Grpc. + * + * @param request Dapr envelope request, + * @param responseObserver Dapr envelope response. + */ + @Override + public void onInvoke(DaprClientProtos.InvokeEnvelope request, StreamObserver responseObserver) { + try { + if ("say".equals(request.getMethod())) { + // IMPORTANT: do not use Any.unpack(), use Type.ParseFrom() instead. + SayRequest sayRequest = SayRequest.parseFrom(request.getData().getValue()); + SayResponse sayResponse = this.say(sayRequest); + responseObserver.onNext(Any.pack(sayResponse)); + } + } catch (InvalidProtocolBufferException e) { + e.printStackTrace(); + responseObserver.onError(e); + } finally { + responseObserver.onCompleted(); + } + } + + /** + * Handling of the 'say' method. + * + * @param request Request to say something. + * @return Response with when it was said. + */ + public SayResponse say(SayRequest request) { + Calendar utcNow = Calendar.getInstance(TimeZone.getTimeZone("GMT")); + String utcNowAsString = DATE_FORMAT.format(utcNow.getTime()); + + // Handles the request by printing message. + System.out.println("Server: " + request.getMessage() + " @ " + utcNowAsString); + + // Now respond with current timestamp. + SayResponse.Builder responseBuilder = SayResponse.newBuilder(); + return responseBuilder.setTimestamp(utcNowAsString).build(); + } + } + + public static void main(String[] args) throws Exception { + Options options = new Options(); + options.addRequiredOption("p", "port", true, "Port to 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 GrpcHelloWorldDaprService service = new GrpcHelloWorldDaprService(); + service.start(port); + service.awaitTermination(); + } } diff --git a/examples/src/main/java/io/dapr/examples/invoke/http/DemoService.java b/examples/src/main/java/io/dapr/examples/invoke/http/DemoService.java new file mode 100644 index 000000000..e16a87aee --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/invoke/http/DemoService.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.examples.invoke.http; + +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; + +/** + * 1. Build and install jars: + * mvn clean install + * 2. Run in server mode: + * dapr run --app-id invokedemo --app-port 3000 --port 3005 -- mvn exec:java -pl=examples -D exec.mainClass=io.dapr.examples.invoke.http.DemoService -D exec.args="-p 3000" + */ +public class DemoService { + + /** + * Starts the service. + * @param args Expects the port: -p PORT + * @throws Exception If cannot start service. + */ + public static void main(String[] args) throws Exception { + Options options = new Options(); + options.addRequiredOption("p", "port", true, "Port to 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")); + + DaprApplication.start(port); + } +} diff --git a/examples/src/main/java/io/dapr/examples/invoke/http/DemoServiceController.java b/examples/src/main/java/io/dapr/examples/invoke/http/DemoServiceController.java new file mode 100644 index 000000000..d08667f9a --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/invoke/http/DemoServiceController.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.examples.invoke.http; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Mono; + +import java.nio.charset.StandardCharsets; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Map; +import java.util.TimeZone; + +/** + * SpringBoot Controller to handle input binding. + */ +@RestController +public class DemoServiceController { + + /** + * Json serializer/deserializer. + */ + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + /** + * Format to output date and time. + */ + private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + + @PostMapping(path = "/say") + public Mono handleMethod(@RequestBody(required = false) byte[] body, + @RequestHeader Map headers) { + return Mono.fromSupplier(() -> { + try { + String message = body == null ? "" : new String(body, StandardCharsets.UTF_8); + + Calendar utcNow = Calendar.getInstance(TimeZone.getTimeZone("GMT")); + String utcNowAsString = DATE_FORMAT.format(utcNow.getTime()); + + String metadataString = headers == null ? "" : OBJECT_MAPPER.writeValueAsString(headers); + + // Handles the request by printing message. + System.out.println( + "Server: " + message + " @ " + utcNowAsString + " and metadata: " + metadataString); + + return utcNowAsString; + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + +} diff --git a/examples/src/main/java/io/dapr/examples/invoke/http/InvokeClient.java b/examples/src/main/java/io/dapr/examples/invoke/http/InvokeClient.java new file mode 100644 index 000000000..ced9ab84b --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/invoke/http/InvokeClient.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.examples.invoke.http; + +import io.dapr.client.DaprClient; +import io.dapr.client.DaprClientBuilder; +import io.dapr.serializer.DefaultObjectSerializer; +import io.dapr.client.domain.Verb; + +/** + * 1. Build and install jars: + * mvn clean install + * 2. Send messages to the server: + * dapr run --port 3006 -- mvn exec:java -pl=examples -Dexec.mainClass=io.dapr.examples.invoke.http.InvokeClient -Dexec.args="'message one' 'message two'" + */ +public class InvokeClient { + + /** + * Identifier in Dapr for the service this client will invoke. + */ + private static final String SERVICE_APP_ID = "invokedemo"; + + /** + * Starts the invoke client. + * @param args Messages to be sent as request for the invoke API. + */ + public static void main(String[] args) { + DaprClient client = (new DaprClientBuilder(new DefaultObjectSerializer(), new DefaultObjectSerializer())).build(); + for (String message : args) { + client.invokeService(Verb.POST, SERVICE_APP_ID, "say", message, null, String.class).block(); + } + } +} diff --git a/examples/src/main/java/io/dapr/examples/invoke/http/README.md b/examples/src/main/java/io/dapr/examples/invoke/http/README.md new file mode 100644 index 000000000..40e3c1f0c --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/invoke/http/README.md @@ -0,0 +1,130 @@ +# Method invocation Sample + +In this sample, we'll create a two java applications: An exposer service application which exposes a method and a client application which will invoke the method from demo service using Dapr. +This sample includes: + +* DemoService (Exposes the method to be remotely accessed) +* InvokeClient (Invokes the exposed method from DemoService) + +Visit [this](https://github.com/dapr/docs/blob/master/concepts/service-invocation/service-invocation.md) link for more information about Dapr and service invocation. + +## Remote invocation using the Java-SDK + +This sample uses the Client provided in Dapr Java SDK invoking a remote method. Exposing the method + + +## Pre-requisites + +* [Dapr and Dapr Cli](https://github.com/dapr/docs/blob/master/getting-started/environment-setup.md#environment-setup). +* Java JDK 11 (or greater): [Oracle JDK](https://www.oracle.com/technetwork/java/javase/downloads/index.html#JDK11) or [OpenJDK](https://jdk.java.net/13/). +* [Apache Maven](https://maven.apache.org/install.html) version 3.x. + +### Checking out the code + +Clone this repository: + +```sh +git clone https://github.com/dapr/java-sdk.git +cd java-sdk +``` + +Then build the Maven project: + +```sh +# make sure you are in the `java-sdk` directory. +mvn install +``` + +### Running the Demo service sample + +The Demo service application is meant to expose a method that can be remotely invoked. In this example, the exposer feature has two parts: + +In the `DemoService.java` file, you will find the `DemoService` class, containing the main method. The main method uses the Spring Boot´s DaprApplication class for initializing the `ExposerServiceController`. See the code snippet below: + +```java +public class DemoService { + ///... + public static void main(String[] args) throws Exception { + ///... + // If port string is not valid, it will throw an exception. + int port = Integer.parseInt(cmd.getOptionValue("port")); + + DaprApplication.start(port); + } +} +``` + +`DaprApplication.start()` Method will run an Spring Boot application that registers the `DemoServiceController`, which exposes the invoking action as a POST request. The Dapr's sidecar is the one that performs the actual call to the controller, based on the binding features and the remote invocation action. + +This Spring Controller exposes the `say` method. The method retrieves metadata from the headers and prints them along with the current date in console. The actual response from method is the formatted current date. See the code snippet below: + +```java +@RestController +public class DemoServiceController { + ///... + @PostMapping(path = "/say") + public Mono handleMethod(@RequestBody(required = false) byte[] body, + @RequestHeader Map headers) { + return Mono.fromSupplier(() -> { + try { + String message = body == null ? "" : new String(body, StandardCharsets.UTF_8); + + Calendar utcNow = Calendar.getInstance(TimeZone.getTimeZone("GMT")); + String utcNowAsString = DATE_FORMAT.format(utcNow.getTime()); + + String metadataString = headers == null ? "" : OBJECT_MAPPER.writeValueAsString(headers); + + // Handles the request by printing message. + System.out.println( + "Server: " + message + " @ " + utcNowAsString + " and metadata: " + metadataString); + + return utcNowAsString; + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } +} +``` + +Use the follow command to execute the exposer example: + +```sh +dapr run --app-id invokedemo --app-port 3000 --port 3005 -- mvn exec:java -pl=examples -D exec.mainClass=io.dapr.examples.invoke.http.DemoService -D exec.args="-p 3000" +``` + +Once running, the ExposerService is now ready to be invoked by Dapr. + + +### Running the InvokeClient sample + +The Invoke client sample uses the Dapr SDK for invoking the remote method. The main method declares a Dapr Client using the DaprClientBuilder class. Notice that this builder gets two serializer implementations in the constructor: One is for Dapr's sent and recieved objects, and second is for objects to be persisted. It needs to know the method name to invoke as well as the application id for the remote application. In `InvokeClient.java` file, you will find the `InvokeClient` class and the `main` method. See the code snippet below: + +```java +public class InvokeClient { + +private static final String SERVICE_APP_ID = "invokedemo"; +///... + public static void main(String[] args) { + DaprClient client = (new DaprClientBuilder(new DefaultObjectSerializer(), new DefaultObjectSerializer())).build(); + for (String message : args) { + client.invokeService(Verb.POST, SERVICE_APP_ID, "say", message, null, String.class).block(); + } + } +///... +} +``` + +The class knows the app id for the remote application. It uses the the static `Dapr.getInstance().invokeService` method to invoke the remote method defining the parameters: The verb, application id, method name, and proper data and metadata, as well as the type of the expected retun data. + + Execute the follow script in order to run the InvokeClient example, passing two messages for the remote method: +```sh +dapr run --port 3006 -- mvn exec:java -pl=examples -D exec.mainClass=io.dapr.examples.invoke.http.InvokeClient -D exec.args="'message one' 'message two'" +``` +Once running, the output should display the messages sent from invoker in the exposer service output as follows: + +![exposeroutput](../../../../../../resources/img/exposer-service.png) + +Method have been remotely invoked and displaying the remote messages. + +For more details on Dapr Spring Boot integration, please refer to [Dapr Spring Boot](../../../springboot/DaprApplication.java) Application implementation. \ No newline at end of file diff --git a/examples/src/main/java/io/dapr/examples/pubsub/http/Publisher.java b/examples/src/main/java/io/dapr/examples/pubsub/http/Publisher.java new file mode 100644 index 000000000..18eb1d615 --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/pubsub/http/Publisher.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.examples.pubsub.http; + +import io.dapr.client.DaprClient; +import io.dapr.client.DaprClientBuilder; +import io.dapr.serializer.DefaultObjectSerializer; + +import java.util.Collections; + +/** + * Message publisher. + * 1. Build and install jars: + * mvn clean install + * 2. Run the program: + * dapr run --app-id publisher --port 3006 -- mvn exec:java -pl=examples -D exec.mainClass=io.dapr.examples.pubsub.http.Publisher + */ +public class Publisher { + + //Number of messages to be sent: 10 + private static final int NUM_MESSAGES = 10; + //The title of the topic to be used for publishing + private static final String TOPIC_NAME = "testingtopic"; + + public static void main(String[] args) throws Exception { + //Creating the DaprClient: Using the default builder client produces an HTTP Dapr Client + DaprClient client = new DaprClientBuilder(new DefaultObjectSerializer(), new DefaultObjectSerializer()).build(); + for (int i = 0; i < NUM_MESSAGES; i++) { + String message = String.format("This is message #%d", i); + //Publishing messages + client.publishEvent(TOPIC_NAME, message).block(); + System.out.println("Published message: " + message); + + try { + Thread.sleep((long)(1000 * Math.random())); + } catch (InterruptedException e) { + e.printStackTrace(); + Thread.currentThread().interrupt(); + return; + } + } + + //Publishing a single bite: Example of non-string based content published + client.publishEvent( + TOPIC_NAME, + new byte[] { 1 }, + Collections.singletonMap("content-type", "application/octet-stream")).block(); + System.out.println("Published one byte."); + + System.out.println("Done."); + } +} diff --git a/examples/src/main/java/io/dapr/examples/pubsub/http/README.md b/examples/src/main/java/io/dapr/examples/pubsub/http/README.md new file mode 100644 index 000000000..572d5f1d9 --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/pubsub/http/README.md @@ -0,0 +1,140 @@ +# Dapr Pub-Sub Sample + +In this sample, we'll create a publisher and a subscriber java applications using Dapr, based on the publish-subcribe pattern. The publisher will generate messages of a specific topic, while subscriber will listen for messages of specific topic. See [Why Pub-Sub](#why-pub-sub) to understand when this pattern might be a good choice for your software architecture. + +Visit [this](https://github.com/dapr/docs/tree/master/concepts/publish-subscribe-messaging) link for more information about Dapr and Pub-Sub. + +## Pub-Sub Sample using the Java-SDK + +This sample uses the HTTP Client provided in Dapr Java SDK for subscribing, and Dapr Spring Boot integration for publishing. This example uses Redis Streams (enabled in Redis versions => 5). +## Pre-requisites + +* [Dapr and Dapr Cli](https://github.com/dapr/docs/blob/master/getting-started/environment-setup.md#environment-setup). +* Java JDK 11 (or greater): [Oracle JDK](https://www.oracle.com/technetwork/java/javase/downloads/index.html#JDK11) or [OpenJDK](https://jdk.java.net/13/). +* [Apache Maven](https://maven.apache.org/install.html) version 3.x. + +### Checking out the code + +Clone this repository: + +```sh +git clone https://github.com/dapr/java-sdk.git +cd java-sdk +``` + +Then build the Maven project: + +```sh +# make sure you are in the `java-sdk` directory. +mvn install +``` +### Running the subscriber + +The first is the subscriber. It will subscribe to the topic to be used by the publisher and read the messages published. The Subscriber uses the Spring Boot´s DaprApplication class for initializing the `SubscriberController`. In `Subscriber.java` file, you will find the `Subscriber` class and the `main` method. See the code snippet below: + +```java +public class Subscriber { + + public static void main(String[] args) throws Exception { + ///... + // Start Dapr's callback endpoint. + DaprApplication.start(port); + } +} +``` +`DaprApplication.start()` Method will run an Spring Boot application that registers the `SubscriberController`, which exposes the message retrieval as a POST request. The Dapr's sidecar is the one that performs the actual call to the controller, based on the pubsub features. + +This Spring Controller handles the message endpoint, Printing the recieved message which is recieved as the POST body. See the code snippet below: + +```java +@RestController +public class SubscriberController { + ///... + @GetMapping("/dapr/subscribe") + public byte[] daprConfig() throws Exception { + return SERIALIZER.serialize(new String[] { "message" }); + } + + @PostMapping(path = "/message") + public Mono handleMessage(@RequestBody(required = false) byte[] body, + @RequestHeader Map headers) { + return Mono.fromRunnable(() -> { + try { + // Dapr's event is compliant to CloudEvent. + CloudEventEnvelope envelope = SERIALIZER.deserialize(body, CloudEventEnvelope.class); + + String message = envelope.getData() == null ? "" : new String(envelope.getData()); + System.out.println("Subscriber got message: " + message); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } +} +``` +Execute the follow script in order to run the Subscriber example: +```sh +dapr run --app-id subscriber --app-port 3000 --port 3005 -- mvn exec:java -pl=examples -D exec.mainClass=io.dapr.examples.pubsub.http.Subscriber -D exec.args="-p 3000" +``` + +### Running the publisher + +The other component is the publisher. It is a simple java application with a main method that uses the Dapr HTTP Client to publish 10 messages to an specific topic. + +In the `Publisher.java` file, you will find the `Publisher` class, containing the main method. The main method declares a Dapr Client using the `DaprClientBuilder` class. Notice that this builder gets two serializer implementations in the constructor: One is for Dapr's sent and recieved objects, and second is for objects to be persisted. The client publishes messages using `publishEvent` method. See the code snippet below: +```java +public class Publisher { + private static final int NUM_MESSAGES = 10; + private static final String TOPIC_NAME = "testingtopic"; +///... + public static void main(String[] args) throws Exception { + DaprClient client = new DaprClientBuilder(new DefaultObjectSerializer(), new DefaultObjectSerializer()).build(); + for (int i = 0; i < NUM_MESSAGES; i++) { + String message = String.format("This is message #%d", i); + client.publishEvent(TOPIC_NAME, message).block(); + System.out.println("Published message: " + message); + //.. + } + } +///... +} +``` + +This example also pushes a non-string content event, the follow code in same `Publisher` main method publishes a bite: + +```java +public class Publisher { +///... + public static void main(String[] args) throws Exception { +///... + //Publishing a single bite: Example of non-string based content published + client.publishEvent( + TOPIC_NAME, + new byte[] { 1 }, + Collections.singletonMap("content-type", "application/octet-stream")).block(); + System.out.println("Published one byte."); + System.out.println("Done."); + } +///... +} +``` + +Use the follow command to execute the Publisher example: + +```sh +dapr run --app-id publisher --port 3006 -- mvn exec:java -pl=examples -D exec.mainClass=io.dapr.examples.pubsub.http.Publisher +``` + +Once running, the Publisher should print the output as follows: + +![publisheroutput](../../../../../../resources/img/publisher.png) + +Messages have been published in the topic. + +Once running, the Subscriber should print the output as follows: + +![publisheroutput](../../../../../../resources/img/subscriber.png) + +Messages have been retrieved from the topic. + +For more details on Dapr Spring Boot integration, please refer to [Dapr Spring Boot](../../../springboot/DaprApplication.java) Application implementation. \ No newline at end of file diff --git a/examples/src/main/java/io/dapr/examples/pubsub/http/Subscriber.java b/examples/src/main/java/io/dapr/examples/pubsub/http/Subscriber.java new file mode 100644 index 000000000..e772e331f --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/pubsub/http/Subscriber.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.examples.pubsub.http; + +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; + +/** + * Service for subscriber. + * 1. Build and install jars: + * mvn clean install + * 2. Run the server: + * dapr run --app-id subscriber --app-port 3000 --port 3005 -- mvn exec:java -pl=examples -Dexec.mainClass=io.dapr.examples.pubsub.http.Subscriber -Dexec.args="-p 3000" + */ +public class Subscriber { + + public static void main(String[] args) throws Exception { + Options options = new Options(); + 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")); + + // Start Dapr's callback endpoint. + DaprApplication.start(port); + } +} \ No newline at end of file diff --git a/examples/src/main/java/io/dapr/examples/pubsub/http/SubscriberController.java b/examples/src/main/java/io/dapr/examples/pubsub/http/SubscriberController.java new file mode 100644 index 000000000..dee0a131a --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/pubsub/http/SubscriberController.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.examples.pubsub.http; + +import io.dapr.client.domain.CloudEvent; +import io.dapr.serializer.DefaultObjectSerializer; +import org.springframework.web.bind.annotation.*; +import reactor.core.publisher.Mono; + +import java.util.Map; + +/** + * SpringBoot Controller to handle input binding. + */ +@RestController +public class SubscriberController { + + /** + * Dapr's default serializer/deserializer. + */ + private static final DefaultObjectSerializer SERIALIZER = new DefaultObjectSerializer (); + + @GetMapping("/dapr/subscribe") + public byte[] daprConfig() throws Exception { + return SERIALIZER.serialize(new String[] { "testingtopic" }); + } + + @PostMapping(path = "/testingtopic") + public Mono handleMessage(@RequestBody(required = false) byte[] body, + @RequestHeader Map headers) { + return Mono.fromRunnable(() -> { + try { + // Dapr's event is compliant to CloudEvent. + CloudEvent envelope = CloudEvent.deserialize(body); + + String message = envelope.getData() == null ? "" : new String(envelope.getData()); + System.out.println("Subscriber got message: " + message); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + +} diff --git a/examples/src/main/java/io/dapr/examples/state/grpc/Example.java b/examples/src/main/java/io/dapr/examples/state/grpc/Example.java index 7b9b20d19..9b01b2dfc 100644 --- a/examples/src/main/java/io/dapr/examples/state/grpc/Example.java +++ b/examples/src/main/java/io/dapr/examples/state/grpc/Example.java @@ -16,46 +16,46 @@ import java.util.UUID; * dapr run --grpc-port 50001 -- mvn exec:java -pl=examples -Dexec.mainClass=io.dapr.examples.Example */ public class Example { - public static void main(String[] args) { - ManagedChannel channel = - ManagedChannelBuilder.forAddress("localhost", 50001).usePlaintext().build(); - DaprBlockingStub client = DaprGrpc.newBlockingStub(channel); + public static void main(String[] args) { + ManagedChannel channel = + ManagedChannelBuilder.forAddress("localhost", 50001).usePlaintext().build(); + DaprBlockingStub client = DaprGrpc.newBlockingStub(channel); - String key = "mykey"; - // First, write key-value pair. - { - String value = UUID.randomUUID().toString(); - StateRequest req = StateRequest - .newBuilder() - .setKey(key) - .setValue(Any.newBuilder().setValue(ByteString.copyFromUtf8(value)).build()) - .build(); - SaveStateEnvelope state = SaveStateEnvelope.newBuilder() - .addRequests(req) - .build(); - client.saveState(state); - System.out.println("Saved!"); - } - - // Now, read it back. - { - GetStateEnvelope req = GetStateEnvelope - .newBuilder() - .setKey(key) - .build(); - GetStateResponseEnvelope response = client.getState(req); - String value = response.getData().getValue().toStringUtf8(); - System.out.println("Got: " + value); - } - - // Then, delete it. - { - DeleteStateEnvelope req = DeleteStateEnvelope - .newBuilder() - .setKey(key) - .build(); - client.deleteState(req); - System.out.println("Deleted!"); - } + String key = "mykey"; + // First, write key-value pair. + { + String value = UUID.randomUUID().toString(); + StateRequest req = StateRequest + .newBuilder() + .setKey(key) + .setValue(Any.newBuilder().setValue(ByteString.copyFromUtf8(value)).build()) + .build(); + SaveStateEnvelope state = SaveStateEnvelope.newBuilder() + .addRequests(req) + .build(); + client.saveState(state); + System.out.println("Saved!"); } + + // Now, read it back. + { + GetStateEnvelope req = GetStateEnvelope + .newBuilder() + .setKey(key) + .build(); + GetStateResponseEnvelope response = client.getState(req); + String value = response.getData().getValue().toStringUtf8(); + System.out.println("Got: " + value); + } + + // Then, delete it. + { + DeleteStateEnvelope req = DeleteStateEnvelope + .newBuilder() + .setKey(key) + .build(); + client.deleteState(req); + System.out.println("Deleted!"); + } + } } diff --git a/examples/src/main/java/io/dapr/examples/state/http/OrderManager.java b/examples/src/main/java/io/dapr/examples/state/http/OrderManager.java index 05fa5d108..df2650f7e 100644 --- a/examples/src/main/java/io/dapr/examples/state/http/OrderManager.java +++ b/examples/src/main/java/io/dapr/examples/state/http/OrderManager.java @@ -2,6 +2,9 @@ package io.dapr.examples.state.http; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpServer; +import io.dapr.client.DaprClient; +import io.dapr.client.DaprClientBuilder; +import io.dapr.serializer.DefaultObjectSerializer; import org.json.JSONArray; import org.json.JSONObject; @@ -20,6 +23,8 @@ import java.net.http.HttpResponse; import java.net.http.HttpResponse.BodyHandlers; import java.nio.charset.Charset; import java.time.Duration; +import java.util.Arrays; +import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; @@ -27,117 +32,84 @@ import static java.lang.System.out; /** * OrderManager web app. - * + *

* Based on the helloworld Node.js example in https://github.com/dapr/samples/blob/master/1.hello-world/app.js - * + *

* To install jars into your local maven repo: - * mvn clean install - * + * mvn clean install + *

* To run (after step above): - * dapr run --app-id orderapp --app-port 3000 --port 3500 -- mvn exec:java -pl=examples -Dexec.mainClass=io.dapr.examples.state.http.OrderManager - * + * dapr run --app-id orderapp --app-port 3000 --port 3500 -- mvn exec:java -pl=examples -Dexec.mainClass=io.dapr.examples.state.http.OrderManager + *

* If this class changes, run this before running it again: - * mvn compile + * mvn compile */ public class OrderManager { - static HttpClient httpClient; + public static void main(String[] args) throws IOException { + int httpPort = 3001; + HttpServer httpServer = HttpServer.create(new InetSocketAddress(httpPort), 0); - public static void main(String[] args) throws IOException { - int httpPort = 3000; - String daprPort = Optional.ofNullable(System.getenv("DAPR_HTTP_PORT")).orElse("3500"); - String stateUrl = String.format("http://localhost:%s/v1.0/state", daprPort); - HttpServer httpServer = HttpServer.create(new InetSocketAddress(httpPort), 0); + DaprClient daprClient = + (new DaprClientBuilder(new DefaultObjectSerializer(), new DefaultObjectSerializer())).build(); - httpClient = HttpClient.newBuilder().version(Version.HTTP_1_1).followRedirects(Redirect.NORMAL) - .connectTimeout(Duration.ofSeconds(2)).build(); - - httpServer.createContext("/order").setHandler(e -> { - out.println("Fetching order!"); - fetch(stateUrl + "/order").thenAccept(response -> { - int resCode = response.statusCode() == 200 ? 200 : 500; - String body = response.statusCode() == 200 ? response.body() : "Could not get state."; - - try { - e.sendResponseHeaders(resCode, body.getBytes().length); - OutputStream os = e.getResponseBody(); - try { - os.write(body.getBytes()); - } finally { - os.close(); - } - } catch (IOException ioerror) { - out.println(ioerror); - } - }); - }); - - httpServer.createContext("/neworder").setHandler(e -> { - try { - out.println("Received new order ..."); - String json = readBody(e); - JSONObject jsonObject = new JSONObject(json); - JSONObject data = jsonObject.getJSONObject("data"); - String orderId = data.getString("orderId"); - out.printf("Got a new order! Order ID: %s\n", orderId); - - JSONObject item = new JSONObject(); - item.put("key", "order"); - item.put("value", data); - JSONArray state = new JSONArray(); - state.put(item); - out.printf("Writing to state: %s\n", state.toString()); - - post(stateUrl, state.toString()).thenAccept(response -> { - int resCode = response.statusCode() == 200 ? 200 : 500; - String body = response.body(); - try { - e.sendResponseHeaders(resCode, body.getBytes().length); - OutputStream os = e.getResponseBody(); - try { - os.write(body.getBytes()); - } finally { - os.close(); - } - } catch (IOException ioerror) { - out.println(ioerror); - } - }); - } catch (IOException ioerror) { - out.println(ioerror); - } - }); - - httpServer.start(); - out.printf("Java App listening on port %s.", httpPort); - } - - private static CompletableFuture> fetch(String url) { - HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).build(); - return httpClient.sendAsync(request, BodyHandlers.ofString()); - } - - private static CompletableFuture> post(String url, String body) { - HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)) - .header("Content-Type", "application/json; charset=UTF-8").POST(BodyPublishers.ofString(body)).build(); - - return httpClient.sendAsync(request, BodyHandlers.ofString()); - } - - private static String readBody(HttpExchange t) throws IOException { - // retrieve the request json data - InputStream is = t.getRequestBody(); - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - byte[] buffer = new byte[1024]; - int len; + httpServer.createContext("/order").setHandler(e -> { + out.println("Fetching order!"); try { - while ((len = is.read(buffer)) > 0) - bos.write(buffer, 0, len); - } catch (IOException e) { - e.printStackTrace(); - } finally { - bos.close(); + byte[] data = daprClient.getState("order", String.class).block().getValue().getBytes(); + e.getResponseHeaders().set("content-type", "application/json"); + e.sendResponseHeaders(200, data.length); + e.getResponseBody().write(data); + e.getResponseBody().close(); + } catch (IOException ioerror) { + out.println(ioerror); + e.sendResponseHeaders(500, ioerror.getMessage().getBytes().length); + e.getResponseBody().write(ioerror.getMessage().getBytes()); + e.getResponseBody().close(); } - return new String(bos.toByteArray(), Charset.forName("UTF-8")); + }); + + httpServer.createContext("/neworder").setHandler(e -> { + try { + out.println("Received new order ..."); + String json = readBody(e); + JSONObject jsonObject = new JSONObject(json); + JSONObject data = jsonObject.getJSONObject("data"); + String orderId = data.getString("orderId"); + out.printf("Got a new order! Order ID: %s\n", orderId); + + daprClient.saveState("order", data.toString()).block(); + + out.printf("Saved state: %s\n", data.toString()); + e.sendResponseHeaders(200, 0); + e.getResponseBody().write(new byte[0]); + e.getResponseBody().close(); + } catch (IOException ioerror) { + out.println(ioerror); + e.sendResponseHeaders(500, ioerror.getMessage().getBytes().length); + e.getResponseBody().write(ioerror.getMessage().getBytes()); + e.getResponseBody().close(); + } + }); + + httpServer.start(); + out.printf("Java App listening on port %s.", httpPort); + } + + private static String readBody(HttpExchange t) throws IOException { + // retrieve the request json data + InputStream is = t.getRequestBody(); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int len; + try { + while ((len = is.read(buffer)) > 0) + bos.write(buffer, 0, len); + } catch (IOException e) { + e.printStackTrace(); + } finally { + bos.close(); } + return new String(bos.toByteArray(), Charset.forName("UTF-8")); + } } diff --git a/examples/src/main/java/io/dapr/springboot/DaprApplication.java b/examples/src/main/java/io/dapr/springboot/DaprApplication.java new file mode 100644 index 000000000..22d22ea6b --- /dev/null +++ b/examples/src/main/java/io/dapr/springboot/DaprApplication.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.springboot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * Dapr's HTTP callback implementation via SpringBoot. + */ +@SpringBootApplication(scanBasePackages = {"io.dapr.springboot", "io.dapr.examples"}) +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); + app.run(String.format("--server.port=%d", port)); + } + +} diff --git a/examples/src/main/java/io/dapr/springboot/DaprController.java b/examples/src/main/java/io/dapr/springboot/DaprController.java new file mode 100644 index 000000000..1f1ffa018 --- /dev/null +++ b/examples/src/main/java/io/dapr/springboot/DaprController.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +package io.dapr.springboot; + +import io.dapr.actors.runtime.ActorRuntime; +import org.springframework.web.bind.annotation.*; +import reactor.core.publisher.Mono; + +/** + * SpringBoot Controller to handle callback APIs for Dapr. + */ +@RestController +public class DaprController { + + @GetMapping("/") + public String index() { + return "Greetings from Dapr!"; + } + + @GetMapping("/dapr/config") + public byte[] daprConfig() throws Exception { + return ActorRuntime.getInstance().serializeConfig(); + } + + @PostMapping(path = "/actors/{type}/{id}") + public Mono activateActor(@PathVariable("type") String type, + @PathVariable("id") String id) throws Exception { + return ActorRuntime.getInstance().activate(type, id); + } + + @DeleteMapping(path = "/actors/{type}/{id}") + public Mono deactivateActor(@PathVariable("type") String type, + @PathVariable("id") String id) throws Exception { + return ActorRuntime.getInstance().deactivate(type, id); + } + + @PutMapping(path = "/actors/{type}/{id}/method/{method}") + public Mono invokeActorMethod(@PathVariable("type") String type, + @PathVariable("id") String id, + @PathVariable("method") String method, + @RequestBody(required = false) byte[] body) { + return ActorRuntime.getInstance().invoke(type, id, method, body); + } + + @PutMapping(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); + } + + @PutMapping(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) byte[] body) { + return ActorRuntime.getInstance().invokeReminder(type, id, reminder, body); + } + +} diff --git a/examples/src/main/resources/img/exposer-service.png b/examples/src/main/resources/img/exposer-service.png new file mode 100644 index 0000000000000000000000000000000000000000..8034d839abd82dd65ff8f9e01e52bb2b449def82 GIT binary patch literal 131379 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


4Nzt_kaqo+3BejHsOKajrI7U z-*)47yY(2R&Z#HN$)#>HjS&6uGi2LswY}3*0B*VDRZrq*kTFy1*1%V0=&rgfFO=Ul zo&Hl!d4bZbevW_Q>Luvy1!Acls1}R8;PeQ#@_B35I2fgQxeMjE5*ctgBG2gZ?{U!zx1}|ynm{R!4>VhkC-eHT7p)fjFH0L&q?0u?7PVgxbKclushs!j_-Xo@`2lRr@^H zrEG9Q|GZKAV@!AZ4;wJ@P(fLB9NHk&5eUi;S_-ojR+#z2UXP;0T4Ib$uovIHWliXh zvr440xwyvNRh8KEwrI)IMluPe7&(3aSuQv%4BFt=Q%I8D#&41u0M8a!_Ur|9uVF^` zUZ~Y0(74;)>cwG*IJcpm<7b_usk}1SkoR@Mov$s!H_U;0gF*0H<2`2Y@MtDEhmj=W z$uPO;W~`9+>S~)DYD1QmZl4G!a&8Wvp?p8TcX1D{It-GU98-hcX(b9^MoK8se0AJX zR5dBYf(Q-V?D7 zvV4L4Wg}ehMHpg)iI5?JijKn39e)nE2r?OBrOcggDQ>9MO0013+$&&x#|)!|88q0Q z^}K0thPDS{r_^lKBVVT*^|TCD>2nQgAhRG#Icm3eT9poKu=$d7&sPKH*O@EOJA#bz zv}=LP;h#Gz(~7q65TfM>Ve2X)5Re@!6gB|?p{ziqwUB;cbC`ux&1pbs~-IL z4Ux4n<}X+Cuz_@cCL;wB(LIkE^-E|lm<#Ur$iHecYnvugX>&n02QldI1NIM4xg%#M zmrs4W1PC7j1&$B{q!U}&NQ6%W948kpLu;bHAJOkKA*Tj#edoBgr0<7?=}x&>e00%H z#&`yLSdY|v6u&3ra5ACfiOpL(1=aP2tXG;#Mo#jX`=4o*{#=p`a zlcybX?V=~EVjksB6w>|Lu9N3-+9GLSY)dz3^zo9351Uwk1l_!k68Ge3#ewmk=9ha@oVCvoOKs1|YWlE#2L2 z=~~+|r9Qs0=X6t+g&JdLd{xH59wWjhQ|9HWds=q%G+o~!-8>C|vdO9UbudOBl_($k z$FoeE=LhdP38|pGZ&~KJumOhNvcrXGyx#!d6yIMeTFwr*lIwF>l&=$J~zqNbfYYuqo6p5Fhry{(TPUR6O{Rp-XL!1Hkc6OFjH!#c~Ajf{xS(Alb- z$?#wC9CWjF9Zy*GU!>K8yy2ax1!qLE4RvR#;KDwc;(}Ne9js(&ONC4v2OpRzb(77p zr!>aRwp$ox6v=C}I0S3mBp@mn$?KDs%5+HbEYof)cM#*5eC}A9PQN@|#HR}>mv~kN zA&LB4h9#>&$wi4X%*J1=lOf_rGxzm*sHtXJhLSW4K6Q_>fmF)yg%g`w(ttt4C6!)4 z6{WAVE6$$J!gTK5J=}@&0}2=cfEFlhK$e^EamORCa%tD>eSaQYVluU0AH|#l#KN^e zBR*|B)E@bYjHMfprh2ZI(W44Q3QfQlSnm_A-&sG)(HPm&u*mKfOC&>(-_-o9Nm{pF z`0}`&wq{~KACqMmsu~N}KMyo)Rn@yBpFz82OGvBc*3iJQVn%$KKuE>%hrV0hnD0|F z(GC=epd}KVTf1j(f%Y+lzq8%_7k7Y6yys8m>BZgz*>6{f_dX43VeXMpo`ITF9~9*mG9bG zp8on(i>2R0{B7hUtVNy0cmJqsbpu_CM3=+zK{ET5K4vIkGq*exUb+KlO2S*ejpy+s?l^$mF~ZIhYZJN%81=_jY{GJ% ztJ&Ek>L;&h;kt0AlRv}+WuJTB>Mv|P5y_MtdHQGZ6xBQXC}YwNtVepP3Dsy!f=8>_ ziXj$uzIqtMd^C-GCnaHVI>R?^mfgLjhqp;U{Fq%J@a)DIwH38-i`{V7-u(1t=|xcr zI{4-gB^=U40rHIVS&#jy56@~3i)|lEe^F_uCj*X@NrkZ;-YvWzbTGR6Qbu59BvqVT z=peT(cN+d!y`wyo*ka8?Tzre`EwxPHT-Z>-9PQ!0>1r)LyIP49Qz%~xtJMSw8yUpO z$DC1)^&s>|g0?(AuOUJ0*E#$U2zwep4yw_Ox>wdaWRD9Y=)MlPu@$m8^|ZAN54>sH z-8$t8^?%)$Rwk)Gpip@9K#`K>PvEILhcg>cxI1|HR9(R>&*t=aI* zMDQEX_I2=Ia~Y!lA}q!yjOD%DK=$LM)#wG4nEIqub_1atzq6jh7_uFql}AZwzIGeN z-aV#<8u5}}3IN4r!5DoQ0HiGU_+kPLcbAr8p71Z-mP?*o)SVGP3WG)>bf&L&kL$8a z_uZN4(+^7g-}U9ynzT-jmg6S*R&!R`u3%iBnO-cOpFV$!d4JowH^&t50eBEWM_M3G zFI#bX@#B8Ubt?XSTxYZlfM4s+JJ#Xtz9T0lsciRd^Ct>gcDiuyJgFJ1rP znyr2G%`e{SJ7N*Nfuz5MHb9!*$h5%KM3;}gjN_pu01skKGt$`%9fO(`afTj@saQ5n zypScSbXxD|vA|g2nNB=>H%8V%nkI=PDjfboe`QSx_PdfjlM zjvAUVH#D2b8^1hC$Og9*N79ek|V_mEI;qdZAt#8%XZ>xx#+{<*2K3-h?eeBhxKZ)@< z$ZM};78v-Y4SVC;4{f&oAG;f6h`u#7v!#>C_a)8}n*p)o{cjw1XuDt{B+4bE4QmE& z2mwjtraYFjW6R55Wi*n!$YLvSYOjq!&?%8)-=|Hjpo-aE#ss7)@F)=>428!-q!ry; zGsiZm*BCQkReXmwkM0V;r3tNvFc%AajIwYuD#tPmA)z#1hkA`97M+-n?Mqs(5V>aG zJ#5)*VvFB+oPqRn{b9}!ba~i-@3z%?Gpy6s!c^opM3sxIB(gfT?H3702GD}-bb~nA zsJovk{q!i8G!=bumdU;U!-T$s7y4O)A7H;Kt=2R9g=gZ~mos+u@BzDqJ#krnq6A45 zJyK;Y>bB_j%&uJ~d7nkNPkB=b*n}E>+EK?Z6fa5B{JWaM$*(aAcgK|C8jg7fqZc4U zMX_nqTe1aRf`MU1t|6)2B9BbTGz=N{*yf(Okin>;z!A~sT7{Ush!|z%y=55r?bx5% z(FmZ$$F*8(4Q>c+zQ6Xs;A=*}dD3ydXn0lFw~rRm<+DLOsG@h8s~Q#yS9ZY`dBPUk z{Fgn^UZr!V1q!e(aG1Z91-IEDy{3WOVs(TXmTxvPEnQVEe(5 zdClr_Ly(!pp=t8#oVh|}ITPmb>^>A>?#&|ym#F+24u*?1S(>9k(i_qjxv$o^*2nE@ zQEE4^`TCTnsdd|s!!b|HWGAtrc;?A4$s#-{nMOM%%;u43 zN#ZO&;>pEI@WMGdUAw0 zE&drDrt)GE2-Mr{#Q5@I9v14u)h0RIVRMJReP3;Te!dhtUu+quyq-N3(GF*O9u)j` z$HA>tZX=g=HfaBLl#vY>u45doP5+mD#t`N%5A#<*eJ*vng!${(B!Mgnm`}N#uknN> z$z6RZ7=md z!G%rVV*%*l5+8$t!CSgp}843Y84B%Gab^xCk7#f?Z(DVXq1E7 z@~{ptO%8l=t@{h2c|iGjhi&tAl#M{}pv3d9vkS=|!!NJ46^Ls)t@kC*x)of2IFAv( zX2t5ca=<<5!@jy5^(Yv6KXZyW+{g*Ckn8(u`Dl6~O2KU61VZ~2rW2qF{<2pMCRcIz zr{eV$OZy?~8>*%^`mI=gql1Wq`rNV+Ktq)~;>F8ET?d(}Aguf%1v6XH9n_mv|HW~e zvNFLM0+G{t2_I9BLsqY|$9P{gx-Yse>h4Gw8}FJ@!%9mJnq2#*F3GAamwUsHAHElH z>&J2c(t1VaV+x{p6X=3^#IACn676QxOHhTpZE40+W)lIOX9@X=tT>BdX2au zJ|^F^7ukL+G1KPBcs)*XNQe2xTK+;WI|pgMKTGq02s)ixRMCn&%T?dHjgTy}cZ9wC zQQo>AX8eYu8xtY?4`oLM4jk2tA8)OU25j{(qbYDZL*m=rx6;>MNF38`sM9BF$r5@F zA);@{CF3{iij2RMJn(S}*u|+_8=5?Osqy`LzRCyMc5U@&NSiN^z0rHx4HG;BHKv?} z6ReR@MVRXVym;4sw*M4}6;riB#u;-ac+F2eV{N7GEWGY?(IZySu)bQ;gh9YZKwp?! z(1(>#3RnZepE;}lEGx~SPWwy9;uUtaYW7S&dK+EfL1?9?6EvNP4v|DX^>yv47T0yG z@Bbm%jc*`q@q{VpLn_Hj7U3br$^Hx8XD*!It^;GN8jEk3hqI=)LI9 zyl5S7VEN>jj-y~HYwnEC#43{uJr6isr2Wh$*@tbd`N6pss^sU~Xxpyzc@BY{QK|Ka zr9Wu~Cpq9G$4mg(cK%u+e9cpcsk7*iu=;n63u)Uy%#4Z>hqJmkIa28$#>mTHab)Gt zXJ6bbWs1@B)`KL&YQhz^1{gsp)w%}|)3#%~-;0*L&_a>9ZEch#mT4hf7Q^~lFCwjr z5oOF5ITq2B7efAlV>lMe-vydX5Z_AdOn$ROg_F_ELqna6YW!sBz3?>7<>x4YQ*Iy z04TooM_WoX7Im@<3iRMI)ACZyZ@0`&glV^$C17wn3FvMoah9C3GTcw`5F%l}09Lj;Q zp5gN z3e}TtU2-EqAuh|Y+V^tPNb(rp>)JAKZ8OrCey^U{#->v9Qjsz+pMnvCJ3s5 zen~?IktV0-#*IgD-*RY*een$A^M=5LumqPi+o2Nctp+l8zv$>U%l9e5txqST?CHGx z3%i!h3s~FQtKDB`TNnAfJVdQgPW1ltU?r+7=5_=HKZ3s26Wo4ZAfq6H>!-Mg0icC+x09V$VJ-t?8;y4}HbW&c#~`6+0q{Ui9~!Wl)T5Y&`L>Z4uVm+$_V{GJ zWvL7DAqf;*zpR}7g0oZ5ln^5hm(Xw|qCO~a8-3wyWFvIy`z;Hy*Mz@xXl_-=l%B_* zP4k{rrM97M(btNLrTT0^0cvBt_g3fxxoZMu-xj$DU zY>0mS()#d1XV8cy*z`g;8cDNs*mo1;A{pjRnBIn_?b2+p*>N@gl_1yW5ig;{v{;qX zZ+t73Y;1Vgo;I29J&CCIX$8MLac*)RuP$gis3IT1ZBI{wrVZ@}@wd`zuuwN-sh^Xx z|G@m_hPvF$Kb}qoHZ-uZB)dB&7dR&cvu~%JjkW`DO$b%A){o2W@0tk(Z=>MqX%$#0 zeD9D!moE{+oQxC3cOBT^$RLY#(~YU_x0PJ+_YJA4Dud_0GCnJIux!YC^Tb%xTRKQQ zssG1{qUsa)WsLkqy`UE z`~PHED#imvZ<#8j1z@}4Jh&IGY?Ac7s4SiweDcut_}W}6I$-r`^r{jX&gA{|vFmF1 z%u2b~iKuUnVfYH42EVM;=C*&-*_$tt5m=k6V>6mFNOZ`AaUGMxbaF&kjB<{>Q~4+g zJ}Sf(54)qFCm$hX8BkF+uX%HmHdIg#twvFZL(D!{+#gu>sn<~Bp{~nK;|=$ds3);s z)2bNYcW$8X1_qBPdaWVi&2Surd#*7NG%aSz@~)$#v=zB?i&4Y$QX0B+G0k)xH71VO zIByqRl-}HGy^17m1f{`MRgqv}`VD^{iO+LT_A+jV@Z@A6p} z=+h2fMm_r%n}DzIjS>5~QY6pdE)-o+ ze*D$k@=sCeGV&m*!k;78N7zGG1lR)jr6kXcy>C+3Z8jS1XxU{Rd_x{IYhK*keoE!g zixEWlsrbMULB8d;D$-gGWumzsA=gR5cJnDF;q|1{eVcft?iYt)3&#(KM@@D5tnE~ zymqeSgwTcKA#d7ud#;z=oR8mG8>l(xK-;imuP;=Q&x1y30+Gi>7NB?Qg!2OI3`LbR zCJ-f&_BAGkwZ*L_7u)b?2Q18YUjGRD9v`$;JSRW)7Qm)OpahzV*CXF4qRCaWASH8hgwp*yGJ z+@UFmPI#V;-`&jePO3+Hm5~?XrE(~3VDIgC_-eDWPI*XGe$-DSceM51NeC4UiE1F; z`r~$Ay#)GPq*8@>*+FK<>$OiXRWqM-QI#AY7mOL#TqU#WH3bfI|3=e$jau-@@F=y_ znsoB`%HUv(_q(3nyqScChWqYBD`n#)=D#~aTv6ei)Yv>yu3uV8OC@nOWkg3m+W0?a zUvKJ;KhF9oixWuOz$S_{AMc5BZ!{)qRegd8am_Ok_SgvB~?6cG;2aak0 zKQLw9?_V{}v*(sM(vvLa3U!*s{MaEWYpV2(DpO|as_zW!_W;XEyXD&idDeOjlOFud5&fpHB|Y4~)1&bH z;27O(MkLW2H6W6_p#HUB1$N6bAVDrlGFKe*2`;0m-6w{HibYAXi$L;UN1-BE@f6=S zlDDe)N*#hg}hf6BSEx!JaE;rZgxrG1y~tlUN__|5J#C;#44A0wRu=ij*r4J~M9v!J*r z6AZo8vV9Q5RMMq?&(g$3`TktcZhI4Qw~0J9Eq<$hE~jbvbryW**{iK^UxSDSL5`d`LD@`mk!+r;Wdn%v0tM0osk^6}TP!i&5gx;Y9PF^KX zc;pB235c+gz0kvBKR#1nLX09qT-xbIg@F6f?;&eGCx-*!01jt{wm_e$Bh66piz_d^ zMslGrSen_u1YBAra0~N&FM&20cx`u1ghbDej=v&WNeOb0F)B*x>)s7Ux8BD;e$~_>GhG5o4&}OCb8; zV%)K?1YQdT1KC)u1Mme*`-qkNr#7^4rS6h|hwR!2b^O89U23{=Q}ix%&hOaQjnB*^ zt|OK{D>)dDx7xV>0Tx+o%>D;zgY8wm`xQ%7SncnWWFj`- z>kwq;bBBY&J`IA~r2Q8Hpy<7IQSAq*9nCptAy{X(J7R3#hbw)h0#i7(AFQrI=#9qN z_LFN*{;stCVekM-qt3R{%f{c<9ckZO80yUj>FZQ$-FQ2DUrun|H~QL(ve8@@qF!?Yc_bnVcg7k5RJV>=KAt&c=~VY&FH@IK5noER_j0wd46 z>RwghI!hf|o_Q|Q2>x4-uJGm#M&jrlRa;aR@Rr+w$NVJu9Nj5%a3fjLMR$s~tt~KJZUCQ~S>CB?5h=xM&LKp= zv!lXj>>VN!K?jhXi6vO&{@dfeQ7R&?PH*c^Q0IW9Sj#Lc@uc8iqlndKUCZ~Uwx>fs z3c?8>^9flF#H$=~`svJ+I#zTIa$zX>MOjLY(Aba}{Ll6`aob{7`_{%QyB)f$QXNgK zCT)wH87wfGaOvMfAgm|1T?xCjesL@% zGHTzd`mvZw9(&s#i@bZJhZw=Wd-tg7F5&4Xybw5`1@DBc%+x`lsT44zT#6(Pj;5wL za!d1VU6hoy>*!C2VbW#q2&S2dqI3XFzie)A1AA*cj#A!-sim`^&O0)o6{56V6b5e? z(t!o0ipA1%9W$Qys^vO#k&^P|rF59aP%x6%SnN2dQ{vksGFcvu-YNt@3v2)@Y1`!oa}Zl+O+D^eLZWCsk?vrgKQ z2nSxATKWjm)%dWQ;JU`aQT6MU;r_!Syh_Uf{H&=F(jS3lj)1c3{eHWhq^0p9)bIt0 zfwt9}|n+Ena za3M;A=5EknPVx{3EX6}VpoRDzb@EY%mG}(CN{dvW#EyxwiS9S2FlMGBnB4Wj(}~WK zzy`aAz{JUp6#T^DpFO?zt=m`b-F(Ls*o%j;YQ#1x?Z}1FOkoWM5-nlFK@rXCx6mA6 zcR4-fE{}x^Hst5gz&VJ@hcuOd1EyS_j9xSJ*0W~;{Ty0-DNx8-GYbNBYpjOaja6PG zcsTf8FZivinRn~APM5)456^cuJCB2iN}RP@4RiGsyYicJ-@niIlN68yRQh6Wn2_)6 zt8GA&7ZQ6;*QEK~(jKBF^4@)Bq!EKgUk14O23b_aY|t%U7T33E7zw{X`X2j8u0)3L z>*8VD9iqMT91DNLgw`}fSR26KR(f(L+vTJ~JAUtQ^lWLI6Y+(woynx7ec$2)W17zo z*^ubrG)P7oqE?})oZM)yq~Bv}VGLP3Pmo39xtP?V0b-YKahxZx>x`!5Uw^WQab@*K zLxU!bBrQy{RmQqET(AUhBzVK!#=q6z&eq5cnv8ka#5|KWYTX1#?Hb=X6Cb(E91z=b z!`us-w&8a%hu-TfUurEw6lcTEfeATdA^1T@jrC)2ViU-#{QWC(z$!z+8~P62xmvM; zK5^sn+}D^K`!%62zy*E*y9zFOm&7IIN`fe&JisuB?ItESEGwOkw=CVF>}U2v)UTEf zbJ_v=!`BFwa@zXd8f-8T`*a0AIuvLrC{Pn zCYblW9=zUt-?DUh_EA#w>1~mynnD>iL+cSAMjOo)imTR+M{dB=v5}YLxYk@w7}s(^ zX3nwisAgC&W!o*t<-%>r)Gj6oVMYU4 zFzc(2M@n2UD0j*Xdb_-#`cPU+cldZ-V*5&O7MXB`mLTf81J3Legh+4V@6q_-LwK;j zRyQRluvjrcUx!YO4sRuUp3$p|Syc{q)`0j*k{11Arq?>A5)aL_UIzp6`x7j7vS@(4 zeir`A!#t&myhD}TnI3sLCRr?MYrb30PJg#|NcD}-9M$b+!|IP#m-uq(nGaq73SA#? z?BK~#{$$98U35JU;3+t1F}x&_=BKO*wHI#-9%e)oCXb#_E~^LgHLTe1io!#qo4J{V zwil(-<{m$pXbG(|F5P-?up&q}*x>(2n)WRf(lcI~(!oT8>5c(&wF~T=O7os%yR1zn z?V5=3;aWi`4I#UsgP8!eiFCT?Cto&NXUH2BJ&B(l0yr&3H25rsYG8>nz;-}=zHs{> zHo#Y+&hR_b4)Kk6jDiFltfM3$Xb8zW+T7pz0{k9bX;I-w8y)+ZM@@!(-8Ohgd;!^~ zF33h{=EGP{zoPOXJD!(eVEmYvCuF`MC*eTNVrHP_U{)RA0I0eXIZSaDN3!;e-pQV0 zI?vI>q4@*CHE-x0dz1-FDoJ?4PKY_=m0qUF!oc!dgI7OsE7{I*D)vcM(wshIcwYL)}cq{!F$D&J9uqN0KCoQMsx8jSP1#me;KHb|f zB-CpA2;{y(ia~EIf0(zOaS`RJccfpOrjav`EkronwmDx8MD!6gk zKvBjTxDw$O<&WO71b4R1p7p4HT>fyOL8@X2NWMs1JHIrJ_r1S&d&nB)%9)q;*g#xK z&vFLPR~LxzKe~k#a{NF63?TIPJHw;6X8|R#zQ!-QzH{-Tj4`(fTGGv) zfaDSr6f>|jXbio76q0;PC`a|*6qdHDVrLRKH?0#8Ql}#((O=7l1BUY4ZTlq*7SSs& zZ30^lWY6rd046UICpawak8|QE=`A<$L&L|Ts_Mw#?igGI`<4`ic0KgU&)HF9#7g7W z_W@djk+(Hz44g%YsA?~HP)6&p4NHz}L>1wbDX75Iy zUiOEY^v*VI+eP1%g*SMbFXsd{&)LD_S#!ArOXu^Xw{n8`m_7zr0+W#b6f(pZVp?(ZwF1Jn#l9L zY=W`*E2!=rzLSeEn_L)Nt z&>edR3L9UchQ3HOq~#ebNFq&}Q_%@XqQie31?@a4)QT$Pq}k(1|`|`^TAopXscd=NZBJ^_q(_uA*Agv@~39%3CU`!NqVL{B`AYx#>r)@irZ%Bz#B-C76eH9$= zwi}IN+IF@cQU1D5HT`I!x9izRY2WWRnK3xs$7VgsC4rchCN%m7=7B-oFo*b5kz?3R z$xW2GGQNM-XaH`8FLY>z^(<}g)~BD#nNZ#_6HN*Jsbw_0m7l4alY4*`D`#r|`Aqy? zd^H;m{L`lqJ7IR)%HGF6(#cvnMC`7}PJSMv?$I^aKF_YIEgyD(-O1c~v!>wmY#_e} z|6Jmf0Vf8N3?{o&`MX&9>(gq8U-O=otz7LM&t4dBF5errE`?t{2kg_0wuAR}f7y$) ztf%b}6;TZzGp@Yo3)-;uPO^Dh>5@UwgTmmH4O>H!jhG_r9X|{rbE=&~a>y;8O3`%Y8{29dT>8#}|xaN&8X{1|?0=WWo8UfDXN zm6S60N|v_Y7Ll_i({b3eF@p(B^%#zEYC`|P09TJMXP5m)&X(|am-hJB#mX#xnqFKb z$3kP>*3=HFpljJvF7mh~gYyVs0|xKMn-T0qaK)d2DQtO|Wc5cg~1E96_vse}QftA(Ad#Os3=l|2g1fs*ut0Db90CM)cXziykPzJ6-QAtw?mlnw$+>dQdH25e z1NPqCRn=A9yH~AsQ#gnGXh43N9#dpp|7d~?|L!V$bHG%6H1s5?6u(Bi$Aj$d+9iZ` z`^VFjNC#1|aaYxfEn{1JlhzqJuhZf^xP#Zd#n*M9lZN=qs?19?*cAmg)b{Ii#Bg~b zH@kN9NYdLpWCmIYa1aL!;iX0a?cMiuhC#cWR&;62_?+Vc+xu{mDin+jD?L{5aT`OW z_*dTIyI2amZWAP-a1qNd;)5kGg$e%9u{E)HB^^O0#)xLeYs6e6=|tJ-B@{ zI>G}PCplnqpTgg@B#6ds!wfQ54EJa_-h7*uU71k=hY=K_B`-`o%jHeA%xH4frj;*? zsQKA=4cq&b5wTOQ;nU)IT~P?uAO^>-{tK`0}3Q!PpHugD)A}N-+2gV>g@?@YqtK z6ueSyWh-RUU(`8U?ZTY;GWY8OGUkJJWcoc!_tM3JE9q-j8>G*zt!X@UZ!i|QVq@7- zJR#4kmro;VfYL@3ao+0pg_GGzG;7!NwtwtLxbG7)+@yxSfTB6_XkTI90j%;#Sj0BuJLfD`lS)Vli|j)pGUsS7SQ1(ZhXq+R|F4qx?z zA4-^nSc;nIdH(G4m1k%6`B5dqvj7BD^`3L_*3xuXKe*a}_;CiBFhoIYnXE|)kQVJf zZlW-;DcIT6k9?Mia!1~s*np*!=Z(~Ra0y=f`q0X(tqE)h&1Z^FT!e(B?vTymP;!rL zO>&JstU4#-ku!{LTz#=vv)-R%%?{I+Yz8q#c`E}IxpDQ2ps+i{zUNs48GYp9<=rD# zI`*O+RJ7m3mz@t@2R+uGM1^!BGqOONSADFkA@+k$nRHCGFbXDhEDOjl5SiHEl0Q&R zySN)FW@k1wvv)vI(E@hvE%9W|c@M3d4sc;R>wEPbm^Z?-(N(Dr!RwPFJgmGp4f58n zooVF}ZA}i*o5%U^RCI#$D5BX6K!qwU}MPF;L`hfoeYV_MEl(r z(^e9lT6>NkR!?9r&Ttk5Y>#c0&e-%wC8W+hHZI`2;DLQ$hNxfL^z@u1vv&N9Ki>V) zGEV5bB3pI)!Nn_SoMLh>;IgMa2W!!KXlr;}1kM`9;VVPjS)9Cogd<*l*Z0a|{xSFJ zuJ>e0gQg-aGiizHCHP!<@3|FvyB5K;f|RdQ&OERl1Ml&32kFRj?7&fzl{9WsG%c-3 zyKnEyBNYAmeD<=z6Me)KW9R{A(Z|?b%MsY>>$dA})d<2D*u!jlC;Cd#>7?<3$z}E^ zfrh`;J&l~mAFT(1%3ADOMzw%Yrx^O>a!vSo~sp3Rvh`fQd6-Se2cj$xlWf0M=Gd-~SEtn<8^#f~XCl;`F8%jMn#7YmdrpPdR( zo*|I$2EUfi{){ry>Bzg*^Q@2K@%4<(#F_y!-Do4oiszY%t>X8O(A#C23~JxN<+YL> zNSrnH{j$nAju7H`6xx&J)ditFk1TnQFC_+|R^Jaxx4onUDva<|!5Z!`uA(a{{LY-V zv@I8(u-XJ0V&crCQJD|VZP5GDjKM(bZ%(9&t z`uRMem(tYRl{@oRlK1t=t3(XC*(@`8tk2q83u2NN4Ko9xR%MNzB;|f^*^%}h$PA=S zqFmL1>**-(q^qJbteD{YY-rd%ly4c!{+O~Yt(eLnd3eAT_VrMG5|Y6E)LJR??5YFV zt5dVztKWTPfrganI(T7ttQqA+w3_R=Pf;Y*_;9QAYAu=GkqpsZ^<$V)n}$L7v_o6YJ+T+@EUpvaQ zMSYhckFeB=SmtfqYnMLDTl#dleQ*0o_Nc+dxVgvKed}=+ed@ubSa{fkm_P|vi_gMo zh_@xO$-|h;QIcl@7NU`wBs6>Nb(vp&fWeHm^KK zMP~W*e3&%r`V(N4yrfcE>Udw&v+{6vWZVMlS=y?>^4z`&#d%vfZ(Q&hEZ=V-SR9`` z#fBO8df2sx4v}4LZX($;p{^LEHLh9;Zj%thF<^m}ze0gb1$ca!iSQ0ddnD$MAqZCe zr*_Uc8z1~?Z7ukS&pkdRL3#BZH1GHFtD!tn+XVdj8al?u_HtKgKUPy~NM~XN01$Oj zUMiQi4Vk;%);$A}DsF0O36Q=P8XK*)M1=rj=oKFl_U4b8`vBFD!Wazs8nHy(j5b z`XTwoi)pG2P9jgnXa%cmy%=XP8~;HtW1Z`S%lC)%z570D%Yde3W&4bN_L->GIZ1wOTrzye`;Z!9Z|OLyilVj|=Y#=J_r>{_77I5_CF^T#g- zA4>@{9@Z-}v^B@%zdotU@`ZkLBrmOC3OqG!yk5L@3u+s{Es)1AFs|5qs=a*6?P6@H zJnd=LU;f zWH8d*wLcgbuv=;_qbAKKdWAiY9x^+i z%rcSyyl|JG4lyG{{9%_R2+_e7pKpdA(C)LPCUO%*SE(M7m7$7u<+?i9l=jL+A`Db? zV_srZ8mL@C^Lkowh+j?6!$N2%aCzv8_+)IEoD)xE zdcJyi^qs{mp!rUtv!xOcy7T(>>L&5d@p>F?CLDm=7o{9QVdpoUMgY8q=gKeszvZI#yF%|UTV ztHxu{v|ZIkm-SVs;NwL6-mJT3>4LIX8bBO*l%GmmSf~xkMEOSF2LI>Q34%%(FNj>(MC9U&ch(&mS*FO1mF)YYj4G9I_yHe}I;*f_RV z5|DeB6W#DT%ej^F@m$|+3_SmIfwrs;opv?wxwmZDz#wqKSjo(7K6CP#Vj?(YUOkOl zb!*i(Uo)JB_NeSEUS)z5M%~B^f|JgxL?FM^}&o8wD z8E%@NnIo5rD?JJo*)8}URg$rg+ zod_z+pfvvBE@X<%d>6wQ0UG_#K5B&iEKcqkY+$(!Ne`05m)DJTB&mU1&JRsrxgFWR zu*WIvNG(mijimcPcf8FyR>%0u!;hrmSn1*ffAXpaObU=NoElvWi(mWh;oR)V#1!ptX;+L~cp@S@CBLZzqN&evio%Mw2Vo1G^Bi@e zk%w%(+*Qmk=Q#`eLc)@D)&-+RBY7CJAM?Zpxw$-yMX>UJpFXM`H*m3(R{1Qy6=#{w z2wyHR!Fj$91@PnRd_D-MC>o#eBO?}1j@2~5j z^73}S^j^&Jo=_eVI3KLjs^u3NJ+1yQo(b(e&~yxJn{y(mcQ&#+kVNbo3I)~0xdEhq zO!e1kQUjgZ2Hk!@sxvz@Xm?#AMZMq%hzu1K0KNhEP{}~5gak`pzG*3@vDXjAOMWm8 zi&m48382&#IfC*X8cklrQpZxGmDaMiUnd`0XBE&a1sv}@SRzq1+4(5MM(cjkeBe4&?4m(A8cZ(}mdz;)!k{p?}_!^SRd{z8uz>^2A^)3R- zsypKNZ7yF@R%iF-XpZ>!>F~bmYrHsZ*=ll{C_{V!=N16NlR4H|HSUEZ{ttFOtF#P%Qs?JNs!zmJj?Q!Ec$K#giGr2&AZZ2P%}ZZt}=MorsXrYhe* zVZFlKn6PwfesSJLw2Pgbc??a4O#`q5Okl{VA6sndq35w*s0}W2h`u@vIttKk^Wp2S z(KdgaV@tFfF^^$3eP-950=lrf^L8-PH;hDJ9%Dq&>9)1_5W88Hg*Rm#jJ3HV zI6vIN0RWaIlqJ%cuK86X{^RW1pPS?!E>6GlhmE4{A%2MrlTIB)B|oqt+WiK&r2PfL znOK){;Gj#4*?ahwcLwdvDQjZ<^3;ux zxY6sbW4sGS|684I6=lx6;c@=FrG|>0ogF%Kyy=R2y z{ULvDo~W<{3C_tHADhmqRleNUPeyrgYFsKwxnq1xv5(Y_6X-uD9P=l z1(XKrpQ;^eM_Y9i9y!G2*owZ9>ZO;P?!juS+g&B-(!F31ZzNp66AN8#W-Oq(XA)EK z7)?z0pcjp+xKh}pAoi5Jqqyy&vo%Md&7{-ctXm?&@r^Bj4V=zeOE!l5-gNO60bFz* z_@3u8Y06$tT=s{~A1;b0g3cE+E~4uFmrs~fRojh%qwf*IMbbPjsf=6C^fs!=on0`^}rsgp{aU{exxtjbH-$;y*KOYvN~oz0HM6 zR((?|aKlar4q*F}89We>b9tQShNh7S;6T0Sj?~f!`h3fB8neR5qLZq4&PQ#h);WNs*>z030+s+A~YW8L!p# z)E=*3lzJKk%Z*>o+}iWRzn?6p?r#Xq&VR?*znOYT!=?hfHQBWYE>U?g28gh?w>}t9 z+=gf7H_GO6^i``PFjqDX3~a1w(LTHeUBdSH+4V69L(l1~RYYiQ zhN{9EhD;W5hWOs3CVvkl^y$Pl2}U?3kN=QVheZ{R=h(O|!g$4V_Es(}2Q{l>s)na-**C*i)kbq_{T-QcWXjz<8Dw`ej#QU6s4O@KYV*~Zoj?&(K1qB zQ%M{uEYccfH0_+sYxHtvgT?;)&MKa3NID2F~Ja6Phs^3=6Rq zVH7{xtzHMlMb3`HwVH>m7uv9U(ey>cH^w*|ix;9*ykmW%tueb{l=9|cPQ^;j*(z^S zQ%zab$)^dc&V*Ow`E)>iB$QVKai#Rvkv_78K;dbXO52cb=&8vg@pqEfY_QB-MQtYd3IYVUolCKh6lgJ8im0Cguo^8)L!8?FF0ZK$O=V)hGdrv6w#VFGP5bym|Wv8J6Z z=ar#zBx?zTDzgqMnzpt<3cG)@xzkOUXR$N1Yw^*SvzQa#Wl(~#$43)gOAa0rmkHsI z0Wa9$GmMI@eR~bhQQ%D6txRiZhjRGFbeP&8Na6g(^U56}jiyUl)^LLvns8$C$6yx@ ztYiNGJ~2NgT2}L_|ve`{fjqA1C7pTZ^Aga=KaB$(pbF zQ4i%02ly}f0uSBSVx5(^pT)0yJL=#a;;M{d8SCe_#u?rs@XZx;iRlJ7;eN#RopHPo zi$=lsK>PgKQl2iDoK-DASa)~22JsNZ;Hsjd+!9L~7COQj+Fz`3>(f~(v7fBEm{1qD z{Bu>q z8p{SZuhqswcVT6ugql!Hy=O|YS&8=B~yG@IBDS2NN})s#)zf(JS^`{-50bAFPz>+9%ke9@FcG>xXK{11CCHb6H2wOhAU zbxGsrkcTi!VKq8Uap<0FS0GKBxznK}^gSJH&YqbK6&~@hG3!87G>srBHNQPGLJl%_ znTzy(>?qB7mCZo-3(X&nFiuRmKQqbAq1#a|@)Dfp!ca6EMOR%e!pS6z!w{5U7}Ix@ zhj~XqM)uF?FIhpXRH_S{Bw7H31t&)ABAC_nH@vjM?_s&g{FJj4Q$NXym2c- zG%ANYf%kG6_Z5!dITCxxxd-i9i8e?@#{it15HOkS3XQADd^&oaR7%) zMy~$|)Bm)eIC4K#8alqG%rA4zvg{Ckd8lIuair&Q9`ln?dHG`Spf)>a@2^b-l;PyJ zo(kfIPeAnH9~;rm|LTWoU#HlYMpzY$V_y~`OccXXq6lMJy&pWoMvy?b1ygw{vH!&! z^P*Wu3)DQhZ6n`LhFWbQ(s^Nj?qb@^7mpLkNADfhyApQ!JK&F_v zE*r*7as9Fr%Qx~bW}$aEi3ugX3lj*EZweE!gd?tqCAY1}@Htx|c`h03Me%xbG;USe zU|n$A)P}hD9EJ}Td!IeJIZBayOG7IvCRdfV8)S4?5V`AS;2hSubJ|fQO9P7uQs{Oa z!kLon@Bv~;{oEc+9lq+LmX;*TiDyaD&1Fh+oo%#cL&r}0VmAjlIn!q%fk%D?^Akn^?mB0_L>2k4wFs{u;j%}aA6 z)ZqWpUBr~xuDdThVgc2Ui7SWEpM@7**%HuJJp`Ab9(Fj2QF6jK;EUu~F05!V0Dn{5 zpo58Cw`HMP1%IE`AF$ zVL!@GS|5`t*~LawL`s>anl^8X9QfauHOADH;wLL?97L@#VJ;M35~h-=KT&hZR(Q_l zkl(aT|HRAXXbws3@)N`dZcHMqy%oYXdJ5JDHmsQ%5CIoU^fMH{F8s#Zjpe--p)PD- zkK}Su@i@JZHyc9?Dux&4(|6V?k8P(zWwl?N&Mt~hd(nvcZG5C6|DS_xc>zMrs5ged zn;Xk82ilZETY%!{qZonSS$?t5y5_t9Z17~@FXJ>zAF z5nO^qy@@Xe#ZeY-N;D*E!`r*niM z@BP-dsX>FZn|O~W>l$=$C^0?K#|j(ZkGuK3AU6>eCghUUn*k)xYT|-xx!50QH zV&*rc>py5j(OtPjYT{)=C*U@+(~ROS^u_uK%4?d$_yv2i!qUj|Y1PrPvjTuuYGSQ8 zD!i5WMnCfHy%lU=m|tj?!$~?@EI8+dNf;$DnLOOlYY}9Tlshzd*b^mINA^`-XkpGjZN~ z6!u@X({`r0sKAn)FmkaFKuqRLFV_D+Gdx414ZOIdW1yvrstN^hy^WL|d=rpL%R|^L znIXWsUOsumnwl^Cy(yq#D#wzx9}p7C7KITUjb)0jk27HIm_+s_E0h|9o{OjmrKS%2 z7u>juJ<>#dfn9w;RjB1CzpA;1FkX#`#$qBV!WBa0tvbyMrb4re-|0I8vIRJZEk`dJ zn;;LqBAz94jI%QMirl|-LWioD=JH~Ovg%BrwGv1W?EqrSZgL2`^M-9O!QK!sBWGPo zIMergHJ~OiYpvvA4jFtAm~-Upk$B_TwXk=cGCY|LRlE4SKh72pM(h^-cf(n)3B#w% z{g~jFdJYv>M*KY*s3@uB=6AF$VSTX3g8!V~`2q=)?Q8d&WDs1T_tfq1q36mMJ}1bI zENx#q;}P!Fj@n8e5P`9qnd0JyFjzg3DGAHjLJ4EJyCkZ;WZmd*mgmLUlHwC`U!C6B zDBj#6rSYRpc-cO?Mmg!l!gtFEzF5@S8f)t*mT@os+Ka5 z=&0#X)oSG+TJ9)?Esaz4PT$*@Ema$8MP5⁡t&X5SF>3(rb7L1~h1r<~oj%pyU|u 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