Saturday, January 11, 2014

Multitenancy and Android client: frustrating experience with GAE endpoints

When I read isolated technical documents published by Google, they all make sense.  When I start combining them together to build a working product, things often fall apart.  Here is one example.
Let's assume we have a GAE endpoint that requires authentication (more information on authenticated endpoints can be found in Testing authenticated GAE endpoints in Android Studio).  Let's also assume we want to build a multi-tenant application using Namespaces.  The goal is have a namespace per user as described in Creating namespaces on a per user basis.  The key point is to use user ID as the namespace name.  Let's try getting user ID first:
@Api(
    name = "testendpoint",
    namespace = @ApiNamespace(ownerDomain = Consts.API_OWNER_DOMAIN, 
        ownerName = Consts.API_OWNER_NAME, 
        packagePath = Consts.API_PACKAGE_PATH),
    scopes = { Consts.EMAIL_SCOPE },
    clientIds = { Consts.WEB_CLIENT_ID, Consts.ANDROID_CLIENT_ID, 
        Consts.ANDROID_CLIENT_ID_TEST },
    audiences = { Consts.ANDROID_AUDIENCE }
)
public class TestEndpoint {
    @ApiMethod(name = "getUserInfo", path = "getUserInfo")
    public UserInfo getUserInfo(User user) throws OAuthRequestException {
        if (user == null) {
            throw new OAuthRequestException("Method requires authentication");
        }
        return new UserInfo(user.getEmail(), user.getUserId());
    }
    // ...
}
Since authenticated @ApiMethods have user parameter, we do not need to use UserServiceFactory to get the current user.  All we need to do is to call user.getUserId().  We deploy our application to GAE, open the API explorer and test getUserInfo() method.  Everything works fine, both user.getEmail() and user.getUserId() return correct values.
Since we want to use our GAE application from an Android client, we want to verify that getUserInfo() also works when called from an Android application:
public void testUserInfo() throws Exception {
    final UserInfo ui = endpoint.getUserInfo().execute();
    Log.d(TAG, "userId: " + ui.getUserId());
    Log.d(TAG, "email: " + ui.getEmail());
}
Unfortunately it does not: getEmail() returns correct email address, but getUserId() returns null.  At this point we think that may be the user object that is passed to getUserInfo() method is created differently from the User object retrieved from UserServicesFactory.  To verify this assumption we add another method to TestEndpoint class:
    @ApiMethod(name = "getUserInfo2", path = "getUserInfo2")
    public UserInfo getUserInfo2(User user) throws OAuthRequestException {
        if (user == null) {
            throw new OAuthRequestException("Method requires authentication");
        }
        final User currentUser = 
            UserServiceFactory.getUserService().getCurrentUser();
        return new UserInfo(currentUser.getEmail(), currentUser.getUserId());
    }
We re-deploy our application to GAE, open the API explorer and test getUserInfo2(). Unfortunately, the method does not work even in the API explorer: the returned currentUser is null and our application crashes with NullPointerException. The same situation occurs when getUserInfo2() is called from an Android client:
public void testUserInfo2() throws Exception {
    final UserInfo ui = endpoint.getUserInfo2().execute();
    Log.d(TAG, "userId: " + ui.getUserId());
    Log.d(TAG, "email: " + ui.getEmail());
}
Setting a per-user namespace in a namespace filter also fails:
@Override
public void doFilter(ServletRequest request, 
    ServletResponse response, FilterChain chain) 
    throws IOException, ServletException {

    final UserService usrSvc = UserServiceFactory.getUserService();
    final User user = usrSvc.getCurrentUser();
    NamespaceManager.set(user.getUserId());
}
The returned user object is null and the next line fails with NullPointerException.
There is an open issue related to User.getUserId() returning null that has not been fixed for almost a year. Until this issue is addressed it will be impossible to create multi-tenant applications that utilize namespaces on per-user basis and work with Android clients. Hopefully the issue will be addressed soon. +Google Developers 

No comments:

Post a Comment