mercoledì 30 settembre 2009

Using Appengine to store and retrieve data

TBD

giovedì 17 settembre 2009

Geolocation

Keychain_team is actually working on a maps mashup (JooinK) based on the google maps api.
During the development we faced the problem of geolocate the remote user.

We looked a lot around the web and we found that it is possible
to obtain the position of the remote user accessing the "google
loader" ClientLocation field, unfortunately appear not possible to do
this directly through gwt's maps api but defining a jni function


private static native LatLng UserLocation() /*-{
if( $wnd.google ) {
if( $wnd.google.loader ) {
if ($wnd.google.loader.ClientLocation){
var center = new $wnd.google.maps.LatLng(
$wnd.google.loader.ClientLocation.latitude,
$wnd.google.loader.ClientLocation.longitude
);
return center;
}
}
}
return null;
}-*/;


and then calling the native method


LatLng userDefaultLocation = UserDefaultLocation();


We get the LatLng position of the user
Do not forget to load google's jsapi, e.g. putting in the head of your html file


<script type="text/javascript"
src="http://www.google.com/jsapi">
</script>


you will find soon the complete project on http://www.keychain.it

That's all folk!!

martedì 8 settembre 2009

Authentication on demand (using appengine UserService)

During the development of our map's mashup (JooinK, still in progress) we faced the problem of handling authentication (in this post we focus on Google Accounts).

Our target was to let the user to authenticate on demand during the navigation of JooinK without losing the current status (say, the current position on the map). We know this is in no way an unexplored field (actually, blogspot does something similar) but we think that the description of the complete procedure worth a note.

So let us suppose we want to write a simple application (named JooinK ;-) ) to be published on Google App Engine using the provided UserService (see http://code.google.com/webtoolkit/tutorials/1.6/appengine.html#user for the general overview).

Roughly speaking authentication on-demand should proceed as follows:

  1. the user navigate anonymously JooinK;
  2. the user begin the process of authentication (e.g. triggered by a component of the user interface);
  3. the control is transferred to the "google login page";
  4. at the end of the authentication process the control returns back to JooinK;
  5. the user interface changes accordingly to the result of the authentication, not loosing the "current status".


In particular:

Step a. represents a first look to the application by an anonymous user, which can navigate in and, for instance, choose a location on the map.

To proceed through step b. it is required to know the URL of the google account's identity provider, which has to be retrieved using a GWT-RPC call to an ad hoc service, as described in the aforementioned example and quoted below:


public class LoginServiceImpl
extends RemoteServiceServlet
implements LoginService {

public LoginInfo login(String ComeBackURL) {
UserService userService
= UserServiceFactory.getUserService();
User user = userService.getCurrentUser();
LoginInfo loginInfo = new LoginInfo();

if(user != null) {
loginInfo.setLoggedIn(true);
loginInfo.setEmailAddress(user.getEmail());
loginInfo.setNickname(user.getNickname());
loginInfo.setLogoutUrl(
userService.createLogoutURL(ComeBackURL));
} else {
loginInfo.setLoggedIn(false);
loginInfo.setLoginUrl(
userService.createLoginURL(ComeBackURL));
}
return loginInfo;
}
}


After a successful call to the login service the returned LoginInfo object will contain:

  • informations about the user i.e. username etc, for a logged in user;

  • the URL of the identity provider for the anonymous user.


Please note that ComeBackURL is the URL of the page where the idp will redirect the user's browser on completion of the login process, i.e. it is the URL of the part of our code that performs Step d. (we will give further details later on this paper).

When the user express the aim of authenticating (so entering into step b), say with a click on the "let me authenticate" link, we call the loginService as in the following code:


// do something informing the user that
// we are processing the
// authentication request,
//e.g. change the link text with something
//else
.....
loginService.login(ComeBackUrl,
new AsyncCallback<logininfo>() {
public void onFailure(Throwable error) {
// error comunication with the server
//.... handle this.
Window.alert('Something unhappy happened
to Jooink, try later');
}
public void onSuccess(LoginInfo result) {
loginInfo = result;
if(loginInfo.isLoggedIn()) {
// the user is already logged in ...
// nothing to do
// modify the user interface
// e.g remove the authentication link and
// sostitute it with the logout one
// show somewhere the username
//(it is inside loginInfo)
} else {
// the user is not logged in
// we have to pass the control to the idp
manageLogin(loginInfo);
}
);


Here the function manageLogin(...) represents step c and is obviously executed only if the user is not already authenticated.


public void manageLogin(LoginInfo li) {
dialogBox = new DialogBox();
dialogBox.setModal(true)
dialogBox.setText("-- Authentication --");
final Frame idpFrame
= new NamedFrame("idpFrame");
dialogBox.add(idpInteractionFrame);
idpFrame.setUrl(loginInfo.getLoginUrl());
}


The purpose of the above function is to let the user interact directly with the identity provider but this cannot be realized simply redirecting the user to loginInfo.loginURL because this will
trash the status of the application (or force the status to be serialized somewhere in ComeBackUrl), instead we use an IFRAME (IFrames or windows appear to be the only choice here).

Actually the shown code does what we expect, i.e. let the user to authenticate but unfortunately, after a successful login we will get ComeBackUrl loaded inside the FRAME that may be not the desired result (this is where we enter into step d: taking back control).

Definitely what we want is: closing the dialog box, having a variable in our code bounded with the current username.
To accomplish this we need two ingredients:
  • a servlet associated to ComeBackUrl;
  • a bit of jsni;
as shown in the following code:

The servlet is simply a non RPC version of LoginServiceImpl (but we cannot call the RPC as a result of a redirection) that returns an html page containing javascript cote that "injects" variables in the running GWT application.


...
public class LoginServlet extends HttpServlet {
public void doGet(HttpServletRequest request,
HttpServletResponse res)
throws ServletException {
UserService userService
= UserServiceFactory.getUserService();
User user = userService.getCurrentUser();
LoginInfo loginInfo = new LoginInfo();
if (user != null) {
returnSuccess(res,
user.getEmail(),user.getNickname());
} else {
returnFailure(res,
user.getLoginUrl(ComeBackUrl));
}
return;
}
}


Unfortunately now we are unable to return the loginInfo as we did in the RPC: a servlet cannot return an object to our application so we have to play with html, plain javascript and jsni.

The returnFailure method is the simpler, we have to generate an html noticing the user that some problem happened and eventually a courtesy link to let the user to retry authentication. For instance:


public void
returnFailure(HttpServletResponse res,
String loginURL) {
try {
PrintWriter out = response.getWriter();
out.println("Autentication UNsuccessfull. " +
"Retry");
} catch (Exception e) {
...
}
return;
}


On the other hand returnSuccess should trigger a javascript method to close the dialog and "inject" username and email in the application running in the browser. Let us remark that the following code is for demonstration purpose and is cumbersome because a more elegant
implementation will require to describe a more structured set of classes that are out of the scope of this note.


public void
returnSuccess(HttpServletResponse res,
String email, String name) {
String onload =
"window.parent.userData('"+name+"');";
onLoad +=
"window.parent.userEmail('"+email+"');";
onLoad += "window.parent.dialogHide();";
try {
PrintWriter out = response.getWriter();
out.println("Autentication OK");
} catch (Exception e) {
...
}
return;
}


where userData, userEmail, dialogHide (step e!) have to be provided by jsni; to do this we have to call, e.g. in manageLogin just after the assignment of dialogBox, a special function (actually a native function) defined as follows:


public static native void
prepareAuthHookMethods(LoginInfo logI,
DialogBox db) /*-{
$wnd.userName = function(s) {
return
logI.@FULL_PACKAGE_NAME_HERE.
LoginInfo::
setUserName(Ljava/lang/String)(s);
}
$wnd.userEmail = function(s) {
return
logI.@FULL_PACKAGE_NAME_HERE.
LoginInfo::
setUserEmail(Ljava/lang/String)(s);
}
$wnd.dialogHide= function() {
return
db.@FULL_PACKAGE_NAME_HERE.
LoginInfo::hide()();
}
}


That's all folks.