Push Notifications using Atmosphere and Spring

Posted on Posted in Custom Programming, Java

Most recent applications rely on push notifications to alert users of events that they are interested in. Just like how Facebook alerts users of new friend requests or messages.

notification

This can be implemented in several ways… the simplest is to display the alert when page is loaded or refreshed but this is not very user friendly as user will only be notified when they refresh the page. Alternatively, users can be alerted in a more real-time fashion by polling the server for updates. This works, but when there are thousands of users, this can be a resource hog because each user will have to several poll request every minute.

The ideal solution is to use non-blocking IO technology or websockets to keep the connection open and avoid constant polling. Below is a sample implementation on how to achieve this using Atmosphere and Spring.

STEP 1: Include Atmosphere libraries in your Spring project. In your pom.xml, add Maven to atmosphere library.

		
			org.atmosphere
			atmosphere-runtime
			${org.atmosphere-version}
		

STEP 2: Update web.xml to use the Atmosphere servlet. Add the atmosphere servlet that will handle the NIO requests. This servlet will receive the NIO calls and uses retains your Spring configuration. The Atmosphere servlet should have different URL pattern so that only NIO calls are forwarded to Atmosphere.

        notification
        org.atmosphere.cpr.MeteorServlet
        
            org.atmosphere.servlet
            org.springframework.web.servlet.DispatcherServlet
        
        
            contextClass
            org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        
        2
        true        
    
    
  
	notification
	/n/*
  

STEP 3: Create Spring controller. Below is a sample code snippet to handle atmosphere request. This will be the URL to be invoked to retrieve the NIO request. Notice that the request is suspended by Atmosphere.

@Controller
public class NotifyHandler {

    @Autowired
    private NotificationService notificationService;
	
    @RequestMapping(value = "/notify/{userId}")
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public List watch(@PathVariable("userId") String userId,
                      HttpServletRequest request) throws Exception {
        //Atmosphere framework puts filter/servlet that adds ATMOSPHERE_RESOURCE to all requests
        AtmosphereResource resource = (AtmosphereResource) request.getAttribute(ApplicationConfig.ATMOSPHERE_RESOURCE);

        //suspending resource to keep connection
        resource.suspend();

        //find broadcaster, second parameter says to create broadcaster if it doesn't exist
        Broadcaster broadcaster = BroadcasterFactory.getDefault().lookup(userId,true);

        //saving resource for notifications
        broadcaster.addAtmosphereResource(resource);
        
        long nCount = notificationService.countNewPopup(new Long(userId));
        List notifications = notificationService.findMostRecentPopup(new Long(userId));
        List response = new ArrayList();
        response.add(""+nCount);
        for (Notification n:notifications) {
        	response.add(n.getMessage());
        }
        return response;
    }
}

STEP 4: Broadcast message to appropriate users when needed. In your code, whenever a notification is triggered you may broadcast the message to recipient using the sample code below.

    Broadcaster b = BroadcasterFactory.getDefault().lookup(userId);
    if (b!=null) {
        long nCount = notificationDao.countNewPopup(new Long(userId));
        List notifications = notificationDao.findMostRecentPopup(new Long(userId));
	StringBuffer response = new StringBuffer("[");
        response.append(nCount);
        for (Notification n:notifications) {
        	response.append(",\"")
        			.append(n.getMessage())
        			.append("\"");
        }
        response.append("]");
        b.broadcast(response.toString());
    }

STEP 5: Finally, include javascript to invoke Atmosphere library. The code below shows the snippet to invoke the URL.

$.atmosphere.subscribe(
                "/n/notify/1", // URL containing the atmosphere servlet
                callback, // javascript callback to handle the response
                $.atmosphere.request = { transport:"websocket", requestCount: 0 });

That’s it. The code snippets above is not complete and cannot not run by itself. The purpose of this post is just to provide directions on the steps needed to get push notification to work. Hope this gives you some direction on how to go about exploring push notification for your application.

If you’re a client looking for an extra hand for your IT project, please don’t hesitate to contact us today.

Send us a message

6 thoughts on “Push Notifications using Atmosphere and Spring

  1. Nice tuto, but can you upload all files of this project, I have some problems to get the controller working.

  2. Hi,
    I am trying to run above code in FireFox but I don’t see any notifications displaying in FireFox 47.0.1.
    I tried with sample chat application using MeteorService where I am able to display the messages in the browser and notifications are not working in FirFox 47.0.1, but same sample was working in Chrome.

    So, had a doubt whether Push Notification with Atmosphere & Websocket would work in ForFox 47.0.1 or not.
    PFB sample query and let me you ideas for same.

    Code:
    ——-
    @MeteorService(path = “/*”, interceptors = {AtmosphereResourceLifecycleInterceptor.class})
    public class MeteorChat extends HttpServlet {

    @Inject
    @Named(“/*”)
    private Broadcaster broadcaster;

    /**
    * Create a {@link Meteor} and use it to suspend the response.
    *
    * @param req An {@link HttpServletRequest}
    * @param res An {@link HttpServletResponse}
    */
    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
    // Set the logger level to TRACE to see what’s happening.
    Meteor m=Meteor.build(req).addListener(new AtmosphereResourceEventListenerAdapter());
    m.resumeOnBroadcast(m.transport() == LONG_POLLING ? true : false).suspend(-1);
    Broadcaster broadcaster= m.getAtmosphereResource().getAtmosphereConfig().getBroadcasterFactory().lookup(“venu”, true);
    broadcaster.addAtmosphereResource(m.getAtmosphereResource());
    }

    /**
    * Re-use the {@link Meteor} created on the first GET for broadcasting message.
    *
    * @param req An {@link HttpServletRequest}
    * @param res An {@link HttpServletResponse}
    */
    @Override
    public void doPost(HttpServletRequest req, HttpServletResponse res) throws IOException {
    String body = req.getReader().readLine().trim();
    Meteor m=Meteor.build(req);
    // Simple JSON — Use Jackson for more complex structure
    // Message looks like { “author” : “foo”, “message” : “bar” }
    String author = body.substring(body.indexOf(“:”) + 2, body.indexOf(“,”) – 1);
    String message = body.substring(body.lastIndexOf(“:”) + 2, body.length() – 2);
    System.out.println(“–author—-“+author);
    System.out.println(“–message—-“+message);

    Broadcaster b = m.getAtmosphereResource().getAtmosphereConfig().getBroadcasterFactory().lookup(“venu”);
    if (b!=null) {
    b.broadcast(new Data(author, message).toString());

    }

    }

    private final static class Data {

    private final String text;
    private final String author;

    public Data(String author, String text) {
    this.author = author;
    this.text = text;
    }

    public String toString() {
    return “{ \”text\” : \”” + text + “\”, \”author\” : \”” + author + “\” , \”time\” : ” + new Date().getTime() + “}”;
    }
    }
    }

  3. Does any one get success with this source?
    seems web.xml has wrong values
    If anyone can share working source it would be brilliant?!

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.