Skip to main content

Actors in Java: Coding the Fortune Cookie Application

Posted by jcmansigian on October 22, 2013 at 3:42 AM PDT
Introduction

This week I am presenting a coding of a simple two actor application designed to show actor creation, message passing, and actor termination. This application is called the "Fortune Cookie Application". It is the next state of the art beyond the HelloWorld program. Despite its simplicity this application shows the elements that every actor application uses.


What We Will Be Using

This exercise and all that will follow in the coming months will make use of the Akka actor libraries and microkernel from the Typesafe company. By way of full disclosure at the time of this writing I am not employed by or affiliated in any compensable manner with the Typesafe company. I am a user of their Akka technology and choose it based solely on merit. Additionally we will be coding in Java exclusively, using Java JDK 7 or later for all of our work.


Getting Started

First it is necessary to obtain the Akka library jars that we will use. These can be downloaded free of charge from the Typesafe company at this download site. Please unpack the library jars for Akka into any convenient directory of your choice that has appropriate permissions set to allow read access for the account that will be used for this exercise. Once that is done check the list of Akka 2.2.1 jars below to make sure you have what you need. You will note that some of them are Scala related jars. Because the Akka middleware and microkernel are written in the Scala language it is necessary to retain these jars even though all of our own work will be coded in Java.



Akka Jar List
akka-actor_2.10-2.2.1.jar
akka-agent_2.10-2.2.1.jar
akka-camel_2.10-2.2.1.jar
akka-channels-experimental_2.10-2.2.1.jar
akka-cluster_2.10-2.2.1.jar
akka-contrib_2.10-2.2.1.jar
akka-dataflow_2.10-2.2.1.jar
akka-durable-mailboxes-2.2.1.jar
akka-file-mailbox_2.10-2.2.1.jar
akka_jar_list
akka-kernel_2.10-2.2.1.jar
akka-mailboxes-common_2.10-2.2.1.jar
akka-multi-node-testkit_2.10-2.2.1.jar
akka-remote_2.10-2.2.1.jar
akka-remote-tests_2.10-2.2.1.jar
akka-slf4j_2.10-2.2.1.jar
akka-testkit_2.10-2.2.1.jar
akka-transactor_2.10-2.2.1.jar
akka-zeromq_2.10-2.2.1.jar
camel-core-2.10.3.jar
config-1.0.2.jar
jna-3.0.9.jar
jnr-constants-0.8.2.jar
netty-3.6.6.Final.jar
protobuf-java-2.4.1.jar
scalabuff-runtime_2.10-1.2.0.jar
scala-library.jar
scala-reflect-2.10.2.jar
scala-stm_2.10-0.7.jar
slf4j-api-1.7.2.jar
uncommons-maths-1.2.2a.jar
zeromq-scala-binding_2.10-0.0.7.jar




Obtaining Source


The next thing to do is to obtain the source for our application. This can be found in two places. You can download the file attachment to this blog post entitled 'FortuneCookie-src.zip'. I have also created an actor learning project and committed the "Fortune Cookie Application" source to it. You can find the project at Learning Actors in Java. The thing to note is that if you obtain source from either the attached file or from the project you will get compile and go bash scripts that were written for my use on my personal workstation which is a Linux platform. So adjust these for your own operating system if you wish to use scripts but also if you prefer to work with an IDE then by all means do so. The compile and go commands shown below in this post are generic javac and java commands. In all cases it is necessary for you to adjust the classpath of commands to reflect where you have deployed the Akka jar files.


Actor Creation

The most common and useful actors are objects instantiated from a class that extends UntypedActor. Actors are organized into actor systems that are hierarchical in structure. Extending UntypedActor creates an actor that is created by an actor system. That actor system is its parent and supervisor. The actor created thus operates at the top level in the hierarchy of the actor system to which it belongs. Its context is the actor system in which it was created. The notion of context is crucial to understanding supervision in actor interaction. The fact that the diner actor is created in the context of a certain actor system makes that actor system its supervisor. There can be more than one actor at the top level within an actor system. See the line below in the Diner.java source.

public class Diner extends UntypedActor



The actor we have created here models a dining patron who will interact with a waiter. The diner actor is top level and will be superior to a child actor that will model the waiter. Child actors are created by parent actors using methods that configure and manufacture the child actor. See the line below in the source Diner.java.

waiter = getContext().actorOf( Props.create( Waiter.class), "waiter");



These method calls are found in all actor applications. A lot is going on here in one statement so let's take it apart to understand everything that's happening. First on the left hand side of the assignment statement we see an ActorRef field. An ActorRef field is a reference to the actor being created. It has a one to one relationship with an actor. It is the only way that one actor has of sending a message to another actor. ActorRefs are immutable, network aware, and serializable. They can be sent in a message and used within other actors.
Next, on the right hand side of the assignment statement is the 'getContext()'. The context being fetched here is not the context of the actor system. It is the context of this actor. The next part of the statement is 'actorOf(....)'. This is the factory method that makes new child actors. This method takes configuration information provided by Props, an actor configuring class. Here in the 'Props.create(....)' part of the statement we will name the class of the child actor to be created. Finally at the end of the statement we see the name "waiter". This provides an identifying display name for the actor that will appear in logs and various debugging mechanisms. It is optional but recommended. I like to name my actor the same as the ActorRef field name.

In summary this actor creation statement will create a child actor of class 'Waiter' to be supervised within the context of its creating parent 'Diner'. Information about it will appear in logs and debug text identified as "waiter".


Sending Messages

Actors have their local state completely sealed from the outside world. The exchange of information that occurs between actors takes place entirely by the exchange of messages. An example of passing a message can be seen in the source Diner.java on the line below.

waiter.tell( waiter.Message.ASK_FOR_COOKIE, getSelf());

Communicating a message is very simple. The 'tell(...)' method is executed on the ActorRef field of the actor who is to be the recipient of the message. The tell takes two parameters, the message object and a return address for replies. The normal return address is the sender's self address and that is what is illustrated here.

Receiving Messages

Every actor is required to have the method onReceive() coded. This gives the actor the capability to receive messages sent to its mailbox. An example of this can be found in the source 'Waiter.java'. Look at the lines below taken from this file.

@Override
public void onReceive( Object message )
{

In onReceive() it is best to be certain to process every message that enters. The messages will at the grossest level break down into two categories; those that are recognized and those that are not recognized. The processing of recognized messages is of course application specific. The processing of unrecognizable messages is a lot more standard. They are usually published to a facility known as the EventStream which provides functionality whose description is beyond the scope of this post. An example handling unrecognizable messages can be seen in the lines below taken from the source 'Waiter.java'.

unhandled( message );

Termination

When an actor is stopped the entire hierarchy of actors beneath it is also stopped. This makes for easy leak proof management of actors. An example of actor termination can be found on the line below taken from the file 'Diner.java'.

getContext().stop( getSelf());



Conclusion

The 'Fortune Cookie Application' presented here illustrates some fundamental techniques needed by all actor applications. This application in its current form runs entirely with default configuration values for its actor system. Next week we will return to the 'Fortune Cookie Application' to learn about how to custom configure its actor system to meet a variety of purposes and perspectives. See "Actors in Java: Configuring Local Actor Systems" in about a week.





/***
Diner.java is source for the diner actor in the Fortune Cookie application; a simple exercise designed to show basic child creation, messaging, and termination.
***/


import akka.actor.Props;
import akka.actor.UntypedActor;
import akka.actor.ActorRef;

public class Diner extends UntypedActor
{
private final ActorRef waiter;

public Diner()
{
// Create the waiter actor, a child actor of the parent diner actor.
waiter = getContext().actorOf( Props.create( Waiter.class), "waiter");
}

@Override
public void preStart()
{
// Ask the waiter for a fortune cookie.
waiter.tell( Waiter.Message.ASK_FOR_COOKIE, getSelf());
}

// This is where we listen for messages.
@Override
public void onReceive( Object message )
{
// Reveal the fortune.
System.out.println( message.toString() );

// Stop the application by stopping the parent actor. This wiil cause child actor to stop.
getContext().stop( getSelf());

}
}//end class Diner





/***
Waiter.java is source for the the waiter actor in the Fortune Cookie application.
***/


import akka.actor.UntypedActor;
import java.util.Random;
import java.util.Date;
import java.util.List;
import java.util.Arrays;

public class Waiter extends UntypedActor
{
public static enum Message { ASK_FOR_COOKIE; }
private List fortune_cookies = Arrays.asList(
"Your monkey face will earn you many peanuts.",
"You will discover fountain of youth in middle of quick sand pit.",
"You will be safe. Your enemies will all die laughing.",
"The wisdom of our ancient sages will blow past your head like a gentle breeze.",
"Your only friend will soon betray you. This betrayal will bring a new friend.",
"You will be shunned by everyone for fearing to read your fortune cookie.");

@Override
public void onReceive( Object message )
{
if ( message == Message.ASK_FOR_COOKIE )
{
// Reply only to a message we recognize.
Date date = new Date();
Random generator = new Random( date.getTime());
int index = generator.nextInt( fortune_cookies.size());
// Reply to sender with message and self address.
getSender().tell( "\n"+fortune_cookies.get( index )+"\n", getSelf());
}
else
{
// We will publish messages not recognized to the EventStream.
unhandled( message );
}
}
}//end class Waiter





Compile and go commands. Set 'path_to_akka_jars' to place where you put these jars.



compile
javac -cp "./:path_to_akka_jars/*" Waiter.java Diner.java

go
java -classpath "./:path_to_akka_jars/*" akka.Main Diner





AttachmentSize
FortuneCookie-src.zip2.31 KB