Amol Chakane Amol Chakane - 4 months ago 27
reST (reStructuredText) Question

How to return Respone from Jersey REST api method which has listners inside

I am trying to create REST apis to perform write operations using Firebase Server SDK and Jersey.

Here is what I am trying to do:


  1. Initializing Firebase SDK

  2. Getting idToken from Client and Validating idToken

  3. Performing some write operations on few Firebase nodes

  4. Sending api Response



I want to return Response only if Firebase write operation succeeds or fails.
That happens inside of Firebase Listeners. However, I try to return value from Listeners, I get error becuase Firebase listeners have void return type.

How can I return response from the listener?

Here is my code.

MyEndpoint.java

@Path("/restapi")
//BaseEndpoint does Authentication operations
public class MyEndpoint extends BaseEndpoint {

public void Authenticate(String token, AuthenticationListener authenticationResultListener) {
super.Authenticate(token, authenticationResultListener);
}

@POST
@Produces("application/json")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response createCommunity(@FormParam("idToken") String idToken) {
Authenticate(idToken, new AuthenticationListener() {
@Override
public void onAuthenticationSuccess() {
// The app only has access as defined in the Security Rules
DatabaseReference ref = FirebaseDatabase.getInstance().getReference("/mysamplenode");
ref.addListenerForSingleValueEvent(new ValueEventListener() {
public void onDataChange(DataSnapshot dataSnapshot) {
String res = dataSnapshot.getKey();
//I will do some write operations on Firebase here
//If they complete successfuly only then I want to return api response
return Response.status(200).entity().build();
}
@Override
public void onCancelled(DatabaseError arg0) {
System.out.println("DatabaseError-------------" + arg0);
return Response.status(500).entity("DatabaseError").build();
}
});
}
@Override
public void onAuthenticationFailure() {
return Response.status(403).entity("Forbidden").build();
}
});
}
}


BaseEndpoint.java

public class BaseEndpoint {

public static String uid;

public void Authenticate(String token, AuthenticationListener authenticationResultListener) {
// idToken comes from the client app
FirebaseAuth.getInstance().verifyIdToken(token).addOnFailureListener(new OnFailureListener() {

@Override
public void onFailure(Exception arg0) {
System.out.println("Uid exp= " + arg0);
authenticationResultListener.onAuthenticationFailure();
}
}).addOnSuccessListener(new OnSuccessListener<FirebaseToken>() {
@Override
public void onSuccess(FirebaseToken decodedToken) {
uid = decodedToken.getUid();
System.out.println("Uid= " + uid);
authenticationResultListener.onAuthenticationSuccess();
}
});
}
}

Answer

So I'm guessing the Firebase SDK is asynchronous in nature. In which case, if you're using Jersey 2.x, you can use the asynchronous server support. You can just call AsyncResponse.resume(Response) in your callbacks. For example

@POST
@Produces("application/json")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public void createCommunity(@FormParam("idToken") String idToken,
                                @Suspended final AsyncResponse response) {
    Authenticate(idToken, new AuthenticationListener() {
        @Override
        public void onAuthenticationSuccess() {
            DatabaseReference ref = FirebaseDatabase.getInstance()
                    .getReference("/mysamplenode");
            ref.addListenerForSingleValueEvent(new ValueEventListener() {
                public void onDataChange(DataSnapshot dataSnapshot) {
                    String res = dataSnapshot.getKey();                        
                    response.resume(Response.status(200).entity().build());
                }
                @Override
                public void onCancelled(DatabaseError arg0) {
                    response.resume(Response.status(500).entity("DatabaseError").build());
                }
            });
        }
        @Override
        public void onAuthenticationFailure() {
            response.resume(Response.status(403).entity("Forbidden").build());
        }
    });
}

If you're using Jersey 1.x, there's not really an async option. You just need to block flow with a CountDownLatch, assign a local Response in the callbacks, and count down.

@POST
@Produces("application/json")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public void createCommunity(@FormParam("idToken") String idToken) {
    Response response;
    CountDownLatch latch = new CountDownLatch(1);
    Authenticate(idToken, new AuthenticationListener() {
        @Override
        public void onAuthenticationSuccess() {
            DatabaseReference ref = FirebaseDatabase.getInstance()
                    .getReference("/mysamplenode");
            ref.addListenerForSingleValueEvent(new ValueEventListener() {
                public void onDataChange(DataSnapshot dataSnapshot) {
                    String res = dataSnapshot.getKey();                        
                    response = Response.status(200).entity().build();
                    latch.countDown();
                }
                @Override
                public void onCancelled(DatabaseError arg0) {
                    response = Response.status(500).entity("DatabaseError").build();
                    latch.countDown();
                }
            });
        }
        @Override
        public void onAuthenticationFailure() {
            response = Response.status(403).entity("Forbidden").build();
            latch.countDown();
        }
    });
    latch.await();
    return response;
}