diff --git a/akka-http/pom.xml b/akka-http/pom.xml new file mode 100644 index 0000000000..05e50d2229 --- /dev/null +++ b/akka-http/pom.xml @@ -0,0 +1,48 @@ + + + + 4.0.0 + akka-http + akka-http + + + parent-modules + com.baeldung + 1.0.0-SNAPSHOT + + + + + com.typesafe.akka + akka-http_2.12 + ${akka.http.version} + + + com.typesafe.akka + akka-stream_2.12 + ${akka.stream.version} + + + com.typesafe.akka + akka-http-jackson_2.12 + ${akka.http.version} + + + com.typesafe.akka + akka-http-testkit_2.12 + ${akka.http.version} + test + + + + + UTF-8 + UTF-8 + 1.8 + 10.0.11 + 2.5.11 + + diff --git a/akka-http/src/main/java/com/baeldung/akkahttp/AkkaHttpServer.java b/akka-http/src/main/java/com/baeldung/akkahttp/AkkaHttpServer.java new file mode 100644 index 0000000000..03992b6a36 --- /dev/null +++ b/akka-http/src/main/java/com/baeldung/akkahttp/AkkaHttpServer.java @@ -0,0 +1,39 @@ +package com.baeldung.akkahttp; + +import java.util.concurrent.CompletionStage; + +import akka.NotUsed; +import akka.actor.ActorRef; +import akka.actor.ActorSystem; +import akka.http.javadsl.ConnectHttp; +import akka.http.javadsl.Http; +import akka.http.javadsl.ServerBinding; +import akka.http.javadsl.model.HttpRequest; +import akka.http.javadsl.model.HttpResponse; +import akka.http.javadsl.server.AllDirectives; +import akka.stream.ActorMaterializer; +import akka.stream.javadsl.Flow; + +public class AkkaHttpServer extends AllDirectives { + + private final UserRoutes userRoutes; + + public AkkaHttpServer(ActorSystem system, ActorRef userRegistryActor) { + userRoutes = new UserRoutes(system, userRegistryActor); + } + + public static void main(String[] args) throws Exception { + ActorSystem system = ActorSystem.create("helloAkkaHttpServer"); + final ActorMaterializer materializer = ActorMaterializer.create(system); + + ActorRef userActorRef = system.actorOf(UserActor.props(), "userActor"); + + AkkaHttpServer app = new AkkaHttpServer(system, userActorRef); + + final Flow routeFlow = app.userRoutes.routes().flow(system, materializer); + + final CompletionStage binding = Http.get(system).bindAndHandle(routeFlow, + ConnectHttp.toHost("localhost", 8080), materializer); + + System.out.println("Server is online at http://localhost:8080/"); + } \ No newline at end of file diff --git a/akka-http/src/main/java/com/baeldung/akkahttp/User.java b/akka-http/src/main/java/com/baeldung/akkahttp/User.java new file mode 100644 index 0000000000..9b5b95ebc1 --- /dev/null +++ b/akka-http/src/main/java/com/baeldung/akkahttp/User.java @@ -0,0 +1,30 @@ +package com.baeldung.akkahttp; + +/** + * User Entity + * + */ +public class User { + + private final String name; + private final String address; + + public User() { + this.name = ""; + this.address = ""; + } + + public User(String name, String address) { + this.name = name; + this.address = address; + } + + public String getName() { + return name; + } + + public String getAddress() { + return address; + } + +} \ No newline at end of file diff --git a/akka-http/src/main/java/com/baeldung/akkahttp/UserActor.java b/akka-http/src/main/java/com/baeldung/akkahttp/UserActor.java new file mode 100644 index 0000000000..f6c7015070 --- /dev/null +++ b/akka-http/src/main/java/com/baeldung/akkahttp/UserActor.java @@ -0,0 +1,60 @@ +package com.baeldung.akkahttp; + +import java.util.ArrayList; +import java.util.List; + +import akka.actor.AbstractActor; +import akka.actor.Props; +import akka.event.Logging; +import akka.event.LoggingAdapter; + +/** + * User Actor + * + */ +public class UserActor extends AbstractActor { + + LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this); + + /** + * In memory List to hold the users added via POST operation. + */ + private final List users = new ArrayList<>(); + + /** + * To define UserActor Props. + * + * @return Props + */ + static Props props() { + return Props.create(UserActor.class); + } + + /** + * createReveive() implementation + */ + @Override + public Receive createReceive() { + return receiveBuilder().match(UserRegistryMessages.GetUsers.class, getUsers -> getSender().tell(new Users(users), getSelf())) + .match(UserRegistryMessages.CreateUser.class, createUser -> { + users.add(createUser.getUser()); + getSender().tell(new UserRegistryMessages.ActionPerformed(String.format("User %s created.", createUser.getUser() + .getName())), getSelf()); + }) + .match(UserRegistryMessages.GetUser.class, getUser -> { + getSender().tell(users.stream() + .filter(user -> user.getName() + .equals(getUser.getName())) + .findFirst(), getSelf()); + }) + .match(UserRegistryMessages.DeleteUser.class, deleteUser -> { + users.removeIf(user -> user.getName() + .equals(deleteUser.getName())); + getSender().tell(new UserRegistryMessages.ActionPerformed(String.format("User %s deleted.", deleteUser.getName())), getSelf()); + + }) + .matchAny(o -> log.info("received unknown message")) + .build(); + } + +} diff --git a/akka-http/src/main/java/com/baeldung/akkahttp/UserRegistryMessages.java b/akka-http/src/main/java/com/baeldung/akkahttp/UserRegistryMessages.java new file mode 100644 index 0000000000..831819849a --- /dev/null +++ b/akka-http/src/main/java/com/baeldung/akkahttp/UserRegistryMessages.java @@ -0,0 +1,70 @@ +package com.baeldung.akkahttp; + +/** + * Defines all messages related to User Actor + * + */ +public interface UserRegistryMessages { + + class GetUsers implements Serializable { + + private static final long serialVersionUID = 1L; + } + + class ActionPerformed implements Serializable { + + private static final long serialVersionUID = 1L; + + private final String description; + + public ActionPerformed(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } + } + + class CreateUser implements Serializable { + + private static final long serialVersionUID = 1L; + private final User user; + + public CreateUser(User user) { + this.user = user; + } + + public User getUser() { + return user; + } + } + + class GetUser implements Serializable { + + private static final long serialVersionUID = 1L; + private final String name; + + public GetUser(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + class DeleteUser implements Serializable { + + private static final long serialVersionUID = 1L; + private final String name; + + public DeleteUser(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } +} diff --git a/akka-http/src/main/java/com/baeldung/akkahttp/UserRoutes.java b/akka-http/src/main/java/com/baeldung/akkahttp/UserRoutes.java new file mode 100644 index 0000000000..aa3c9b612f --- /dev/null +++ b/akka-http/src/main/java/com/baeldung/akkahttp/UserRoutes.java @@ -0,0 +1,98 @@ +package com.baeldung.akkahttp; + +import java.time.Duration; +import java.util.Optional; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.TimeUnit; + +import akka.actor.ActorRef; +import akka.actor.ActorSystem; +import akka.event.Logging; +import akka.event.LoggingAdapter; +import akka.http.javadsl.marshallers.jackson.Jackson; +import akka.http.javadsl.model.StatusCodes; +import akka.http.javadsl.server.AllDirectives; +import akka.http.javadsl.server.PathMatchers; +import akka.http.javadsl.server.Route; +import akka.pattern.PatternsCS; +import akka.util.Timeout; + +public class UserRoutes extends AllDirectives { + + private final ActorRef userActorRef; + private final LoggingAdapter log; + Timeout timeout = new Timeout(Duration.create(5, TimeUnit.SECONDS)); + + public UserRoutes(ActorSystem system, ActorRef userActorRef) { + this.userActorRef = userActorRef; + log = Logging.getLogger(system, this); + } + + public Route routes() { + return pathPrefix("users", () -> route(getOrPostUsers(), path(PathMatchers.segment(), name -> route(getUser(name), deleteUser(name))))); + } + + /** + * Defines and returns a route to get the user by name + * + * @param name + * @return Route + */ + private Route getUser(String name) { + + Route getRoute = get(() -> { + CompletionStage> user = PatternsCS.ask(userActorRef, new UserRegistryMessages.GetUser(name), timeout) + .thenApply(obj -> (Optional) obj); + + return onSuccess(() -> user, performed -> { + if (performed.isPresent()) + return complete(StatusCodes.OK, performed.get(), Jackson.marshaller()); + else + return complete(StatusCodes.NOT_FOUND); + }); + }); + return getRoute; + } + + /** + * Defines and returns a Route to delete the user by name. + * + * @param name + * @return Route + */ + private Route deleteUser(String name) { + + Route deleteRoute = delete(() -> { + CompletionStage userDeleted = PatternsCS.ask(userActorRef, new UserRegistryMessages.DeleteUser(name), timeout) + .thenApply(obj -> (ActionPerformed) obj); + + return onSuccess(() -> userDeleted, performed -> { + log.info("Deleted user [{}]: {}", name, performed.getDescription()); + return complete(StatusCodes.OK, performed, Jackson.marshaller()); + }); + }); + + return deleteRoute; + } + + /** + * Defines two routes, one to get all users and the other is to post users. + * + * @return + */ + private Route getOrPostUsers() { + return pathEnd(() -> route(get(() -> { + CompletionStage futureUsers = PatternsCS.ask(userActorRef, new UserRegistryMessages.GetUsers(), timeout) + .thenApply(obj -> (Users) obj); + return onSuccess(() -> futureUsers, users -> complete(StatusCodes.OK, users, Jackson.marshaller())); + }), post(() -> entity(Jackson.unmarshaller(User.class), user -> { + CompletionStage userCreated = PatternsCS.ask(userActorRef, new CreateUser(user), timeout) + .thenApply(obj -> (ActionPerformed) obj); + return onSuccess(() -> userCreated, performed -> { + log.info("Created user [{}]: {}", user.getName(), performed.getDescription()); + return complete(StatusCodes.CREATED, performed, Jackson.marshaller()); + }); + })))); + } + +} diff --git a/akka-http/src/main/java/com/baeldung/akkahttp/Users.java b/akka-http/src/main/java/com/baeldung/akkahttp/Users.java new file mode 100644 index 0000000000..e5e6c62c06 --- /dev/null +++ b/akka-http/src/main/java/com/baeldung/akkahttp/Users.java @@ -0,0 +1,25 @@ +package com.baeldung.akkahttp; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class is used to send the response enclosed by Users. + * + */ +public class Users { + + private final List users; + + public Users() { + this.users = new ArrayList<>(); + } + + public Users(List users) { + this.users = users; + } + + public List getUsers() { + return users; + } +} diff --git a/pom.xml b/pom.xml index 302530fd24..d0d0fa7049 100644 --- a/pom.xml +++ b/pom.xml @@ -1254,6 +1254,7 @@ aws aws-lambda akka-streams + akka-http algorithms annotations apache-cxf