Websocket Integration With Spring Boot.

Hey Guys, I have posted an article to explain  how to send notification to the web application in real time with spring boot application with websocket.
Few Steps We Need To Follows :

1) Create simple spring boot application with spring web dependency  only. You can use this link https://start.spring.io to create spring boot application.

2) Once project set up then  add one more dependency for websocket  in pom.xml file.

<!-- For web socket -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

3) Now we are ready to configure web socket message broker. I have create  new package com.firststepitsolution.spring.ws.api.config  and created new class WsConfig under the above package and implemented WebSocketMessageBrokerConfigurer. As I have mentioned this is the web socket configuration class so we need to annotate this class as @Configuration and we need to add @EnableWebSocketMessageBroker annotation to enable web socket.

package com.firststepitsolution.spring.ws.api.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

@Configuration
@EnableWebSocketMessageBroker
public class WsConfig implements WebSocketMessageBrokerConfigurer {

	@Override
	public void configureMessageBroker(MessageBrokerRegistry config) {
		config.enableSimpleBroker( "/user", "/secured/user/queue/specific-user");
		config.setApplicationDestinationPrefixes("/app");
	}

	@Override
	public void registerStompEndpoints(StompEndpointRegistry registry) {
		registry.addEndpoint("/ws").setAllowedOrigins("*").withSockJS();
	}
}

In the above code snippet "config.enableSimpleBroker( "/user", "/secured/user/queue/specific-user")"  this line is important  to send message to specific users who have subscribed from the client applicaion. "registry.addEndpoint("/ws").setAllowedOrigins("*").withSockJS();" this line is important to establish connection between server and client.

3) Create new package with name "com.firststepitsolution.spring.ws.api.socket" and create new class with the name of "SocketSender" and annotate with @Component. This class is responsible to send massage to specific user. I have injected SimpMessagingTemplate    and used convertAndSendToUser to send message to specific user. Below is the code snippet for this class.

package com.firststepitsolution.spring.ws.api.socket;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Component;

import com.firststepitsolution.spring.ws.api.DTO.OutputNotificationMessageDTO;

import lombok.extern.slf4j.Slf4j;

@Component
@Slf4j
public class SocketSender {

	private final SimpMessagingTemplate simpMessagingTemplate;

	@Autowired
	public SocketSender(SimpMessagingTemplate simpMessagingTemplate) {
		this.simpMessagingTemplate = simpMessagingTemplate;

	}

	public void sendToWeb(OutputNotificationMessageDTO outputNotificationMessageDTO, Long id) {
		log.info("Sending data to user are {} and message going to id is {}", outputNotificationMessageDTO.toString(),
				id);
		simpMessagingTemplate.convertAndSendToUser(id.toString(), "/queue/notification", outputNotificationMessageDTO);
	}
}

4) I have created one Rest Controller for sending message to user using web socket. For this create one new package "com.firststepitsolution.spring.ws.api.controller" and create class "SendToUserController". This rest controller is only responsible to send message. Below is the code snippet for this class.

package com.firststepitsolution.spring.ws.api.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.firststepitsolution.spring.ws.api.DTO.MessageSendToUserDTO;
import com.firststepitsolution.spring.ws.api.DTO.OutputNotificationMessageDTO;
import com.firststepitsolution.spring.ws.api.socket.SocketSender;

import lombok.extern.slf4j.Slf4j;

@RestController
@Slf4j
public class SendToUserController {

	@Autowired
	private SocketSender socketSender;

	@PostMapping("/send/{id}")
	public ResponseEntity<Object> register(@PathVariable("id") Long id,
			@RequestBody MessageSendToUserDTO messageSendToUserDTO) {
		log.info("Request data are {}", messageSendToUserDTO.toString());
		OutputNotificationMessageDTO outputNotificationMessageDTO = new OutputNotificationMessageDTO();
		outputNotificationMessageDTO.setId(id);
		outputNotificationMessageDTO.setTitle(messageSendToUserDTO.getTitle());
		outputNotificationMessageDTO.setMessage(messageSendToUserDTO.getMessage());
		socketSender.sendToWeb(outputNotificationMessageDTO, id);
		return ResponseEntity.ok().build();
	}
}

5) Backend is ready. All the source code are not added in this article so if you want to download complete working source code then you can visit my git hub repository Repository Link.  I have added some additional library for logs and getter, setter , in this project so you need to add those library as well .

6)  Now we need to create one HTML file to act as a client . Create file name web-socket-client.html and save on Desktop.Make sure this file is not a part of sprint boot project so please keep this file outside the sprint boot project. I have added code snippet below.

<html>
<head>
<title>firststepitsolution web socket demo</title>

<script type="text/javascript">
	var stompClient = null;
	function setConnected(connected) {
		document.getElementById('connect').disabled = connected;
		document.getElementById('disconnect').disabled = !connected;
		document.getElementById('response').innerHTML = '';
	}
	function connect() {
		var socket = new SockJS('http://192.168.1.8:8080/ws'); // domainname/ws
		stompClient = Stomp.over(socket);
		stompClient.connect({}, function(frame) {
			setConnected(true);
			console.log('Connected: ' + frame);
			var from = document.getElementById('from').value;
			stompClient.subscribe('/user/' + from + '/queue/notification',
					function(messageOutput) {
						console.log('log something here')
						showMessageOutput(JSON.parse(messageOutput.body));
					});
		});
	}
	function disconnect() {
		if (stompClient != null) {
			stompClient.disconnect();
		}
		setConnected(false);
		console.log("Disconnected");
	}
	
	function showMessageOutput(messageOutput) {
		var response = document.getElementById('response');
		var p = document.createElement('p');
		p.style.wordWrap = 'break-word';
		p.appendChild(document.createTextNode(messageOutput.title + ": "
				+ messageOutput.id + " (" + messageOutput.message + ")"));
		response.appendChild(p);
	}
</script>
</head>
<body onload="disconnect()">
	<div>
		<div>
			<input type="text" size=200 id="from" placeholder="pass user unique id to receive message to specific user" />
		</div>
		<br />
		<div>
			<button id="connect" onclick="connect();">Connect</button>
			<button id="disconnect" disabled="disabled" onclick="disconnect();">
				Disconnect</button>
				<p id="response"></p>
		</div>
		<br />
		
		<script
			src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.1.4/sockjs.min.js"></script>
		<script
			src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>
	</div>
</body>
</html>

These two libraries are required for client application.

<script
			src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.1.4/sockjs.min.js"></script>
		<script
			src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>

 

6)  Some reference screen shot attached here:
  For source code please visit my git hub repository Repository Link. .

FCM Integration with Spring Boot To Send Push Notification From Server Side.


Hey Guys, recently i have integrated FCM( Firebase Cloud Messaging) with one of my spring boot application. So I would like to share my experience with you.

Some Basics Steps You Need To Follow:
1) Please make sure client application registered under Firebase.

2) Now we need to generate Firebase SDK admin key. This is basically JSON file and the purpose of this file is for server side authorization.You can get more details under https://firebase.google.com/docs/cloud-messaging/auth-server .

3) To get Admin SDK go to Project Setting -> Service Accounts -> Check Java radio button -> Finally click on Generate new private key and save it.


4) Now we need to use generated private JSON file in our spring boot application.So for this I am assuming you have already Spring Boot application , if you don't have then you can create from here https://start.spring.io/

5) Create one folder under src/main/resources in your spring boot application. In my case I have created fcm folder. Now put you private key under this folder.

6) In your application.properties file create key/value like app.firebase-configuration-file=fcm/dummy-d7bd4-firebase-adminsdk-l3psc-8c2aa79daa.json
Note : value key contains location of your private JSON file.

7) Add Firebase dependency in pom.xml file.

<dependency>
    <groupId>com.google.firebase</groupId>
    <artifactId>firebase-admin</artifactId>
    <version>6.8.1</version>
</dependency>

8) Now we need to initialize Firebase application , for this we need private key location so to access the location we can use @Value annotation with key name. Create class FCMInitializer with file name FCMInitializer.java

package com.pushnotification.fcm.service;

import java.io.IOException;

import javax.annotation.PostConstruct;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;

import com.google.auth.oauth2.GoogleCredentials;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;

@Service
public class FCMInitializer {

    @Value("${app.firebase-configuration-file}")
    private String firebaseConfigPath;

    Logger logger = LoggerFactory.getLogger(FCMInitializer.class);

    @PostConstruct
    public void initialize() {
        try {
            FirebaseOptions options = new FirebaseOptions.Builder()
                    .setCredentials(GoogleCredentials.fromStream(new ClassPathResource(firebaseConfigPath).getInputStream())).build();
            if (FirebaseApp.getApps().isEmpty()) {
                FirebaseApp.initializeApp(options);
                logger.info("Firebase application has been initialized");
            }
        } catch (IOException e) {
            logger.error(e.getMessage());
        }
    }

}

In the above code snippets , initialize() method is being called on application start up due to @PostConstruct annotaction.

9) Create Class FCMService with @Service annotation which contains some different types of methods. Create class with file name FCMService.java

package com.pushnotification.fcm.service;

import java.time.Duration;
import java.util.Map;
import java.util.concurrent.ExecutionException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import com.google.firebase.messaging.AndroidConfig;
import com.google.firebase.messaging.AndroidNotification;
import com.google.firebase.messaging.ApnsConfig;
import com.google.firebase.messaging.Aps;
import com.google.firebase.messaging.FirebaseMessaging;
import com.google.firebase.messaging.Message;
import com.google.firebase.messaging.Notification;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.pushnotification.fcm.model.PushNotificationRequest;

@Service
public class FCMService {

    private Logger logger = LoggerFactory.getLogger(FCMService.class);

    public void sendMessage(Map<String, String> data, PushNotificationRequest request)
            throws InterruptedException, ExecutionException {
        Message message = getPreconfiguredMessageWithData(data, request);
        Gson gson = new GsonBuilder().setPrettyPrinting().create();
        String jsonOutput = gson.toJson(message);
        String response = sendAndGetResponse(message);
        logger.info("Sent message with data. Topic: " + request.getTopic() + ", " + response+ " msg "+jsonOutput);
    }

    public void sendMessageWithoutData(PushNotificationRequest request)
            throws InterruptedException, ExecutionException {
        Message message = getPreconfiguredMessageWithoutData(request);
        String response = sendAndGetResponse(message);
        logger.info("Sent message without data. Topic: " + request.getTopic() + ", " + response);
    }

    public void sendMessageToToken(PushNotificationRequest request)
            throws InterruptedException, ExecutionException {
        Message message = getPreconfiguredMessageToToken(request);
        Gson gson = new GsonBuilder().setPrettyPrinting().create();
        String jsonOutput = gson.toJson(message);
        String response = sendAndGetResponse(message);
        logger.info("Sent message to token. Device token: " + request.getToken() + ", " + response+ " msg "+jsonOutput);
    }

    private String sendAndGetResponse(Message message) throws InterruptedException, ExecutionException {
        return FirebaseMessaging.getInstance().sendAsync(message).get();
    }

    private AndroidConfig getAndroidConfig(String topic) {
        return AndroidConfig.builder()
                .setTtl(Duration.ofMinutes(2).toMillis()).setCollapseKey(topic)
                .setPriority(AndroidConfig.Priority.HIGH)
                .setNotification(AndroidNotification.builder().setSound(NotificationParameter.SOUND.getValue())
                        .setColor(NotificationParameter.COLOR.getValue()).setTag(topic).build()).build();
    }

    private ApnsConfig getApnsConfig(String topic) {
        return ApnsConfig.builder()
                .setAps(Aps.builder().setCategory(topic).setThreadId(topic).build()).build();
    }

    private Message getPreconfiguredMessageToToken(PushNotificationRequest request) {
        return getPreconfiguredMessageBuilder(request).setToken(request.getToken())
                .build();
    }

    private Message getPreconfiguredMessageWithoutData(PushNotificationRequest request) {
        return getPreconfiguredMessageBuilder(request).setTopic(request.getTopic())
                .build();
    }

    private Message getPreconfiguredMessageWithData(Map<String, String> data, PushNotificationRequest request) {
        return getPreconfiguredMessageBuilder(request).putAllData(data).setToken(request.getToken())
                .build();
    }

    private Message.Builder getPreconfiguredMessageBuilder(PushNotificationRequest request) {
        AndroidConfig androidConfig = getAndroidConfig(request.getTopic());
        ApnsConfig apnsConfig = getApnsConfig(request.getTopic());
        return Message.builder()
                .setApnsConfig(apnsConfig).setAndroidConfig(androidConfig).setNotification(
                        new Notification(request.getTitle(), request.getMessage()));
    }
}

10) Now I am going to add one more service layer. Create class PushNotificationService with file name PushNotificationService.java

package com.pushnotification.fcm.service;

import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import com.pushnotification.fcm.model.PushNotificationRequest;

@Service
public class PushNotificationService {

    private Logger logger = LoggerFactory.getLogger(PushNotificationService.class);
    private FCMService fcmService;

    public PushNotificationService(FCMService fcmService) {
        this.fcmService = fcmService;
    }

    public void sendPushNotification(PushNotificationRequest request) {
        try {
            fcmService.sendMessage(getSamplePayloadData(), request);
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
    }

    public void sendPushNotificationWithoutData(PushNotificationRequest request) {
        try {
            fcmService.sendMessageWithoutData(request);
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
    }

    public void sendPushNotificationToToken(PushNotificationRequest request) {
        try {
            fcmService.sendMessageToToken(request);
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
    }

    private Map<String, String> getSamplePayloadData() {
        Map<String, String> pushData = new HashMap<>();
        pushData.put("messageId", "msgid");
        pushData.put("text", "txt");
        pushData.put("user", "pankaj singh");
        return pushData;
    }
}

Note : Create these all three above files under the service layer.

11) Now create two files under model layer PushNotificationRequest.java , PushNotificationResponse.java

package com.pushnotification.fcm.model;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Setter
@Getter
@NoArgsConstructor
public class PushNotificationRequest {

    private String title;
    private String message;
    private String topic;
    private String token;  
}
package com.pushnotification.fcm.model;
public class PushNotificationResponse {

    private int status;
    private String message;

    public PushNotificationResponse() {
    }

    public PushNotificationResponse(int status, String message) {
        this.status = status;
        this.message = message;
    }

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

12) Create controller class to test push notification.Create file PushNotificationController.java

package com.pushnotification.fcm.controller;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.pushnotification.fcm.model.PushNotificationRequest;
import com.pushnotification.fcm.model.PushNotificationResponse;
import com.pushnotification.fcm.service.PushNotificationService;

@RestController
public class PushNotificationController {

    private PushNotificationService pushNotificationService;

    public PushNotificationController(PushNotificationService pushNotificationService) {
        this.pushNotificationService = pushNotificationService;
    }

    @PostMapping("/notification/topic")
    public ResponseEntity sendNotification(@RequestBody PushNotificationRequest request) {
        pushNotificationService.sendPushNotificationWithoutData(request);
        return new ResponseEntity<>(new PushNotificationResponse(HttpStatus.OK.value(), "Notification has been sent."), HttpStatus.OK);
    }

    @PostMapping("/notification/token")
    public ResponseEntity sendTokenNotification(@RequestBody PushNotificationRequest request) {
        pushNotificationService.sendPushNotificationToToken(request);
        return new ResponseEntity<>(new PushNotificationResponse(HttpStatus.OK.value(), "Notification has been sent."), HttpStatus.OK);
    }

    @PostMapping("/notification/data")
    public ResponseEntity sendDataNotification(@RequestBody PushNotificationRequest request) {
        pushNotificationService.sendPushNotification(request);
        return new ResponseEntity<>(new PushNotificationResponse(HttpStatus.OK.value(), "Notification has been sent."), HttpStatus.OK);
    }   
}

Finally we have configured everything for push notification.So our code is ready to send the push notification.


We have mainly three use cases:
a) Push notification based on topics. In this kind of notification we don't required any device token. So based on topic configuration push notification will work.

For this you can use end point : /notification/topic

b) Push notification with token.With this end point we can send only title and body part.

For this you can use end point : /notification/token

c) Push notification with additional payload data. With this end point we can send title,body and some additional key/pair data.

For this you can use end point : /notification/data

If you want to test please use postman and pass JSON body like :

{
	"title":"Put the push notification title here",
	"message":"Put here push notification body here",
	"token":"f65ewWeHOUs:APA91bH2mVv-cVpjVv4mJZxkugeNoJJ_AL-Bl_Kfosz26rk1nTJREp-_b9eEzojh3GgvGxtu1VXlyXTxc5j_jQNMl0oBEEw2oe_knHVIpvdAKMG6qya6"
}

Note : You can download full working source code from my repository link https://github.com/firststepitsolution/push-notification-with-fcm-and-spring-boot I have tested on both platform (IOS and Android) and its working as expected.