├── .gitignore
├── src
└── main
│ └── java
│ └── chat
│ ├── events
│ ├── Event.java
│ ├── Login.java
│ ├── Logout.java
│ ├── GetChatLog.java
│ ├── ChatMessage.java
│ └── ChatLog.java
│ ├── actors
│ ├── ChatService.java
│ ├── Session.java
│ ├── ChatClient.java
│ ├── RedisChatStorage.java
│ └── ChatServer.java
│ └── Runner.java
├── README.textile
└── pom.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | *~
2 | target/
3 | .settings/
4 | .project
5 | .classpath
6 |
7 |
--------------------------------------------------------------------------------
/src/main/java/chat/events/Event.java:
--------------------------------------------------------------------------------
1 | package chat.events;
2 |
3 | import java.io.Serializable;
4 |
5 | /**
6 | * ChatServer's internal events.
7 | */
8 | public abstract class Event implements Serializable {
9 |
10 | private static final long serialVersionUID = -1354942905395394545L;
11 |
12 | }
--------------------------------------------------------------------------------
/src/main/java/chat/events/Login.java:
--------------------------------------------------------------------------------
1 | package chat.events;
2 |
3 | public class Login extends Event {
4 |
5 | private static final long serialVersionUID = 1535536798818479222L;
6 | private String user = null;
7 |
8 | public Login(String user) {
9 | this.user = user;
10 | }
11 |
12 | public String getUser() {
13 | return user;
14 | }
15 |
16 | }
--------------------------------------------------------------------------------
/src/main/java/chat/events/Logout.java:
--------------------------------------------------------------------------------
1 | package chat.events;
2 |
3 | public class Logout extends Event {
4 |
5 | private static final long serialVersionUID = -1251115846831134471L;
6 | private String user = null;
7 |
8 | public Logout(String user) {
9 | this.user = user;
10 | }
11 |
12 | public String getUser() {
13 | return user;
14 | }
15 | }
--------------------------------------------------------------------------------
/src/main/java/chat/events/GetChatLog.java:
--------------------------------------------------------------------------------
1 | package chat.events;
2 |
3 | public class GetChatLog extends Event {
4 |
5 | private static final long serialVersionUID = -7000786115556740575L;
6 | private String from = null;
7 |
8 | public GetChatLog(String from) {
9 | this.from = from;
10 | }
11 |
12 | public String getFrom() {
13 | return from;
14 | }
15 | }
--------------------------------------------------------------------------------
/src/main/java/chat/actors/ChatService.java:
--------------------------------------------------------------------------------
1 | package chat.actors;
2 |
3 |
4 | import akka.actor.Actors;
5 |
6 | /**
7 | * Class encapsulating the full Chat Service. Start service by invoking:
8 | *
9 | *
10 | * val chatService = Actor.actorOf[ChatService].start
11 | *
12 | */
13 | public class ChatService extends ChatServer {
14 | public void preStart() {
15 | Actors.remote().start("localhost", 2552);
16 | Actors.remote().register("chat:service", getContext());
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/chat/events/ChatMessage.java:
--------------------------------------------------------------------------------
1 | package chat.events;
2 |
3 | public class ChatMessage extends Event {
4 |
5 | private static final long serialVersionUID = -764895205230020563L;
6 | private String from = null;
7 | private String message = null;
8 |
9 | public ChatMessage(String from, String message) {
10 | this.from = from;
11 | this.message = message;
12 | }
13 |
14 | public String getFrom() {
15 | return from;
16 | }
17 |
18 | public String getMessage() {
19 | return message;
20 | }
21 | }
--------------------------------------------------------------------------------
/src/main/java/chat/events/ChatLog.java:
--------------------------------------------------------------------------------
1 | package chat.events;
2 |
3 | import java.util.List;
4 |
5 | public class ChatLog extends Event {
6 |
7 | private static final long serialVersionUID = -7318212379604445117L;
8 | private List log = null;
9 |
10 | public ChatLog(List log) {
11 | this.log = log;
12 | }
13 |
14 | public List getLog() {
15 | return log;
16 | }
17 |
18 | public String getLogString(String separator) {
19 | String result = "";
20 | for (String logEntry : log)
21 | result = result + separator + logEntry;
22 | return result;
23 | }
24 | }
--------------------------------------------------------------------------------
/README.textile:
--------------------------------------------------------------------------------
1 | h3. Brief
2 |
3 | A sample chat application using the Java API for Akka
4 | Basically a port of:
5 | "https://github.com/jboner/akka/tree/master/akka-samples/akka-sample-chat/":https://github.com/jboner/akka/tree/master/akka-samples/akka-sample-chat/
6 |
7 | Akka version = 1.0-RC5
8 | Akka API = Java
9 |
10 | h3. Instructions
11 |
12 | # Download Redis: "http://code.google.com/p/redis/downloads/list":http://code.google.com/p/redis/downloads/list
13 | # Build Redis: ‘make install’
14 | # Run Redis: ‘./redis-server’
15 | # Run akka_chat_java: "https://github.com/alexaverbuch/akka_chat_java/blob/master/src/main/java/chat/Runner.java":https://github.com/alexaverbuch/akka_chat_java/blob/master/src/main/java/chat/Runner.java
16 |
17 | h3. Acknowledgements
18 |
19 | Developed: "Alex Averbuch":https://github.com/alexaverbuch
20 | Migrated to latest Akka version: "Matt Abrams":https://github.com/abramsm
--------------------------------------------------------------------------------
/src/main/java/chat/Runner.java:
--------------------------------------------------------------------------------
1 | package chat;
2 |
3 | import akka.actor.Actor;
4 | import akka.actor.ActorRef;
5 | import akka.actor.Actors;
6 | import akka.actor.UntypedActor;
7 | import chat.actors.ChatClient;
8 | import chat.actors.ChatService;
9 |
10 | /**
11 | * Test runner emulating a chat session.
12 | */
13 | public class Runner {
14 | public static void main(String[] args) {
15 | // Create ChatServer
16 | ActorRef server = Actors.actorOf((Class extends Actor>) ChatService.class);
17 | server.start();
18 |
19 | // Create ChatClient
20 | ChatClient client = new ChatClient("Alex");
21 | client.login();
22 |
23 | client.post("Hi there...");
24 | System.out.println("***\nCHAT LOG:\n\t"
25 | + client.getChatLog().getLogString("\n\t") + "\n***");
26 |
27 | client.post("Hi again...");
28 | System.out.println("***\nCHAT LOG:\n\t"
29 | + client.getChatLog().getLogString("\n\t") + "\n***");
30 |
31 | client.logout();
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/chat/actors/Session.java:
--------------------------------------------------------------------------------
1 | package chat.actors;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | import akka.actor.ActorRef;
7 | import akka.actor.UntypedActor;
8 | import chat.events.ChatMessage;
9 | import chat.events.GetChatLog;
10 |
11 | /**
12 | * Internal chat client session.
13 | */
14 | public class Session extends UntypedActor {
15 | private ActorRef storage = null;
16 | private final long loginTime = System.currentTimeMillis();
17 | private List userLog = new ArrayList();
18 |
19 | public Session(String user, ActorRef storage) {
20 | this.storage = storage;
21 | log().logger().info(
22 | "New session for user [%s] has been created at [%s]", user,
23 | loginTime);
24 | }
25 |
26 | public void onReceive(final Object msg) throws Exception {
27 | if (msg instanceof ChatMessage) {
28 | userLog.add(((ChatMessage) msg).getMessage());
29 | storage.sendOneWay(msg);
30 | } else if (msg instanceof GetChatLog) {
31 | storage.forward(msg, getContext());
32 | }
33 |
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/chat/actors/ChatClient.java:
--------------------------------------------------------------------------------
1 | package chat.actors;
2 |
3 | import akka.actor.ActorRef;
4 | import akka.actor.Actors;
5 | import chat.events.ChatLog;
6 | import chat.events.ChatMessage;
7 | import chat.events.GetChatLog;
8 | import chat.events.Login;
9 | import chat.events.Logout;
10 |
11 | /**
12 | * Chat client.
13 | */
14 | public class ChatClient {
15 |
16 | private String name = null;
17 | private ActorRef chat = null;
18 |
19 | public ChatClient(String name) {
20 | this.name = name;
21 |
22 | // starts and connects the client to the remote server
23 | this.chat = Actors.remote().actorFor("chat:service", "localhost", 2552);
24 | }
25 |
26 | public void login() {
27 | chat.sendOneWay(new Login(name));
28 | }
29 |
30 | public void logout() {
31 | chat.sendOneWay(new Logout(name));
32 | }
33 |
34 | public void post(String message) {
35 | chat.sendOneWay(new ChatMessage(name, name + ": " + message));
36 | }
37 |
38 | public ChatLog getChatLog() {
39 | return (ChatLog) chat.sendRequestReply(new GetChatLog(name));
40 | }
41 | }
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | rabb
5 | akka_chat
6 | 0.0.1-SNAPSHOT
7 | akka_chat_java
8 |
9 |
10 | Akka
11 | Akka Maven2 Repository
12 | http://akka.io/repository/
13 |
14 |
15 |
16 |
17 |
18 | org.apache.maven.plugins
19 | maven-compiler-plugin
20 | 2.0.2
21 |
22 | 1.6
23 | 1.6
24 |
25 |
26 |
27 |
28 |
29 |
30 | se.scalablesolutions.akka
31 | akka-actor
32 | 1.0-RC5
33 |
34 |
35 | se.scalablesolutions.akka
36 | akka-persistence-redis
37 | 1.0-RC5
38 |
39 |
40 |
--------------------------------------------------------------------------------
/src/main/java/chat/actors/RedisChatStorage.java:
--------------------------------------------------------------------------------
1 | package chat.actors;
2 |
3 | import java.io.UnsupportedEncodingException;
4 | import java.util.ArrayList;
5 | import java.util.List;
6 |
7 | import akka.actor.UntypedActor;
8 | import akka.stm.Atomic;
9 | import chat.events.ChatLog;
10 | import chat.events.ChatMessage;
11 | import chat.events.GetChatLog;
12 |
13 | /**
14 | * Redis-backed chat storage implementation.
15 | */
16 | public class RedisChatStorage extends UntypedActor {
17 |
18 | private final String CHAT_LOG = "akka.chat.log";
19 | private List chatLog = null;
20 |
21 | // FIXME Temp. PersistentVector seems to be buggy in 1.0-M1
22 | // private PersistentVector chatLog = null;
23 |
24 | public RedisChatStorage() {
25 | // FIXME Find out how to do 'self.lifeCycle = Permanent' in Java API
26 | // getContext().setLifeCycle(permanent());
27 |
28 | // FIXME Temp. RedisStorage seems to be buggy in 1.0-M1
29 | chatLog = new ArrayList();
30 | // chatLog = RedisStorage.newVector(CHAT_LOG).asJava();
31 | // chatLog = RedisStorage.newVector(CHAT_LOG);
32 |
33 | log().logger().info("Redis-based chat storage is starting up...");
34 | }
35 |
36 | public void onReceive(final Object msg) throws Exception {
37 | if (msg instanceof ChatMessage) {
38 | log().logger().debug("New chat message [%s]",
39 | ((ChatMessage) msg).getMessage());
40 |
41 | new Atomic() {
42 | public Object atomically() {
43 | try {
44 | chatLog.add(((ChatMessage) msg).getMessage().getBytes(
45 | "UTF-8"));
46 | } catch (UnsupportedEncodingException e) {
47 | e.printStackTrace();
48 | }
49 | return null;
50 | }
51 | }.execute();
52 |
53 | } else if (msg instanceof GetChatLog) {
54 | List messageList = new Atomic>() {
55 | public List atomically() {
56 | List messages = new ArrayList();
57 |
58 | for (byte[] messageBytes : chatLog)
59 | try {
60 | messages.add(new String(messageBytes, "UTF-8"));
61 | } catch (UnsupportedEncodingException e) {
62 | e.printStackTrace();
63 | }
64 | return messages;
65 | }
66 | }.execute();
67 | getContext().replyUnsafe(new ChatLog(messageList));
68 | }
69 |
70 | }
71 |
72 | public void postRestart(Throwable reason) {
73 | // FIXME Temp. RedisStorage seems to be buggy in 1.0-M1
74 | chatLog = new ArrayList();
75 | // chatLog = RedisStorage.getVector(CHAT_LOG).asJava();
76 | // chatLog = RedisStorage.getVector(CHAT_LOG);
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/chat/actors/ChatServer.java:
--------------------------------------------------------------------------------
1 | package chat.actors;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 |
6 | import akka.actor.ActorRef;
7 | import akka.actor.Actors;
8 | import akka.actor.UntypedActor;
9 | import akka.actor.UntypedActorFactory;
10 | import akka.config.Supervision;
11 | import chat.events.ChatMessage;
12 | import chat.events.GetChatLog;
13 | import chat.events.Login;
14 | import chat.events.Logout;
15 |
16 | /**
17 | * Chat server. Manages sessions and redirects all other messages to the Session
18 | * for the client.
19 | */
20 | public class ChatServer extends UntypedActor {
21 | private ActorRef storage = null;
22 | private SessionManagement sessionMgr = null;
23 | private ChatManagement chatMgr = null;
24 |
25 | public ChatServer() {
26 | // Creates and links a RedisChatStorage
27 | storage = getContext().spawnLink(RedisChatStorage.class);
28 |
29 | Supervision.FaultHandlingStrategy faultHandler = new Supervision.OneForOneStrategy(
30 | null, // exceptions to handle
31 | null, // max restart retries
32 | null); // within time in ms
33 | getContext().setFaultHandler(faultHandler);
34 |
35 | sessionMgr = new SessionManagement(getContext(), storage);
36 | chatMgr = new ChatManagement(getContext(), sessionMgr);
37 |
38 | logger().info("Chat server is starting up...");
39 | }
40 |
41 | public void onReceive(final Object msg) {
42 | sessionMgr.handleReceive(msg);
43 | chatMgr.handleReceive(msg);
44 | }
45 |
46 | public void postStop() {
47 | logger().info("Chat server is shutting down...");
48 | sessionMgr.shutdownSessions();
49 | getContext().unlink(storage);
50 | storage.stop();
51 | }
52 |
53 | /**
54 | * Implements user session management.
55 | */
56 | private class SessionManagement {
57 | private ActorRef self = null;
58 | private ActorRef storage = null;
59 | private Map sessions = new HashMap();
60 |
61 | public SessionManagement(ActorRef self, ActorRef storage) {
62 | this.self = self;
63 | this.storage = storage;
64 | }
65 |
66 | public ActorRef getSession(String username) {
67 | return sessions.get(username);
68 | }
69 |
70 | public void handleReceive(final Object msg) {
71 | if (msg instanceof Login) {
72 | final String username = ((Login) msg).getUser();
73 | ActorRef session = Actors.actorOf(new UntypedActorFactory() {
74 | public UntypedActor create() {
75 | return new Session(username, storage);
76 | }
77 | });
78 | session.start();
79 | sessions.put(((Login) msg).getUser(), session);
80 | logger().info("User [%s] has logged in", username);
81 |
82 | } else if (msg instanceof Logout) {
83 | String username = ((Logout) msg).getUser();
84 | ActorRef session = sessions.get(username);
85 | session.stop();
86 | sessions.remove(username);
87 | logger().info("User [%s] has logged out", username);
88 | }
89 | }
90 |
91 | public void shutdownSessions() {
92 | for (ActorRef session : sessions.values())
93 | session.stop();
94 | }
95 | }
96 |
97 | /**
98 | * Implements chat management, e.g. chat message dispatch.
99 | */
100 | private class ChatManagement {
101 | private ActorRef self = null;
102 | private SessionManagement sessionMgr = null;
103 |
104 | public ChatManagement(ActorRef self, SessionManagement sessionMgr) {
105 | this.self = self;
106 | this.sessionMgr = sessionMgr;
107 | }
108 |
109 | public void handleReceive(final Object msg) {
110 | if (msg instanceof ChatMessage)
111 | sessionMgr.getSession(((ChatMessage) msg).getFrom())
112 | .sendOneWay(msg);
113 | else if (msg instanceof GetChatLog)
114 | sessionMgr.getSession(((GetChatLog) msg).getFrom()).forward(
115 | msg, self);
116 | }
117 |
118 | }
119 | }
--------------------------------------------------------------------------------