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
 
 
 
 Posts
Posts
 
 
