I got a problem related to Firebase and Java EE.
I'm currently writing some Java servlets for my project and I'm using Firebase the first time because I wanted to try something new.
My actual problem is the following:
I got a servlet which is responsible for exchanging an iOS device token in an user database. This is necessary for sending Remote Push Notifications to a device.
I've done this like in the google tutorials, but I'm getting the following exception:
java.lang.IllegalStateException: FirebaseApp name [DEFAULT] already exists!
The way I'm accessing the Firebase Database is through the Java SDK.
I do this with the following code:
connect method
// gets called by the servlet to configure Firebase
public static void connect() {
try {
// for authentication purpose
Map<String, Object> auth = new HashMap<>();
auth.put("uid", "my-service-account");
// Setting up the FirebaseOptions object
// constant FIREBASE_DATABASE_URL = "url to my database"
// constant FIREBASE_KEY_PATH = "path to my json key"
options = new FirebaseOptions.Builder()
.setDatabaseUrl(FIREBASE_DATABASE_URL)
.setServiceAccount(new FileInputStream(FIREBASE_KEY_PATH))
.setDatabaseAuthVariableOverride(auth)
.build();
FirebaseApp.initializeApp(options);
// calling the method for exchanging the token
exchangeIosDeviceToken("testmail#example.com", "5bf53173c9ef0a37638f3ddaa59cf2c0687c14ca0dcd47ccf57f9f09bd6368ab");
} catch (FileNotFoundException ex) {
ex.printStackTrace();
}
}
exchangeIosDeviceToken method
public static boolean exchangeIosDeviceToken(String email, String newDeviceToken) {
FirebaseDatabase database = FirebaseDatabase.getInstance();
// getting a reference to my "employee" child
DatabaseReference employeeReference = database.getReference("/employee");
Map<String, Object> employeeUpdates = new HashMap<>();
// updating the device token with child "iosDeviceToken" of "employee"
employeeUpdates.put(email+"/iosDeviceToken", newDeviceToken);
// update the actual children
employeeReference.updateChildren(employeeUpdates);
return true;
}
The funny part is when I'm trying to execute this code in a standalone main class (replacing the connect method, with the main method), the code is working.
Before you're saying things like "there are tons of questions related to this topic"... They are nearly all related to Android and questions related to my problem seldom got answered.
Regards
Solved the problem.
The problem was:
I've called the connect method everytime a request was incoming.
Solution:
Call the connect method only once. (ServletContextListener)
this for the future users, You can check whether the default app is initialized or not like this.
FirebaseApp firebaseApp = null;
List<FirebaseApp> firebaseApps = FirebaseApp.getApps();
if(firebaseApps!=null && !firebaseApps.isEmpty()){
for(FirebaseApp app : firebaseApps){
if(app.getName().equals(FirebaseApp.DEFAULT_APP_NAME))
firebaseApp = app;
}
}
else
firebaseApp = FirebaseApp.initializeApp(options);
This exception appear because you are trying to create the [DEFAULT] FirebaseApp again, simply you can add a validation to check if it exist or not before the initialization, like this:
if(FirebaseApp.getInstance(FirebaseApp.DEFAULT_APP_NAME) != null) {
return;
}
My fix to this issue was call FirebaseApp.getInstance().delete();
My solution
package com.zs.configuration;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOpt`enter code here`ions;
import com.google.firebase.auth.FirebaseAuth;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import java.io.FileInputStream;
#WebListener
public class MyAppServletContextListener
implements ServletContextListener{
#Override
public void contextDestroyed(ServletContextEvent arg0) {
System.out.println("ServletContextListener destroyed");
}
//Run this before web application is started
#Override
public void contextInitialized(ServletContextEvent arg0) {
try {
FileInputStream serviceAccount = new FileInputStream(getClass().getClassLoader().getResource("zs.json").getFile());
FirebaseOptions options = new FirebaseOptions.Builder()
.setCredentials(GoogleCredentials.fromStream(serviceAccount))
.setDatabaseUrl("https://zs.firebaseio.com")
.build();
FirebaseApp firebaseApp = FirebaseApp.initializeApp(options);
FirebaseAuth.getInstance(firebaseApp);
}catch (Exception exc){
System.out.println("Firebase exception "+exc);
}
System.out.println("ServletContextListener started");
}
}
In my Application class
#ServletComponentScan
#SpringBootApplication
public class ZSApplication {
public static void main(String[] args) {
SpringApplication.run(ZSApplication.class, args);
}
}
Summarising all the solutions, very good ones BTW, proposed here: Running code after Spring Boot starts please find below the spring-boot (2.6.3) implementations which works for me.
Beware: Substitute with your app and files names below.
Placing the firebase initialisation method ( FirebaseApp.initializeApp(options)) directly on the main method (as I found on this tutorial: https://www.youtube.com/watch?v=8jK9O0lwem0) it caused the error, just because, debugging I notice, for some reason the call of the method FirebaseApp.initializeApp(options); is done multiple times.
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Objects;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import lombok.extern.slf4j.Slf4j;
#Slf4j
#SpringBootApplication
public class YourAppNameApplication {
public static void main(String[] args) {
SpringApplication.run(YourAppNameApplication.class, args);
}
/**
* Initialize the firebase SDK to integrate with the firebase application. Used
* for check the clients UIDs authentications.
*
* #throws IOException in case the firebase configuration JSON file is not
* present on the path.
*/
#EventListener(ApplicationReadyEvent.class)
public void initFirebaseSDK() throws IOException {
ClassLoader classLoader = YourAppNameApplication.class.getClassLoader();
File file = new File(
Objects.requireNonNull(classLoader.getResource("your_file_firebase.json")).getFile());
FileInputStream serviceAccount = new FileInputStream(file.getAbsolutePath());
#SuppressWarnings("deprecation")
FirebaseOptions options = new FirebaseOptions.Builder()
.setCredentials(GoogleCredentials.fromStream(serviceAccount))
.setDatabaseUrl("https://your_firebase_app_name.firebaseio.com").build();
FirebaseApp.initializeApp(options);
if (!(FirebaseApp.getApps().isEmpty())
&& FirebaseApp.getInstance(FirebaseApp.DEFAULT_APP_NAME).getName() != null) {
log.info("Firebase SDK has been initialised with the app name: "
+ FirebaseApp.getInstance(FirebaseApp.DEFAULT_APP_NAME).getName());
} else {
log.error(
"Firebase SDK has NOT been initialised. This is a serious error. Please contact the administrator of the app.");
}
}
}
Related
Hi I am trying to connect to Big Query and I am using a google service account with a JSON key. I am getting the below error. This is in my java batch program.
Insert operation not performed
com.google.cloud.bigquery.BigQueryException: Unexpected error refreshing access token
This fixed my issue. more here https://cloud.google.com/bigquery/docs/authorization
import com.google.auth.Credentials;
import com.google.auth.oauth2.ServiceAccountJwtAccessCredentials;
import com.google.cloud.bigquery.BigQuery;
import com.google.cloud.bigquery.BigQueryOptions;
import com.google.cloud.bigquery.Dataset;
import java.io.FileInputStream;
import java.net.URI;
public class Example {
public static void main(String... args) throws Exception {
String projectId = "myproject";
// Load JSON file that contains service account keys and create ServiceAccountJwtAccessCredentials object.
String credentialsPath = "/path/to/key.json";
URI audience = URI.create("https://bigquery.googleapis.com/");
Credentials credentials = null;
try (FileInputStream is = new FileInputStream(credentialsPath)) {
credentials = ServiceAccountJwtAccessCredentials.fromStream(is, audience);
}
// Instantiate BigQuery client with the credentials object.
BigQuery bigquery =
BigQueryOptions.newBuilder().setCredentials(credentials).build().getService();
// Use the client to list BigQuery datasets.
System.out.println("Datasets:");
bigquery
.listDatasets(projectId)
.iterateAll()
.forEach(dataset -> System.out.printf("%s%n", dataset.getDatasetId().getDataset()));
}
}
I am using a service account to access google doc files of users in my enterprise google account using impersonation.
See:
https://developers.google.com/drive/api/v3/about-auth#OAuth2Authorizing
So far so good.
Then, I need to download contents of Google Docs.
When calling Google Drive API to download the contents of a Google Doc, the documentation says to run the following:
https://developers.google.com/drive/api/v3/manage-downloads
Here is a java program that should reproduce the problem:
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.util.SecurityUtils;
import com.google.api.services.drive.Drive;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.List;
public class FetchGoogleDocContentsWithServiceAccount {
static int readTimeout = 60000;
static int connectTimeout = 60000;
static String serviceAccountId = "";
static String serviceAccountEmail = "";
static String serviceAccountPrivateKeyFile = "";
static String serviceAccountPrivateKeyFilePassword = "";
static String fileId = "";
static JacksonFactory jacksonFactory = new JacksonFactory();
static NetHttpTransport httpTransport = new NetHttpTransport();
static List<String> googleScopeList = Arrays.asList("https://www.googleapis.com/auth/drive.readonly",
"https://www.googleapis.com/auth/admin.directory.group.readonly",
"https://www.googleapis.com/auth/admin.directory.user.alias.readonly",
"https://www.googleapis.com/auth/admin.directory.group", "https://www.googleapis.com/auth/admin.directory.user",
"https://www.googleapis.com/auth/drive");
public static void main(String[] args) throws Exception {
Drive drive = (new Drive.Builder(httpTransport,
jacksonFactory,
getRequestInitializer(getGoogleCredentials())))
.setApplicationName("Sample app").build();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
drive.files().export(fileId, "application/vnd.google-apps.document")
.executeMediaAndDownloadTo(baos);
System.out.println(baos.toString("UTF-8"));
}
public static HttpRequestInitializer getRequestInitializer(final GoogleCredential requestInitializer) {
return httpRequest -> {
requestInitializer.initialize(httpRequest);
httpRequest.setConnectTimeout(readTimeout);
httpRequest.setReadTimeout(connectTimeout);
};
}
public static GoogleCredential getGoogleCredentials() {
GoogleCredential credential;
try {
GoogleCredential.Builder b = new GoogleCredential.Builder().setTransport(httpTransport)
.setJsonFactory(jacksonFactory).setServiceAccountId(serviceAccountId)
.setServiceAccountPrivateKey(SecurityUtils.loadPrivateKeyFromKeyStore(SecurityUtils.getPkcs12KeyStore(),
new FileInputStream(new File(serviceAccountPrivateKeyFile)), serviceAccountPrivateKeyFilePassword,
"privatekey", serviceAccountPrivateKeyFilePassword))
.setServiceAccountScopes(googleScopeList);
if (serviceAccountEmail != null) {
b = b.setServiceAccountUser(serviceAccountEmail);
}
credential = b.build();
} catch (IOException | GeneralSecurityException e1) {
throw new RuntimeException("Could not build client secrets", e1);
}
return credential;
}
}
When I have performed this operation, we are seeing that the viewedByMeTime field is actually being updated as the impersonated user.
This is not good, because now people think someone might have stolen access to their account. They are going to open tickets with the security team.
Is this expected? How can I make this stop? Is there another method in the API I can call to download the google docs without updating this timestamp?
Also opened a ticket on the github for the google drive java sdk: https://github.com/googleapis/google-api-java-client-services/issues/3160
Updating the viewedByMeTime field upon calling the endpoint is indeed intended behaviour. Any action performed through the API is considered the same way as if the user did that action manually (i.e. that field would be updated too when the user visits the document through the UI).
By using domain-wise delegation (or "user impersonation"), you have no way to avoid this issue.
The only workaround would be to give the service account access to this file, and let it export the file without domain-wide delegation. The viewedByMeTime field will be updated only for the service account itself, but not for the original owner of that file (or any other user having access to it).
I've a mobile application that using Google sign and trying to verify the token in backend (java spring).
I've set a few code for that, following many article.
FirebaseServiceCredential.java
This is for firebase connection, because i'm verifying using Admin SDK
package com.nostratech.nostrafood.config;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import com.google.firebase.database.FirebaseDatabase;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import java.io.FileInputStream;
import java.io.IOException;
#Slf4j
#Configuration
public class FirebaseServiceCredential {
public void firebaseConnect() throws IOException {
try {
FileInputStream serviceAccount = new FileInputStream("resources/charity-firebase-adminsdk-ymwjh-61467z75ba.json");
FirebaseOptions options = new FirebaseOptions.Builder()
.setCredentials(GoogleCredentials.fromStream(serviceAccount))
.setDatabaseUrl("https://charity.firebaseio.com/")
.build();
FirebaseApp.initializeApp(options);
FirebaseDatabase.getInstance(FirebaseApp.getInstance()).setPersistenceEnabled(true);
} catch (Exception e) {
log.debug("Trying to login to firebase failed. Reason: " + e.getMessage());
}
}
}
GoogleSignInService.java
This is code for verifyIdToken
package com.nostratech.nostrafood.service.base;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseAuthException;
import com.google.firebase.auth.FirebaseToken;
import org.springframework.stereotype.Service;
#Service
public class GoogleSignInService {
public void verifyToken(String idToken) throws FirebaseAuthException {
FirebaseToken decodedToken =
FirebaseAuth.getInstance().verifyIdToken(idToken);
String uid = decodedToken.getUid();
}
}
What should I do next for verify the token? I've read many article but still stuck dont know what to do.
If verifyIdToken() returns without throwing an exception, then the token is verified. No other action is needed to verify an ID token. The return value (FirebaseToken) gives you access to the UID and the JWT claims associated with the authenticated user.
I'm trying to Read data from Firebase via Java. The addListenerForSingleValueEvent, and anything I put inside it, does not execute. After searching through other related stack questions, I've been unable to find a non-Android related answer. Please take a look and let me know why this method is not executing. Thanks in advance.
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ValueEventListener;
import java.io.*;
public class App {
public static void main( String[] args ) {
class ServiceFile {
private FileInputStream mMyFile;
public ServiceFile(String myFile) {
try {
mMyFile = new FileInputStream(myFile);
System.out.println("The Service File exists. "); // this line prints
} catch (FileNotFoundException ex) {
System.out.println("The Service File does not exist.");
}
}
public FileInputStream getMyFile() {
return mMyFile;
}
}
ServiceFile serviceFile = new ServiceFile("/Users/xxxx/Documents/development/javaFire/serviceAccountKey.json");
FirebaseOptions options = new FirebaseOptions.Builder()
.setServiceAccount(serviceFile.getMyFile())
.setDatabaseUrl("https://xxxx.firebaseio.com")
.build();
// Initialize the app
FirebaseApp myApp = FirebaseApp.initializeApp(options);
// Get the reference to some location within the DB
final FirebaseDatabase database = FirebaseDatabase.getInstance(myApp);
DatabaseReference ref = database.getReference("/users/john");
// Attach a listener to read the data at the DB reference
ref.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot theData) {
System.out.println("Success! "); // this line does not print
}
#Override
public void onCancelled(DatabaseError databaseError) {
System.out.println("Cancelled" + databaseError.getMessage()); // this line does not print
}
});
}
}
My database rules are as such:
{
"rules": {
".read": true,
".write": "auth != null"
}
}
My data in Firebase, at /users.json
{
"john": {
"dob": "02/20/1980"
}
}
It looks like your callback is not executing because your program ends before the callback can receive new data. Remember that addListenerForSingleValueEvent is asynchronous (it returns immediately), so your main function will return immediately, which means the JVM will exit.
At a very minimum, you could put a call to Thread.sleep() at the end of the function to make sure it doesn't terminate immediately. But for a real program, you'll likely be doing something else.
I have been inventing a way how to work around the problem of adding consumers to a jetty endpoint (it does not allow multiple consumers). The way we do it in our company is to build our own router and a broadcasting endpoint which consumes from jetty and routes requests to underlying "subscriptions". Only one of them will eventually process the request. It kind of works but it's not completely ok, since recently when updating to latest Camel we have found our custom built component to leak memory and in general I consider using built-in functionality over custom hacks.
I started investigating the Camel REST API and found it very nice and pretty much replacing our home-grown component apart from one thing - you cannot re-configure it at runtime - you have to stop the context basically for this to work. Below I include my unit test with a happy path and the path that fails. Frankly I think is a bug, but if there is a legitimate way to achieve what I want, I'd like to hear sound advice:
package com.anydoby.camel;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.commons.io.IOUtils;
import org.junit.Before;
import org.junit.Test;
/**
* Test tries to add/remove routes at runtime.
*/
public class RoutesTest {
private DefaultCamelContext ctx;
#Before
public void pre() throws Exception {
ctx = new DefaultCamelContext();
new RouteBuilder(ctx) {
#Override
public void configure() throws Exception {
restConfiguration("jetty").host("localhost").port(8080);
rest("/")
.get("/issues/{isin}").route().id("issues")
.process(e -> e.getOut().setBody("Here's your issue " + e.getIn().getHeader("isin"))).endRest()
.get("/listings").route().id("listings").process(e -> e.getOut().setBody("some listings"));
}
}.addRoutesToCamelContext(ctx);
ctx.start();
}
#Test
public void test() throws IOException {
{
InputStream stream = new URL("http://localhost:8080/issues/35").openStream();
assertEquals("Here's your issue 35", IOUtils.toString(stream));
}
{
InputStream stream = new URL("http://localhost:8080/listings").openStream();
assertEquals("some listings", IOUtils.toString(stream));
}
}
#Test
public void disableRoute() throws Exception {
ctx.stopRoute("issues");
ctx.removeRoute("issues");
try (InputStream stream = new URL("http://localhost:8080/issues/35").openStream()) {
fail();
} catch (Exception e) {
}
new RouteBuilder(ctx) {
#Override
public void configure() throws Exception {
rest().get("/issues/{isin}/{sedol}").route().id("issues")
.process(e -> e.getOut()
.setBody("Here's your issue " + e.getIn().getHeader("isin") + ":" + e.getIn().getHeader("sedol")))
.endRest();
}
}.addRoutesToCamelContext(ctx);
{
InputStream stream = new URL("http://localhost:8080/issues/35/65").openStream();
assertEquals("Here's your issue 35:65", IOUtils.toString(stream));
}
}
}
The disableRoute() test fails since I cannot add another consumer to an existing endpoint.
So my question is - "is there a way to add a new URL mapping to a restful camel-jetty endpoint"? If you do it during first configuration it works fine, but when later you want to reconfigure one of the routes the error is:
org.apache.camel.FailedToStartRouteException: Failed to start route because of Multiple consumers for the same endpoint is not allowed: jetty:http://localhost:8080/issues/%7Bisin%7D/%7Bsedol%7D?httpMethodRestrict=GET