├── .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) 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 | } --------------------------------------------------------------------------------