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)
|
||||
- [Channels and Connections in RabbitMQ](https://www.baeldung.com/java-rabbitmq-channels-connections)
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue