Java + Flex = Cool application and reliable back end!

Probably you have already heard about Adobe Flex.
Flex is a SDK to develop Rich Internet Applications that will run within the Flash Player, and Flash Player is present in almost all browser and all desktop platforms today.
Flex applications generate an SWF file and this file will communicate with a back end server, this backend server can be a Flash Lifecycle Server, but you can use java and open source for it too.

This two/tree examples work without any paid software, you will need only:
Flex SDK - that is free, and the version 3 will be open source.
A servlet container - I’m using Tomcat
– For the first example that is all
And for the other two examples you’ll need to download
OpenAMF - A flex remoting implementation in Java
RemoteObjectAMF0 - an implementation of the RemoteObject tag that supports the version 0 of AMF protocol (OpenAMF does not supports the version 3 yet)

So, lets start, create a java web project with your favorite IDE, and create a Servlet named TestServlet, I’ll use jaxb to render XML from the servlet, but you can use anything else, if you want to use jaxb too, the schema I created for this example is the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.example.com/example" xmlns:tns="http://www.example.com/example" elementFormDefault="qualified">
    <element name="TestSVo" type="tns:TestList"></element>
        <complexType name="TestSVoType">
    	<attribute name="id" type="int"></attribute>
    	<attribute name="name" type="string"></attribute>
    	<attribute name="other" type="string"></attribute>
    </complexType>
    <complexType name="TestList">
    	<sequence minOccurs="1" maxOccurs="unbounded">
    		<element name="all" type="tns:TestSVoType"></element>
    	</sequence>
    </complexType>
</schema>

This schema is for a XML like the following:

1
2
3
4
5
<TestSVo>
<all id="0" name="foo" other="bar"/>
<all id="1" name="foo1" other="bar1"/>
...
</TestSVo>

Generate the stubs for the XML generation using JaxB or use any other tool to generate a XML with this schema.

Like any other Java application, I’ll start with my web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd ">
	<servlet>
		<servlet-name>testServlet</servlet-name>
		<servlet-class>....servlet.TestServlet</servlet-class>
	</servlet>
	<servlet>
		<servlet-name>AdvancedGateway</servlet-name>
		<servlet-class>org.openamf.AdvancedGateway</servlet-class>
		<init-param>
			<description>
				Location of the OpenAMF config file.
			</description>
			<param-name>OPENAMF_CONFIG</param-name>
			<param-value>/WEB-INF/openamf-config.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>AdvancedGateway</servlet-name>
		<url-pattern>/gateway2</url-pattern>
	</servlet-mapping>
	<servlet-mapping>
		<servlet-name>testServlet</servlet-name>
		<url-pattern>/TestServlet</url-pattern>
	</servlet-mapping>
	<session-config>
		<session-timeout>30</session-timeout>
	</session-config>
	<welcome-file-list>
		<welcome-file>teste.html</welcome-file>
	</welcome-file-list>
</web-app>

for the first example, the only needed servlet is the test servlet, for the other examples the AdvancedGateway servlet is used …

Now write the following code in the servlet:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import java.io.IOException;
 
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
 
/**
 * Servlet implementation class for Servlet: TestServlet
 * 
 */
public class TestServlet extends javax.servlet.http.HttpServlet implements javax.servlet.Servlet
{
	static final long serialVersionUID = 1L;
	private JAXBContext jc;
	private Marshaller marc;
	private Unmarshaller unmarc;
	private ObjectFactory factory;
 
	public TestServlet()
	{
		super();
		factory = new ObjectFactory();
		try {
			jc = JAXBContext.newInstance("....servlet");
			marc = jc.createMarshaller();
			unmarc = jc.createUnmarshaller();
		} catch (JAXBException e) {
			e.printStackTrace();
		}
 
	}
 
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
	{
		float v = Float.parseFloat(request.getParameter("v"));
		float v1 = Float.parseFloat(request.getParameter("v1"));
		response.getWriter().print(v * v1);
	}
 
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException,
		IOException
	{
		System.out.println("List called");
		TestList res = new TestList();
		for(int i=0;i<40;i++){
			res.getAll().add(new TestSVoType(i,"Téste ã " + i));
		}
		JAXBElement<TestList> elem = factory.createTestSVo(res);
		System.out.println(elem.toString());
		try {
			marc.marshal(elem, response.getWriter());
		} catch (JAXBException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

the doGet method of this servlet is a calculator, it just multiply the two parameters and prints out the result.
The doPost prints a XML like the one above, automatically generated using the JAXB API.

Now the flex part:
Create a file named xmltest.mxml in the home folder of your webapp with the following content.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
	 layout="vertical"	backgroundColor="#F4F4F4">
	 <mx:HTTPService id="calc" method="GET" url="http://localhost:8080/teste/TestServlet" >
	 </mx:HTTPService>
	 <mx:HTTPService id="list" method="POST" url="http://localhost:8080/teste/TestServlet" >
	 </mx:HTTPService>
	<mx:Script>
		<![CDATA[
			import mx.controls.DataGrid;
            import mx.controls.Alert;
        ]]>
	</mx:Script>
	<mx:Panel width="80%" height="531" title="Teste">
		<mx:Label width="100%" color="blue"
			text="Type two numbers and press calculate." />
		<mx:TextInput id="v1" text="3" />
		<mx:TextInput id="v2" text="5" />
		<mx:Label id="lbl"  text="{calc.lastResult}"/>
		<mx:Button label="Calculate" click="calc.send({v:v1.text,v1:v2.text});" />
		<mx:DataGrid dataProvider="{list.lastResult.TestSVo.all}" width="100%" height="195" change="Alert.show(DataGrid(event.currentTarget).selectedItem.other)">
			<mx:columns>
				<mx:DataGridColumn headerText="Id" dataField="id" />
				<mx:DataGridColumn headerText="Nome" dataField="name" />
			</mx:columns>
		</mx:DataGrid>
		<mx:Button label="List" click="list.send({v:v1.text,v1:v2.text})" />
	</mx:Panel>
</mx:Application>

The interface with the java code is in the two mx:HTTPService lines, the first one invokes the URL with a GET request, calling the calculator method
Then the first button (Calculate) is activated, the fist service is called [calc.send(parameters)] and the text on the first label will be updated because of the value binding on the last result …
When the seccond button (List) fires a post [list.send(params)] and the data grid will process the returned XML and display the lines …
That is all!
Gotcha: if you call list.send without parameters the HTTPService tag fires a GET method, this is the reason for the unused parameters there, at least with Flex 2 this happened every time

This aproach is very simple and does not need much code from any side, but it does not looks like OOP very much …

And flex has a great remoting support, so what do you think agout creating a POJO in Java and use it as a “remote object” from your flex code?
Like the idea?
So let’s create the TestService bellow with the same two operations (calc and list)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.util.ArrayList;
import java.util.List;
 
public class TestService
{
	public float calc(float v, float v1){
		System.out.format("%f X %f\n", v, v1);
		return v * v1;
	}
 
	public List<TestVo> list(){
		System.out.println("List called");
		ArrayList<TestVo> res = new ArrayList<TestVo>();
		for(int i=0;i<40;i++){
			res.add(new TestVo(i,"Testé  ã " + i));
		}
		return res;
	}
}

And now, we do not need all that XML stuf, a plain old VO will do the JOB …

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class TestVo
{
	private String name;
	private int id;
	public TestVo(int id, String name)
	{
		this.id = id;
		this.name = name;
	}
	public String getName()
	{
		return name;
	}
	public int getId()
	{
		return id;
	}
	public void setName(String name)
	{
		this.name = name;
	}
	public void setId(int id)
	{
		this.id = id;
	}
}

To use this service from Flash, we need to configure the OpenAMF framework using the file WEB-INF/openamf-config.xml, the name and location of this file was configured as a parameter to the AdvancedGateway in web.xml file …

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?xml version="1.0" encoding="UTF-8"?>
<config>
	<amf-serializer>
			<force-lower-case-keys>false</force-lower-case-keys>
	</amf-serializer>
	<invoker>
		<name>Java</name>
		<class>org.openamf.invoker.JavaServiceInvoker</class>
	</invoker>
	<custom-class-mapping>
		<java-class>....TestVo</java-class>
		<custom-class>TestVo</custom-class>
	</custom-class-mapping>
	<service>
		<name>TestService</name>
		<service-location>....TestService</service-location>
		<invoker-ref>Java</invoker-ref>
		<method>
			<name>calc</name>
			<parameter>
				<type>*</type>
			</parameter>
		</method>
		<method>
			<name>list</name>
			<parameter>
				<type>*</type>
			</parameter>
		</method>
	</service>
</config>

I have removed almost all the code from this file
There is the Serializer/Deserializer configured, the only registered invoker is the Java invoker, there is one custom-class-mapping for the VO and a Service definition for the service.
If you do not want to declare all methods for each service, you can use * as the name, and it will match any method …
The OpenAMF frameworks has many more configuration options, you can call EJBs, WebServices, any java class, JMX beans and Spring Beans from your flex code without any problem …

We can not use the standard mx:RemoteObject to call this service, because OpenAMF does not support AMF3, only AMF0 and I do not know how to configure the AMF protocol version for the mx:RemoteObject tag …
So the first example, uses only a NetConnection instance:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
	layout="vertical"
	backgroundColor="#F4F4F4"
	initialize="init()">
	<mx:Script>
		<![CDATA[
            import mx.controls.Alert;
            import mx.rpc.events.ResultEvent;
            import mx.collections.ArrayCollection;   
            [Bindable]
            public var lista:ArrayCollection; 
            public var nc : NetConnection;
 
            function init() : void{
            	nc = new NetConnection();
            	nc.objectEncoding = ObjectEncoding.AMF3;
            	nc.addEventListener(NetStatusEvent.NET_STATUS,netStatus);
            	nc.connect("http://localhost:8080/teste/gateway2");
            }
            function netStatus(event : NetStatusEvent) : void {
            	Alert.show(event.info.code);
            }
            function onRetornaLista(event):void{
            	var e : Array = event;
            	lista = new ArrayCollection(e);
            }       
            function onRetornaCalc(event):void{
            	lbl.text = String(event);
            }
            function calc(v1 : Number, v2 : Number) : void {
            	var r : Responder = new Responder(onRetornaCalc);
            	nc.call("TestService.calc",r,v1,v2);
            }
            function list() : void{
            	var r : Responder = new Responder(onRetornaLista);
            	nc.call("TestService.list",r);
            }
 
        ]]>
	</mx:Script>
	<mx:Panel width="80%" height="531" title="Teste">
		<mx:Label width="100%" color="blue"
			text="Type two numbers and press calculate." />
		<mx:TextInput id="v1" text="3" />
		<mx:TextInput id="v2" text="5" />
		<mx:Label id="lbl"  />
		<mx:Button label="Calculate" click="calc(Number(v1.text),Number(v2.text))" />
		<mx:DataGrid dataProvider="{lista}" width="100%" height="195">
			<mx:columns>
				<mx:DataGridColumn headerText="Id" dataField="id" />
				<mx:DataGridColumn headerText="Nome" dataField="name" />
			</mx:columns>
		</mx:DataGrid>
		<mx:Button label="List" click="list()" />
	</mx:Panel>
</mx:Application>

the init method just initialized the NetConnection object (nc).
When the first button is called (Calculate) the calc method is called, in this method we call the TestService.calc method in the server, using the NetConnection object.

The new thing here is that we need the Responders to process the result from the server call …
I think this approach is better than the last one, but still not looking very good for me.

So I looked a little around and found the RemoteObjectAMF0 library …
It is a little ActionScript library, that enables you to use tags to call your service from flex using the AMF0 protocol like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
	xmlns:renaun="com.renaun.rpc.*" layout="vertical"
	backgroundColor="#F4F4F4">
	<mx:Script>
		<![CDATA[
            import mx.controls.Alert;
            import mx.rpc.events.ResultEvent;
            import mx.collections.ArrayCollection;   
            [Bindable]
            public var lista:ArrayCollection; 
 
            NetConnection.defaultObjectEncoding = ObjectEncoding.AMF0;
 
            function onRetornaLista(event:ResultEvent):void{
            	lista = ArrayCollection(event.result);
            }       
            function onRetornaCalc(event:ResultEvent):void{
            	lbl.text = String(event.result);
            }
        ]]>
	</mx:Script>
	<renaun:RemoteObjectAMF0
		endpoint="http://localhost:8080/teste/gateway2" id="TST"
		source="....TestService" showBusyCursor="true"
		makeObjectsBindable="true"
		fault="Alert.show(String(event.fault)), 'Error'">
		<renaun:methods>
			<renaun:method name="list" result="onRetornaLista(event)"></renaun:method>
			<renaun:method name="calc" result="onRetornaCalc(event)"></renaun:method>
		</renaun:methods>
	</renaun:RemoteObjectAMF0>
	<mx:Panel width="80%" height="531" title="Teste">
		<mx:Label width="100%" color="blue"
			text="Type two numbers and press calculate." />
		<mx:TextInput id="v1" text="3" />
		<mx:TextInput id="v2" text="5" />
		<mx:Label id="lbl"  />
		<mx:Button label="Calculate" click="TST.calc(Number(v1.text),Number(v2.text))" />
		<mx:DataGrid dataProvider="{lista}" width="100%" height="195">
			<mx:columns>
				<mx:DataGridColumn headerText="Id" dataField="id" />
				<mx:DataGridColumn headerText="Nome" dataField="name" />
			</mx:columns>
		</mx:DataGrid>
		<mx:Button label="List" click="TST.list()" />
	</mx:Panel>
</mx:Application>

Still not perfect, since I did not liked the idea of using the full file name to call a service, I think my final solution will be to create an dynamic proxy for the services, but this last one looks good enought for me :D

I hope this post helps some one, I spend two days looking for solutions for this problem, because the current project does not have enought cash for a Life Cycle Server license …

PS.: to compile the mxml files you can use the mxmlc command from the free flex SDK
PS2.: you need to fix the URLs to the full path for your context, and your flex app must be downloaded from the same domain
PS3.: the é and ã letters are not encoding problems, I put it there to test if there would be any encoding problems and there were none :D

If you enjoyed this post, make sure you subscribe to my RSS feed!

A cool chat example created with Ruby On Rails and Juggernaut

Versão em portugues aqui
Before we start with the chat, you need to have the requisites installed, juggernaut need the gems json and eventmachine installed, so, run the following command before reading the rest of this example …

  • gem install -y json eventmachine

Ok, now we are ready to go!
First of all, create a rails application, and install the Juggernaut plugin with this commands:

  • rails -d sqlite3 chattest
  • cd chattest
  • script/plugin install svn://rubyforge.org//var/svn/juggernaut/trunk/juggernaut

Juggernaut uses an external Flash XML Push Server to do the reverse ajax magic, and we’ll need to configure that server, so, let’s edit the file: config/juggernaut.yml
I’ve put in the snipet bellow just the lines I changed in the file

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
PUSH_PORT: 8080
...
DEFAULT_CHANNELS: 
- "chat"
...
PUSH_HELPER_HOST: "localhost"
...
SECRET: "481516232342edededededed"
...
LOGIN_GET_URL: "http://localhost:3000/session/login"
LOGOUT_GET_URL: "http://localhost:3000/session/logout"
...
SESSION_ID: "_chattest_session_id"
...
BASE64: true

I had to change the PUSH_PORT because I’m using a linux box and the application does not run as root, so I was not able to use the default 443 port, and I do not think that 443 is a good port choice because it is the default HTTPS port.
Make sure you change the PUSH_HELPER_HOST to the same host name as the one you are using to access the application, localhost will do the job in the development environment, but remember to change it when you publish your site in a production environment.
the LOGIN_GET_URL and LOGOUT_GET_URL are used to notify the application about clients arriving and leaving, we will really use only the leaving notification.
the SESSION_ID must be the same as the defined cookie name for your application, it is defined in the application controller for rails 1.2.x and will be moved to environment.rb for rails 2.0
and BASE64 must be set to true if we want to use the rails helpers to generate the javascript for us.

Now let’s start the layout for the application. Create a file named pubic/stylesheets/public.css with the following content:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
body {
  background-color: white;
}
#users {
  float: left;
  width: 200px;
  height: 400px;
  border-style: inset;
  overflow: auto;
  color: white;
  background-color: gray;
}
#dasd {
  height: 400px;
  margin-left: 5px;
  border-style: inset;
  overflow: auto;
  color: white;
  background-color: gray;
}
#controls {
  clear: both;
  padding: 0 0 0 0;
  height: 55px;
  vertical-align: top;
  border-style: inset;
  overflow: auto;
  color: white;
  background-color: gray;
}

and a file named: app/views/layouts/application.rhtml with the following content.

1
2
3
4
5
6
7
8
9
10
<html>
  <head>
    <title>Chat Test</title>
    <%= stylesheet_link_tag 'public' %>
    <%= javascript_include_tag :defaults %>
  </head>
  <body>
    <%= yield %>
  </body>
</html>

in this file it is important to add the stylesheet and the default javascript includes.

With the layout ready (ok, I know it is pretty ugly, but I’m a developer not a webdesigner so, for production, ask a designer in your team for a new layout :D ) let’s generate the needed files and database tables with the following four commands.

  • script/generate model online_user username:string session_id:string last_seen:date online:boolean
  • script/generate controller session
  • script/generate controller chat index
  • rake db:migrate

Every thing ready, we just need to edit some files …
Open the OnlineUser model (app/model/online_user.rb) and change the content to something like the following

1
2
3
4
class OnlineUser < ActiveRecord::Base
	validates_presence_of :username, :session_id, :last_seen
	validates_uniqueness_of :username, :if => Proc.new {|user| user.online }
end

It is just a few validations, not really needed, this was my first idea for the chat, I’ve changed it a little but still works.

Now let’s code the main view of the application in the file:app/views/chat/index.rhtml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- Register with Juggernault -->
<%= listen_to_juggernaut_channels [:generic],session.session_id %>
<!-- The Users List -->
<div id="users">
  <ul id="users_list"></ul>
</div>
<!-- The messages pane -->
<div id="dasd"></div>
<!-- The controls pane (login and send messages) -->
<div id="controls"><%= render :partial => 'login' %></div>
<!-- An util javascript to scroll the messages window -->
<script type="text/javascript">
  function scrollMessages(){
    $('dasd').scrollTop = $('dasd').scrollHeight;
  }
</script>

That is all, just tree DIVs, the tag to initialize juggernaut and a simple javascript to scroll the messages DIV to the last sent message.
the messages DIV is named dasd because I was testing some conflicts and forgot to change it back :D

As seen in the page above, we need a login partial, and we’ll need a controls partial too, so let’s code the controls partial (app/views/chat/_controls.rhtml) with the following code:

1
2
3
4
5
6
<% form_remote_tag(
      :url => { :action => :say },
      :complete => "$('message').value = '';$('message').focus();" ) do %>
      <%= text_field_tag( 'message', '', { :size => 90, :id => 'message'} ) %>
      <%= submit_tag "Send" %>
  <% end %>

It has only a remote form tag and two fields, after the form is submited the message field is cleared and the focus os placed back in that field so the user can type another message.

And the login partial (app/views/chat/_login.rhtml):

1
2
3
4
5
6
7
<%= "#{@message}<br/>" if @message %><% form_remote_tag(
      :url => { :action => :login },
      :complete => "$('username').value = ''",
      :after => "$('login').disabled = true" ) do %>
      <%= text_field_tag( 'username', '', { :size => 90, :id => 'username'} ) %>
      <%= submit_tag "Join", :id => 'login' %>
  <% end %>

Very similar to the controls partial, but it shows a message to the user if the chosen nick name is already taken.

the views are all set, and now we need the application logic, as seen in the views, we need a chat controller with two methods: login and say
Let’s take a look at the chat controller (app/controllers/chat_controller.rb):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
class ChatController < ApplicationController
  #this method does not need to exist, but I like to see it here, it only needs to render the index.rhtml view
  def index
  end
  def login
    #creates a new OnlineUser record, this is used to store who are the users that are online now
	@user = OnlineUser.new
	@user.username = Juggernaut.html_and_string_escape params[:username]
	@user.session_id = session.session_id
	@user.online = true
	@user.last_seen = Time.now
        #if we can save, it means that there is no other user with the same nick online, so this user can join the chat
	if @user.save
          #let's save the username in the session for future reference
	  session[:username] = @user.username
          #if there are online users, fill the users box for the new user know who is online
	  @users = OnlineUser.find(:all, :conditions => ["online = true and id != ?", @user.id]) 
          if @users.size >0 
            data = render_to_string(:update) do |page|
              @users.each {|u|
                page.insert_html :bottom, :users_list, %Q{<li id="user_#{u.username}">#{u.username}</li>}
              }
	    end
            #send the javascript only to the new user
            Juggernaut.send_to(@user.session_id, data)
          end
          #create a javascript call to add the new user to the end of the online users list
          data = render_to_string(:update) do |page|
            page.insert_html :bottom, :users_list, %Q{<li id="user_#{@user.username}">#{@user.username}</li>}
            page.insert_html :bottom, :dasd, "<b>user #{@user.username} just joined the chat</b><br/>"
          end
          #add the new user to the chat channel
          Juggernaut.add_channel(@user.session_id, 'chat')
          #send the javascript to all users in the chat channel
          Juggernaut.send_data(data, 'chat')
	  render(:update) do |page|
	    page.replace_html 'controls', :partial => "controls"
          end
	else
          @message = 'This nick name is already in use, please choose another'
	  render(:update) do |page|
	    page.replace_html 'controls', :partial => "login"
	  end
	end
  end
 
  def say
    #escape the message, that way the user can not harm others sending HTML ot JavaScript commands
    message = "#{session[:username]}: #{Juggernaut.html_and_string_escape(params[:message])}"
    #create a javascript to add the new message to the end of the messages screen and scroll the div to the bottom
    data = render_to_string(:update) do |page|
      page.insert_html :bottom, :dasd, "#{message}<br/>"
      page.call "scrollMessages"
    end
    #send the message to all users
    Juggernaut.send_data(data, 'chat')
    render :nothing => true
  end
end

The method say is really simple, so we’ll start explaining it:
It first build a new message appending the escaped original message to the user name, then it creates the javascript to add the message to the bottom of the messages div using the Rails JavaScriptBuilder and the render_to_string method, that returns a string instead of rendering the code directly to the client.
then it sends the message to all users subscribed to the “chat” channel, and all the users will execute that javascript.

Now a little about the login method:

  • from the line 7 to 11 we just configure the new OnlineUser model.
  • if the user is saved to the database it means that there is no other user with the same nick name, so we can proceed with the logic
  • from line 17 to 23 we just create a javascript to populate the users DIV for the new user with the users already online
  • and in the line 25 we send this javascript just for the new user.
  • from line 28 to 31 we create a javascript adding the new user to the users DIV and add a message to the messages window telling all users that a new user joined the chat.
  • in the line 33 we add the new user to the “chat” channel
  • in the line 35 the generated javascript is sent to all users (now including the new arriver)
  • and in from the line 36 to 38 the traditional rails ajax helper is used to change the content of the controls DIV to the “controls” partial (the one that enables the user to send messages to other users.

This is almost all logic needed for this chat application, the only missing thing is removing the nick of users that are no more online from the users DIV from the other users, and we’ll do that in the session controller (app/controllers/session_controller.rb)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class SessionController < ApplicationController
  #Called when a user disconnect (a refresh in the browser causes this to be called too)
  def logout
    #search for the user record using the session_id
    @u = OnlineUser.find_by_session_id(session.session_id)
    reset_session
    #if a user was found
    if @u
      username = @u.username
      #remove it from the database
      @u.destroy
      #remove from the online users list from all users, and tell others this user left the chat
      data = render_to_string(:update) do |page|
        page.remove "user_#{username}"
        page.insert_html :bottom, :dasd, "<b>User #{username} left the chat</b><br/>"
      end
      Juggernaut.send_data(data,'chat')
    end                
    render :nothing => true
  end
 
  def login
    render :nothing => true
  end
end

The login method does nothing, but we have some code in the logout method …

  • in the line 5 we lookup the user record from the database
  • if a user is found
  • we destroy it in the line 8
  • from line 13 to 17 we create a javascript to remove the user from the users DIV and send a message to all users telling that the user has left the chat

That is all folks, we just need to run the application
to run this application we need to start the rails application server as usual, and then start the push server.
to do this, just run the following two commands:

  • script/server
  • script/push_server

and access your newly build chat with the URL: http://localhost:3000/chat and play a little around.
I’ve built this example while studding the juggernaut lib, so it is possible that there is a easy way to do this, but I think this is a good start point :D
The start idea was not to use a database, but I could not find anything like the ServletContext in java for rails (an application context), I’ll try to use ENV to store the online user names, but I could not make it work until now.
Any tips for improving this example will be very welcome.

If you enjoyed this post, make sure you subscribe to my RSS feed!

Rails gotchas (reserved attribute or method name)

Some time ago I’ve got a really strange error message when trying to access a new method in a controller I have created for a rails application …
In the log the error message was:
“wrong number of arguments (2 for 0)”

the step by step to reproduce this error is:

  • rails test
  • cd test
  • script/generate controller test process
  • script/server
  • point your browser to http://localhost:3000/test/process

What really happened?
ActionController has a method named process, and my controller:

class TestController < ApplicationController
 
  def process
  end
end

defined a action with the same name, and it messed up with ActionPack …
Hasan had a very similar problem but with a reserved attribute of ActiveRecord

So, if you are having strange problems like these ones, try looking for a “reserved” attribute or method …
They are not really reserved, but, you have to pay attention when you redefine a “rails core” method :D
Even if you do not know that you are redefining the method :D

If you enjoyed this post, make sure you subscribe to my RSS feed!

A very simple login example with JSF

Following the very simple example line, as in the “login example with Rails“, now we’ll see a very simple login example with Java Server Faces.
I’m writing this sequence of short howto posts because in the forums I read one of the recurrent question from the beginners is “how to implement a login with X”, so, let’s go to the JSF login example.

Of course there are many ways to implement a login in a JSF aplication, you can use JAAS, you can use a Servlet Filter, but this one I think is one of the best approaches, of course, in a real application, I usually combine it with some AOP and annotations, but AOP and annotations are out of the scope of this tutorial …

First, write a web.xml for your application, with the faces servlet in it …

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>/faces/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.jsf</url-pattern>
</servlet-mapping>
<error-page>
<exception-type>java.lang.SecurityException</exception-type>
<location>/login.jsf</location>
</error-page>
</web-app>

In this web.xml we are using the servlets 2.5 specification, and I’m using JSF 1.2, for this example you can use other versions without problem.
The servlet container is configured to show a login page if at any time the application throws a java.lang.SecurityException, this is a important point for the example.

And now a backing bean for the application, here is where we’ll validate the user’s login, in this example there is no need for database access, but in a real application you’ll search this data in your user repository (database, ldap, …)

package br.com.urubatan.jsfjpasec;
public class Login {
private boolean loginOk;
private String userName;
private String password;
public boolean isLoginOk() {
return loginOk;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public  String validateLogin(){
if(userName!=null &&   password!=null && !userName.equalsIgnoreCase(password)){
loginOk = true;
return "secpage";
}else return "login";
}
}

It is a very simple backing bean, with only 3 properties:
name and password for the user, and a property telling the application that a user has already logged in.
probably this last property will be replaced by a list of groups filled by the validateLogin method …

Now a bean with some static data for the example:

package br.com.urubatan.jsfjpasec;
import java.util.List;
import java.util.ArrayList;
public class SomeData {
private List<String> data =