Introduction:
Ajax Push is a mechanism to push data from server. Generally
in the web application when the client request for the server then we will get
the dynamic data or updated data from server to web client/browser.
But sometimes we need a mechanism it automatically
push data from server to client this is called server push mechanism. Server
push is technique to push data from server to client. In this scenarios client will not send request to server but server will automatically push the data to client
when it get updated.
To make it work server push technique we will use
one protocol called Bayeux protocol.
Bayeux:
Bayeux is a protocol for transporting asynchronous
messages (primarily over HTTP), with low latency between a web server and a web
client. The messages are routed via named channels and can be delivered
We can use protocol for server to client, client to server,
client to client (via the server)
The primary purpose of Bayeux is to support
responsive bidirectional interactions between web clients, for example using AJAX,
and the web server.
Here we will produce data on some channels and client
will subscribe channels. When client subscribe channeled as soon as data
published on channels will aquatically reached the client who are subscribed channels.
CometD
CometD is a scalable HTTP-based event routing bus
that uses a Ajax Push technology pattern known as Comet
CometD is a Dojo Foundation project to provide
implementations of the Bayeux protocol in JavaScript, java, Perl, python and
other languages. Other organizations (eg. Sun, IBM and BEA) also have
implementations of Bayeux that are not strictly part of the CometD project, but
where possible they are linked to from this site.
Environment:
Liferay 6.2 +Tomcat 7.x+MySQL 5.1
Note:
The code will work for portal 6.2 version you can
try for 6.1 too.
Download Liferay CometD Ajax Push Portlet from following location
You can find source and war file
Portlet
Screen-1:
Procedure for deploy
portlet and Use:
You can use war file and directly place in your portal deploy folder and
test or you can also use source to deploy portlet.
Once portlet is deployed successfully you can see the portlet in sample category name as Liferay Commet D Reverse Ajax.
As soon as you place the portlet in page you can see updated stock prices
for every few seconds and it will be updated automatically.
You can also open portlet in another browser there also you can see price
changes automatically.
Note:
Before use this portlet please Read entire Article
and Use it
Implementation:
We will use Bayeux
java implementation for produce data on channels.
Once data produced on channels we will use CometD
java script implementation to get the data to client automatically. We have
Dojo and JQuery java script implementation for CometD.
Using
ComedD Ajax Push in Liferay Portlet
- Configure CometD servlet in portlet web.xml
- Implement Data Producer to produce data
- Implement Service to publish data on Channels Using Bayeux
- Use CometD java script in client side to subscribe channel and show data.
Concept:
We will take an example of stock prices updates so
that we will send stock price changes to client when changes in price changes
in stocks.
We will use Java Scheduler to produce data for every
few second from server side and as soon as data produced we will use CometD
service to publish data on channels.
In the client side we will use CometD JQuery to subscribe
the channels and show the stock price changes.
Configure CometD servlet in portlet web.xml
We need configure CometD servlet to initiate all the
process. We have different servlets one of the servlet AnnotationCometdServlet and need to pass CometD service as init parameter.
The following is code snippet
<servlet>
<servlet-name>cometd</servlet-name>
<servlet-class>org.cometd.annotation.AnnotationCometdServlet</servlet-class>
<init-param>
<param-name>transports</param-name>
<param-value>org.cometd.websocket.server.WebSocketTransport</param-value>
</init-param>
<init-param>
<param-name>services</param-name>
<param-value>com.meera.commetd.StockPriceService</param-value>
</init-param>
<init-param>
<param-name>maxLazyTimeout</param-name>
<param-value>2000</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>cometd</servlet-name>
<url-pattern>/cometd/*</url-pattern>
</servlet-mapping>
|
Implement
Data Producer to produce data
We already we need some source need to generate data
so we will use some java class to produce data.
We will randomly generate some price for stock quotes
and we have used Scheduler Executer to run this task for every few seconds. We have
used StockPriceEmitter java class
Implement
Service to publish data on Channels Using Bayeux
We need service that responsible for publish data on
channels with the help of Bayeux protocol. We need create channel and then need
to publish
The following example syntax to create channel and
publish
Create
channel
bayeuxServer.createChannelIfAbsent(channelName,
new
ConfigurableServerChannel.Initializer()
{
public void
configureChannel(ConfigurableServerChannel channel)
{
channel.setPersistent(true);
channel.setLazy(true);
}
});
|
Publish
data on channel
ServerChannel
channel = bayeuxServer.getChannel(channelName);
channel.publish(sender, data);
|
Use
CometD java script in client side to subscribe channel and show data
In the client side will use CometD java script implementation
to subscribe channel and show the data.
We need configure CometD servlet URL and need to add
two leisters to make hand shake with server.
The
following is syntax
var cometURL =
location.protocol + "//" + location.host + config.contextPath + "/cometd";
cometd.configure({
url: cometURL,
logLevel: 'debug'
});
cometd.addListener('/meta/handshake', _metaHandshake);
cometd.addListener('/meta/connect', _metaConnect);
cometd.handshake();
|
Subscribe
Channel Syntax
cometd.subscribe('/stock/*', function(message)
{
var data =
message.data;
});
|
Note:
We need add CometD java script implementation source
in page and we will use cometd is java script object available to call methods.
We have implement application related java script in
application.js
The following are all required java
script source files
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<script type="text/javascript"
src="<%=renderRequest.getContextPath() %>/js/comet/cometd-namespace.js"></script>
<script type="text/javascript"
src="<%=renderRequest.getContextPath() %>/js/comet/cometd-json.js"></script>
<script type="text/javascript"
src="<%=renderRequest.getContextPath() %>/js/comet/AckExtension.js"></script>
<script type="text/javascript"
src="<%=renderRequest.getContextPath() %>/js/comet/TransportRegistry.js"></script>
<script type="text/javascript"
src="<%=renderRequest.getContextPath() %>/js/comet/Transport.js"></script>
<script type="text/javascript"
src="<%=renderRequest.getContextPath() %>/js/comet/RequestTransport.js"></script>
<script type="text/javascript"
src="<%=renderRequest.getContextPath() %>/js/comet/WebSocketTransport.js"></script>
<script type="text/javascript"
src="<%=renderRequest.getContextPath() %>/js/comet/CallbackPollingTransport.js"></script>
<script type="text/javascript"
src="<%=renderRequest.getContextPath() %>/js/comet/LongPollingTransport.js"></script>
<script type="text/javascript"
src="<%=renderRequest.getContextPath() %>/js/comet/Utils.js"></script>
<script type="text/javascript"
src="<%=renderRequest.getContextPath() %>/js/comet/Cometd.js"></script>
<script type="text/javascript"
src="<%=renderRequest.getContextPath() %>/js/comet/jquery.cometd.js"></script>
<script type="text/javascript"
src="<%=renderRequest.getContextPath() %>/js/comet/application.js"></script>
|
Complete
Source code for Implementation
The following is web.xml file
<web-app id="WebApp_ID" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>LiferayCommetDRevserseAjax-portlet</display-name>
<servlet>
<servlet-name>cometd</servlet-name>
<servlet-class>org.cometd.annotation.AnnotationCometdServlet</servlet-class>
<init-param>
<param-name>transports</param-name>
<param-value>org.cometd.websocket.server.WebSocketTransport</param-value>
</init-param>
<init-param>
<param-name>services</param-name>
<param-value>com.meera.commetd.StockPriceService</param-value>
</init-param>
<init-param>
<param-name>maxLazyTimeout</param-name>
<param-value>2000</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>cometd</servlet-name>
<url-pattern>/cometd/*</url-pattern>
</servlet-mapping>
<filter>
<filter-name>cross-origin</filter-name>
<filter-class>org.eclipse.jetty.servlets.CrossOriginFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>cross-origin</filter-name>
<url-pattern>/cometd/*</url-pattern>
</filter-mapping>
<jsp-config>
<taglib>
<taglib-uri>http://java.sun.com/portlet_2_0</taglib-uri>
<taglib-location>
/WEB-INF/tld/liferay-portlet.tld
</taglib-location>
</taglib>
<taglib>
<taglib-uri>http://liferay.com/tld/aui</taglib-uri>
<taglib-location>/WEB-INF/tld/aui.tld</taglib-location>
</taglib>
</jsp-config>
</web-app>
|
The following is StockPriceServiceJava
Class
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import
javax.inject.Inject;
import
org.cometd.annotation.Service;
import
org.cometd.annotation.Session;
import
org.cometd.bayeux.server.BayeuxServer;
import
org.cometd.bayeux.server.ConfigurableServerChannel;
import
org.cometd.bayeux.server.LocalSession;
import
org.cometd.bayeux.server.ServerChannel;
@Service
public class StockPriceService implements
StockPriceEmitter.Listener
{
@Inject
private BayeuxServer bayeuxServer;
@Session
private LocalSession sender;
public void
onUpdates(List<StockPriceEmitter.Update> updates)
{
for
(StockPriceEmitter.Update update : updates)
{
// Create the channel name using
the stock symbol
String channelName = "/stock/" +
update.getSymbol().toLowerCase(Locale.ENGLISH);
// Initialize the channel, making
it persistent and lazy
bayeuxServer.createChannelIfAbsent(channelName,
new
ConfigurableServerChannel.Initializer()
{
public void
configureChannel(ConfigurableServerChannel channel)
{
channel.setPersistent(true);
channel.setLazy(true);
}
});
// Convert the Update business
object to a CometD-friendly format
Map<String, Object> data = new HashMap<String,
Object>(4);
data.put("symbol",
update.getSymbol());
data.put("oldValue",
update.getOldValue());
data.put("newValue",
update.getNewValue());
System.out.println("===========================");
// Publish to all subscribers
ServerChannel channel = bayeuxServer.getChannel(channelName);
channel.publish(sender, data);
}
}
}
|
The following is StockPriceEmitter java
class
import
java.util.ArrayList;
import java.util.Arrays;
import
java.util.EventListener;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import
java.util.concurrent.CopyOnWriteArrayList;
import
java.util.concurrent.Executors;
import
java.util.concurrent.ScheduledExecutorService;
import
java.util.concurrent.TimeUnit;
public class StockPriceEmitter implements Runnable {
private final
ScheduledExecutorService scheduler = Executors
.newSingleThreadScheduledExecutor();
private final List<String>
symbols = new
ArrayList<String>();
private final Map<String,
Float> values = new HashMap<String, Float>();
private final
List<Listener> listeners = new CopyOnWriteArrayList<Listener>();
public StockPriceEmitter() {
symbols.addAll(Arrays.asList("ORCL", "MSFT", "GOOG", "YHOO", "FB"));
values.put("ORCL", 29.94f);
values.put("MSFT", 27.10f);
values.put("GOOG", 655.37f);
values.put("YHOO", 17.82f);
values.put("FB", 21.33f);
}
public List<Listener> getListeners()
{
return listeners;
}
public void start() {
run();
}
public void stop() {
scheduler.shutdownNow();
}
public void run() {
Random random = new Random();
List<Update> updates = new
ArrayList<Update>();
// Randomly choose how many stocks
to update
int howMany =
random.nextInt(symbols.size()) + 1;
for (int i = 0; i <
howMany; ++i) {
// Randomly
choose which one to update
int which =
random.nextInt(symbols.size());
String symbol = symbols.get(which);
float oldValue = values.get(symbol);
// Randomly
choose how much to update
boolean sign =
random.nextBoolean();
float howMuch =
random.nextFloat();
float newValue =
oldValue + (sign ? howMuch : -howMuch);
// Store the new
value
values.put(symbol,
newValue);
updates.add(new Update(symbol,
oldValue, newValue));
}
// Notify the listeners
for (Listener listener
: listeners) {
listener.onUpdates(updates);
}
// Randomly choose how long for
the next update
// We use a max delay of 1 second
to simulate a high rate of updates
long howLong =
random.nextInt(1000);
scheduler.schedule(this, howLong,
TimeUnit.MILLISECONDS);
}
public static class Update {
private final String symbol;
private final float oldValue;
private final float newValue;
public Update(String
symbol, float oldValue, float newValue) {
this.symbol = symbol;
this.oldValue = oldValue;
this.newValue = newValue;
}
public String getSymbol()
{
return symbol;
}
public float getOldValue() {
return oldValue;
}
public float getNewValue() {
return newValue;
}
}
public interface Listener extends EventListener {
void
onUpdates(List<Update> updates);
}
}
|
The following is Liferay Portlet Class
import
java.io.IOException;
import
javax.portlet.ActionRequest;
import
javax.portlet.ActionResponse;
import
javax.portlet.PortletException;
import
com.liferay.util.bridges.mvc.MVCPortlet;
public class
LiferayCommetDReverseAjax extends MVCPortlet {
private StockPriceEmitter emitter;
public void startStockUpdates(
ActionRequest
actionRequest, ActionResponse actionResponse)
throws IOException,
PortletException {
emitter = new
StockPriceEmitter();
StockPriceService
service(StockPriceService)getPortletContext().
getAttribute(StockPriceService.class.getName());
// Register the
service as a listener of the emitter
emitter.getListeners().add(service);
// Start the emitter
emitter.start();
}
public void stopStockUpdates(
ActionRequest
actionRequest, ActionResponse actionResponse)
throws IOException,
PortletException {
//emitter
= new StockPriceEmitter();
emitter.stop();
}
}
|
The following is application.js code
(function($)
{
var cometd = $.cometd;
//alert(cometd);
$(document).ready(function()
{
function
_connectionEstablished()
{
$('#body').append('<div>CometD
Connection Established</div>');
}
function
_connectionBroken()
{
$('#body').append('<div>CometD
Connection Broken</div>');
}
function
_connectionClosed()
{
$('#body').append('<div>CometD
Connection Closed</div>');
}
// Function that manages the connection
status with the Bayeux server
var _connected = false;
function
_metaConnect(message)
{
if
(cometd.isDisconnected())
{
_connected = false;
_connectionClosed();
return;
}
var wasConnected =
_connected;
_connected = message.successful
=== true;
if (!wasConnected
&& _connected)
{
_connectionEstablished();
}
else if (wasConnected
&& !_connected)
{
_connectionBroken();
}
}
// Function invoked when first contacting
the server and
// when the server has lost the state of
this client
function
_metaHandshake(handshake)
{
if
(handshake.successful === true)
{
cometd.batch(function()
{
cometd.subscribe('/stock/*', function(message)
{
var data = message.data;
var symbol =
data.symbol;
var value =
data.newValue;
// alert(symbol);
var id = 'stock_'+ symbol;
var
symbolDiv=document.getElementById(id);
if (!symbolDiv)
{
symbolDiv =
document.createElement('div');
symbolDiv.id =id;
document.getElementById('stocks').appendChild(symbolDiv);
}
symbolDiv.innerHTML =
'<span
class="symbol">' + symbol + ': <b>' + value + '</b></span>';
});
// Publish on a
service channel since the message is for the server only
//cometd.publish('/stock/*',
{ name: 'World' });
});
}
}
// Disconnect when the page unloads
$(window).unload(function()
{
cometd.disconnect(true);
});
var cometURL =
location.protocol + "//" + location.host + config.contextPath + "/cometd";
cometd.configure({
url: cometURL,
logLevel: 'debug'
});
cometd.addListener('/meta/handshake', _metaHandshake);
cometd.addListener('/meta/connect', _metaConnect);
cometd.handshake();
});
})(jQuery);
|
The following is portlet JSP page.
<%@ include
file="init.jsp"%>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<script type="text/javascript"
src="<%=renderRequest.getContextPath() %>/js/comet/cometd-namespace.js"></script>
<script type="text/javascript"
src="<%=renderRequest.getContextPath() %>/js/comet/cometd-json.js"></script>
<script type="text/javascript"
src="<%=renderRequest.getContextPath() %>/js/comet/AckExtension.js"></script>
<script type="text/javascript"
src="<%=renderRequest.getContextPath() %>/js/comet/TransportRegistry.js"></script>
<script type="text/javascript"
src="<%=renderRequest.getContextPath() %>/js/comet/Transport.js"></script>
<script type="text/javascript"
src="<%=renderRequest.getContextPath() %>/js/comet/RequestTransport.js"></script>
<script type="text/javascript"
src="<%=renderRequest.getContextPath() %>/js/comet/WebSocketTransport.js"></script>
<script type="text/javascript"
src="<%=renderRequest.getContextPath() %>/js/comet/CallbackPollingTransport.js"></script>
<script type="text/javascript"
src="<%=renderRequest.getContextPath() %>/js/comet/LongPollingTransport.js"></script>
<script type="text/javascript"
src="<%=renderRequest.getContextPath() %>/js/comet/Utils.js"></script>
<script type="text/javascript"
src="<%=renderRequest.getContextPath() %>/js/comet/Cometd.js"></script>
<script type="text/javascript"
src="<%=renderRequest.getContextPath() %>/js/comet/jquery.cometd.js"></script>
<script type="text/javascript"
src="<%=renderRequest.getContextPath() %>/js/comet/application.js"></script>
<%--
The reason to use a JSP is that it is
very easy to obtain server-side configuration
information (such as the contextPath) and
pass it to the JavaScript environment on the client.
--%>
<style>
#stocks .symbol b{
color:red;
}
#controler-table td{
padding-left:30px;
}
</style>
<script type="text/javascript">
var config = {
contextPath: '<%=renderRequest.getContextPath()%>'
};
</script>
<portlet:actionURL var="startStockUpdates"
name="startStockUpdates">
</portlet:actionURL>
<portlet:actionURL var="stopStockUpdates"
name="stopStockUpdates">
</portlet:actionURL>
<h2>Liferay CometD Ajax
Push Portlet</h2>
<br/>
<table id="controler-table">
<tr>
<td><a href="<%=startStockUpdates.toString()%>">Start</a></td>
<td><a href="<%=stopStockUpdates.toString()%>">Stop</a></td>
</tr>
</table>
<br/>
<div id="stocks"></div>
|
Author
0 comments :
Post a Comment