BAEL-5900: Using Firebase Cloud Messaging in SpringBoot Applications (#13102)
* [BAEL-4849] Article code * [BAEL-4968] Article code * [BAEL-4968] Article code * [BAEL-4968] Article code * [BAEL-4968] Remove extra comments * [BAEL-5258] Article Code * [BAEL-2765] PKCE Support for Secret Clients * [BAEL-5698] Article code * [BAEL-5698] Article code * [BAEL-5900] Initial commit
This commit is contained in:
parent
aaf070cd55
commit
f6ae9ceefa
|
@ -0,0 +1 @@
|
||||||
|
/firebase-service-account.json
|
|
@ -0,0 +1,48 @@
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>com.baeldung</groupId>
|
||||||
|
<artifactId>parent-boot-2</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<relativePath>../parent-boot-2</relativePath>
|
||||||
|
</parent>
|
||||||
|
<artifactId>gcp-firebase</artifactId>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<firebase-admin.version>9.1.1</firebase-admin.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.firebase</groupId>
|
||||||
|
<artifactId>firebase-admin</artifactId>
|
||||||
|
<version>${firebase-admin.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
|
@ -0,0 +1,12 @@
|
||||||
|
package com.baeldung.gcp.firebase.publisher;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class FirebasePublisherApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args ) {
|
||||||
|
SpringApplication.run(FirebasePublisherApplication.class,args);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
package com.baeldung.gcp.firebase.publisher.config;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import com.google.api.client.http.HttpTransport;
|
||||||
|
import com.google.auth.oauth2.GoogleCredentials;
|
||||||
|
import com.google.firebase.FirebaseApp;
|
||||||
|
import com.google.firebase.FirebaseOptions;
|
||||||
|
import com.google.firebase.messaging.FirebaseMessaging;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableConfigurationProperties(FirebaseProperties.class)
|
||||||
|
public class FirebaseConfiguration {
|
||||||
|
|
||||||
|
private final FirebaseProperties firebaseProperties;
|
||||||
|
|
||||||
|
public FirebaseConfiguration(FirebaseProperties firebaseProperties) {
|
||||||
|
this.firebaseProperties = firebaseProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
GoogleCredentials googleCredentials() {
|
||||||
|
try {
|
||||||
|
if (firebaseProperties.getServiceAccount() != null) {
|
||||||
|
try( InputStream is = firebaseProperties.getServiceAccount().getInputStream()) {
|
||||||
|
return GoogleCredentials.fromStream(is);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Use standard credentials chain. Useful when running inside GKE
|
||||||
|
return GoogleCredentials.getApplicationDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException ioe) {
|
||||||
|
throw new RuntimeException(ioe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
FirebaseApp firebaseApp(GoogleCredentials credentials) {
|
||||||
|
FirebaseOptions options = FirebaseOptions.builder()
|
||||||
|
.setCredentials(credentials)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return FirebaseApp.initializeApp(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
FirebaseMessaging firebaseMessaging(FirebaseApp firebaseApp) {
|
||||||
|
return FirebaseMessaging.getInstance(firebaseApp);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package com.baeldung.gcp.firebase.publisher.config;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
|
||||||
|
@ConfigurationProperties(prefix = "gcp.firebase")
|
||||||
|
public class FirebaseProperties {
|
||||||
|
private Resource serviceAccount;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the serviceAccount
|
||||||
|
*/
|
||||||
|
public Resource getServiceAccount() {
|
||||||
|
return serviceAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param serviceAccount the serviceAccount to set
|
||||||
|
*/
|
||||||
|
public void setServiceAccount(Resource serviceAccount) {
|
||||||
|
this.serviceAccount = serviceAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package com.baeldung.gcp.firebase.publisher.controller;
|
||||||
|
|
||||||
|
public class ConditionMessageRepresentation {
|
||||||
|
|
||||||
|
private String condition;
|
||||||
|
private String data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the condition
|
||||||
|
*/
|
||||||
|
public String getCondition() {
|
||||||
|
return condition;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param condition the condition to set
|
||||||
|
*/
|
||||||
|
public void setCondition(String condition) {
|
||||||
|
this.condition = condition;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the data
|
||||||
|
*/
|
||||||
|
public String getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param data the data to set
|
||||||
|
*/
|
||||||
|
public void setData(String data) {
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,109 @@
|
||||||
|
package com.baeldung.gcp.firebase.publisher.controller;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
|
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.google.firebase.messaging.AndroidConfig;
|
||||||
|
import com.google.firebase.messaging.AndroidFcmOptions;
|
||||||
|
import com.google.firebase.messaging.ApnsConfig;
|
||||||
|
import com.google.firebase.messaging.BatchResponse;
|
||||||
|
import com.google.firebase.messaging.FcmOptions;
|
||||||
|
import com.google.firebase.messaging.FirebaseMessaging;
|
||||||
|
import com.google.firebase.messaging.FirebaseMessagingException;
|
||||||
|
import com.google.firebase.messaging.Message;
|
||||||
|
import com.google.firebase.messaging.MulticastMessage;
|
||||||
|
import com.google.firebase.messaging.Notification;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class FirebasePublisherController {
|
||||||
|
|
||||||
|
private final FirebaseMessaging fcm;
|
||||||
|
|
||||||
|
public FirebasePublisherController(FirebaseMessaging fcm) {
|
||||||
|
this.fcm = fcm;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/topics/{topic}")
|
||||||
|
public ResponseEntity<String> postToTopic(@RequestBody String message, @PathVariable("topic") String topic) throws FirebaseMessagingException {
|
||||||
|
|
||||||
|
Message msg = Message.builder()
|
||||||
|
.setTopic(topic)
|
||||||
|
.putData("body", message)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
String id = fcm.send(msg);
|
||||||
|
return ResponseEntity
|
||||||
|
.status(HttpStatus.ACCEPTED)
|
||||||
|
.body(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/condition")
|
||||||
|
public ResponseEntity<String> postToCondition(@RequestBody ConditionMessageRepresentation message ) throws FirebaseMessagingException {
|
||||||
|
|
||||||
|
Message msg = Message.builder()
|
||||||
|
.setCondition(message.getCondition())
|
||||||
|
.putData("body", message.getData())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
String id = fcm.send(msg);
|
||||||
|
return ResponseEntity
|
||||||
|
.status(HttpStatus.ACCEPTED)
|
||||||
|
.body(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@PostMapping("/clients/{registrationToken}")
|
||||||
|
public ResponseEntity<String> postToClient(@RequestBody String message, @PathVariable("registrationToken") String registrationToken) throws FirebaseMessagingException {
|
||||||
|
|
||||||
|
Message msg = Message.builder()
|
||||||
|
.setToken(registrationToken)
|
||||||
|
.putData("body", message)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
String id = fcm.send(msg);
|
||||||
|
return ResponseEntity
|
||||||
|
.status(HttpStatus.ACCEPTED)
|
||||||
|
.body(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/clients")
|
||||||
|
public ResponseEntity<List<String>> postToClients(@RequestBody MulticastMessageRepresentation message) throws FirebaseMessagingException {
|
||||||
|
|
||||||
|
MulticastMessage msg = MulticastMessage.builder()
|
||||||
|
.addAllTokens(message.getRegistrationTokens())
|
||||||
|
.putData("body", message.getData())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
BatchResponse response = fcm.sendMulticast(msg);
|
||||||
|
|
||||||
|
List<String> ids = response.getResponses()
|
||||||
|
.stream()
|
||||||
|
.map(r->r.getMessageId())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
return ResponseEntity
|
||||||
|
.status(HttpStatus.ACCEPTED)
|
||||||
|
.body(ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/subscriptions/{topic}")
|
||||||
|
public ResponseEntity<Void> createSubscription(@PathVariable("topic") String topic,@RequestBody List<String> registrationTokens) throws FirebaseMessagingException {
|
||||||
|
fcm.subscribeToTopic(registrationTokens, topic);
|
||||||
|
return ResponseEntity.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/subscriptions/{topic}/{registrationToken}")
|
||||||
|
public ResponseEntity<Void> deleteSubscription(@PathVariable String topic, @PathVariable String registrationToken) throws FirebaseMessagingException {
|
||||||
|
fcm.subscribeToTopic(List.of(registrationToken), topic);
|
||||||
|
return ResponseEntity.ok().build();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package com.baeldung.gcp.firebase.publisher.controller;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class MulticastMessageRepresentation {
|
||||||
|
|
||||||
|
private String data;
|
||||||
|
private List<String> registrationTokens;
|
||||||
|
/**
|
||||||
|
* @return the message
|
||||||
|
*/
|
||||||
|
public String getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param message the message to set
|
||||||
|
*/
|
||||||
|
public void setData(String data) {
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @return the registrationTokens
|
||||||
|
*/
|
||||||
|
public List<String> getRegistrationTokens() {
|
||||||
|
return registrationTokens;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param registrationTokens the registrationTokens to set
|
||||||
|
*/
|
||||||
|
public void setRegistrationTokens(List<String> registrationTokens) {
|
||||||
|
this.registrationTokens = registrationTokens;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
# Service account location. Can be a filesystem path or a classpath resource
|
||||||
|
gcp.firebase.service-account=file:firebase-service-account.json
|
|
@ -0,0 +1,48 @@
|
||||||
|
package com.baeldung.gcp.firebase.publisher.controller;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||||
|
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||||
|
import org.springframework.boot.test.web.server.LocalServerPort;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
|
||||||
|
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
|
||||||
|
class FirebasePublisherControllerLiveTest {
|
||||||
|
|
||||||
|
@LocalServerPort
|
||||||
|
int serverPort;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
TestRestTemplate restTemplate;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testWhenPostTopicMessage_thenSucess() throws Exception{
|
||||||
|
|
||||||
|
URI uri = new URI("http://localhost:" + serverPort + "/topics/my-topic");
|
||||||
|
ResponseEntity<String> response = restTemplate.postForEntity(uri, "Hello, world", String.class);
|
||||||
|
|
||||||
|
assertNotNull(response);
|
||||||
|
assertEquals(HttpStatus.ACCEPTED, response.getStatusCode());
|
||||||
|
assertNotNull(response.getBody());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testWhenPostClientMessage_thenSucess() throws Exception{
|
||||||
|
|
||||||
|
URI uri = new URI("http://localhost:" + serverPort + "/clients/fake-registration1");
|
||||||
|
ResponseEntity<String> response = restTemplate.postForEntity(uri, "Hello, world", String.class);
|
||||||
|
|
||||||
|
assertNotNull(response);
|
||||||
|
assertEquals(HttpStatus.ACCEPTED, response.getStatusCode());
|
||||||
|
assertNotNull(response.getBody());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -8,3 +8,4 @@ This module contains articles about RabbitMQ.
|
||||||
- [Pub-Sub vs. Message Queues](https://www.baeldung.com/pub-sub-vs-message-queues)
|
- [Pub-Sub vs. Message Queues](https://www.baeldung.com/pub-sub-vs-message-queues)
|
||||||
- [Channels and Connections in RabbitMQ](https://www.baeldung.com/java-rabbitmq-channels-connections)
|
- [Channels and Connections in RabbitMQ](https://www.baeldung.com/java-rabbitmq-channels-connections)
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue