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:
psevestre 2022-11-28 01:15:01 -03:00 committed by GitHub
parent aaf070cd55
commit f6ae9ceefa
11 changed files with 371 additions and 0 deletions

1
gcp-firebase/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/firebase-service-account.json

48
gcp-firebase/pom.xml Normal file
View File

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

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

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

View File

@ -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());
}
}

View File

@ -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)