diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/AbstractAsyncCallback.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/AbstractAsyncCallback.java
index 1ff36d543e0df4cb22447e9d268481551d0a1c20..805cb2fff3011bf9a5077b991af6f3ecc3729d5c 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/AbstractAsyncCallback.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/AbstractAsyncCallback.java
@@ -33,6 +33,15 @@ import ch.systemsx.cisd.openbis.generic.client.web.client.exception.InvalidSessi
 public abstract class AbstractAsyncCallback<T> implements AsyncCallback<T>
 {
     private static final String PREFIX = "exception_";
+    private static ICallbackListener callbackListener = DummyCallbackListener.DUMMY_LISTENER;
+    
+    /**
+     * Sets the global callback listener. Note: THIS METHOD SHOULD ONLY BE USED IN TEST CODE.
+     */
+    public static void setCallbackListener(ICallbackListener listenerOrNull)
+    {
+        callbackListener = listenerOrNull == null ? DummyCallbackListener.DUMMY_LISTENER : listenerOrNull;
+    }
     
     protected final GenericViewContext viewContext;
     
@@ -46,6 +55,7 @@ public abstract class AbstractAsyncCallback<T> implements AsyncCallback<T>
     
     public void onFailure(Throwable caught)
     {
+        callbackListener.onFailureOf(this, caught);
         System.out.println(caught);
         final String msg;
         if (caught instanceof InvocationException)
@@ -76,4 +86,15 @@ public abstract class AbstractAsyncCallback<T> implements AsyncCallback<T>
         }
     }
 
-}
+    public final void onSuccess(T result)
+    {
+        callbackListener.startOnSuccessOf(this, result);
+        process(result);
+        callbackListener.finishOnSuccessOf(this, result);
+    }
+
+    /**
+     * Processes the specified result of an asynchronous method invocation.
+     */
+    protected abstract void process(T result);
+}
\ No newline at end of file
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/Client.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/Client.java
index c21b2fe5302005a257f5038f6db11ae2208d2470..406b5ec2b75725bdd068ed9db949292fbf4ab5b2 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/Client.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/Client.java
@@ -36,42 +36,24 @@ import ch.systemsx.cisd.openbis.generic.client.web.client.dto.SessionContext;
  */
 public class Client implements EntryPoint
 {
-    private GenericViewContext viewContext;
-
+    GenericViewContext viewContext;
+    AbstractAsyncCallback<SessionContext> loginCallback;
+    
     public void onModuleLoad()
     {
         if (viewContext == null)
         {
             viewContext = createViewContext();
         }
+        loginCallback = loginCallback();
         final IGenericClientServiceAsync service = viewContext.getService();
         service.getApplicationInfo(new AbstractAsyncCallback<ApplicationInfo>(viewContext)
             {
-                public void onSuccess(ApplicationInfo info)
+                @Override
+                public void process(ApplicationInfo info)
                 {
                     viewContext.getModel().setApplicationInfo(info);
-                    service
-                            .tryToGetCurrentSessionContext(new AbstractAsyncCallback<SessionContext>(
-                                    viewContext)
-                                {
-                                    public void onSuccess(SessionContext sessionContext)
-                                    {
-                                        RootPanel rootPanel = RootPanel.get();
-                                        rootPanel.clear();
-                                        Component widget;
-                                        if (sessionContext == null)
-                                        {
-                                            widget = new LoginPage(viewContext);
-                                        } else
-                                        {
-                                            viewContext.getModel()
-                                                    .setSessionContext(sessionContext);
-                                            widget = new Application(viewContext);
-                                        }
-                                        rootPanel.add(widget);
-                                    }
-                                });
-
+                    service.tryToGetCurrentSessionContext(loginCallback);
                 }
             });
     }
@@ -94,4 +76,27 @@ public class Client implements EntryPoint
         return new GenericViewContext(service, messageProvider, imageBundle, pageController);
     }
 
+    private AbstractAsyncCallback<SessionContext> loginCallback()
+    {
+        return new AbstractAsyncCallback<SessionContext>(viewContext)
+            {
+                @Override
+                public void process(SessionContext sessionContext)
+                {
+                    RootPanel rootPanel = RootPanel.get();
+                    rootPanel.clear();
+                    Component widget;
+                    if (sessionContext == null)
+                    {
+                        widget = new LoginPage(viewContext);
+                    } else
+                    {
+                        viewContext.getModel().setSessionContext(sessionContext);
+                        widget = new Application(viewContext);
+                    }
+                    rootPanel.add(widget);
+                }
+            };
+    }
+
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/DummyCallbackListener.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/DummyCallbackListener.java
new file mode 100644
index 0000000000000000000000000000000000000000..117d127313cfe615ded78495d196334f88f5c0d2
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/DummyCallbackListener.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2008 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.generic.client.web.client.application;
+
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+/**
+ * Dummy implementation of a callback listener which does nothing.
+ *
+ * @author Franz-Josef Elmer
+ */
+class DummyCallbackListener implements ICallbackListener
+{
+    /** The one and only one instance of this class. */
+    static final ICallbackListener DUMMY_LISTENER = new DummyCallbackListener();
+    
+    private DummyCallbackListener()
+    {
+    }
+
+    public <T> void onFailureOf(AsyncCallback<T> callback, Throwable throwable)
+    {
+    }
+
+    public <T> void startOnSuccessOf(AsyncCallback<T> callback, T result)
+    {
+    }
+
+    public <T> void finishOnSuccessOf(AsyncCallback<T> callback, T result)
+    {
+    }
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ICallbackListener.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ICallbackListener.java
new file mode 100644
index 0000000000000000000000000000000000000000..2488a5a69925a12738af2f327e11f8725ecb0422
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ICallbackListener.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2008 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.generic.client.web.client.application;
+
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+/**
+ * Listener of invocations of methods of {@link AsyncCallback}.
+ *
+ * @author Franz-Josef Elmer
+ */
+public interface ICallbackListener
+{
+    /**
+     * Handles invocations of {@link AsyncCallback#onFailure(Throwable)} of the specified
+     * callback object with the specified throwable. This method will be invoked before
+     * the callback object is actually handling the failure.
+     */
+    public <T> void onFailureOf(AsyncCallback<T> callback, Throwable throwable);
+    
+    /**
+     * Handles invocations of {@link AsyncCallback#onSuccess(Object)} of the specified
+     * callback object with the specified result object. This method will be invoked before
+     * the callback object is actually processing the result object.
+     */
+    public <T> void startOnSuccessOf(AsyncCallback<T> callback, T result);
+    
+    /**
+     * Handles invocations of {@link AsyncCallback#onSuccess(Object)} of the specified
+     * callback object with the specified result object. This method will be invoked after
+     * the callback object is actually processing the result object.
+     */
+    public <T> void finishOnSuccessOf(AsyncCallback<T> callback, T result);
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/AddGroupDialog.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/AddGroupDialog.java
index 33afa6ab95f0c02c20589e8aa9dc50bd9fb2692a..5d691b2887beff2d81331a7c51cfbd4f5ef08f55 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/AddGroupDialog.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/AddGroupDialog.java
@@ -67,7 +67,8 @@ public class AddGroupDialog extends Window
                             descriptionField.getValue(), null,
                             new AbstractAsyncCallback<Void>(viewContext)
                                 {
-                                    public void onSuccess(Void result)
+                                    @Override
+                                    public void process(Void result)
                                     {
                                         hide();
                                         groupList.refresh();
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/AddPersonDialog.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/AddPersonDialog.java
index d9c489d8da9954743621a5124824c55073dcd4a1..68fa42dbf87fe554b48a03e909ec53aab2b7fac6 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/AddPersonDialog.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/AddPersonDialog.java
@@ -61,7 +61,8 @@ public class AddPersonDialog extends Window
                     viewContext.getService().registerPerson(codeField.getValue(),
                             new AbstractAsyncCallback<Void>(viewContext)
                                 {
-                                    public void onSuccess(Void result)
+                                    @Override
+                                    public void process(Void result)
                                     {
                                         hide();
                                         personList.refresh();
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/AddRoleDialog.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/AddRoleDialog.java
index f4c6a83bca867fa318be8d7eea59b6b119ae2cc4..ca69d5d4c75ed06a104b38353669d6a7b1f0375a 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/AddRoleDialog.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/AddRoleDialog.java
@@ -73,7 +73,8 @@ public class AddRoleDialog extends Window
                     final AbstractAsyncCallback<Void> roleLoadingCallback =
                             new AbstractAsyncCallback<Void>(viewContext)
                                 {
-                                    public void onSuccess(Void result)
+                                    @Override
+                                    public void process(Void result)
                                     {
                                         hide();
                                         roleList.refresh();
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/GroupsView.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/GroupsView.java
index 70ff037aebadd8c4d81ab93928862b5702404c3e..a0940ec9d0a11e508715593ddb6c5eb3daa80787 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/GroupsView.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/GroupsView.java
@@ -171,7 +171,8 @@ public class GroupsView extends LayoutContainer
         viewContext.getService().listGroups(null,
                 new AbstractAsyncCallback<List<Group>>(viewContext)
                     {
-                        public void onSuccess(List<Group> groups)
+                        @Override
+                        public void process(List<Group> groups)
                         {
                             display(groups);
                         }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/LoginWidget.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/LoginWidget.java
index 127fce4f66536035d438c36a134b082ad3e9db5f..8d180c24dff2ea76d98d9ec69188a753a9e6ff21 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/LoginWidget.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/LoginWidget.java
@@ -55,13 +55,16 @@ public class LoginWidget extends VerticalPanel
         userField.setSelectOnFocus(true);
         userField.setAllowBlank(false);
         userField.setValidateOnBlur(true);
+        userField.setId("login-user");
         formPanel.add(userField);
         passwordField = new TextField<String>();
         passwordField.setPassword(true);
         passwordField.setAllowBlank(false);
         passwordField.setFieldLabel(viewContext.getMessage(PREFIX + "passwordLabel"));
+        passwordField.setId("login-password");
         formPanel.add(passwordField);
         Button button = new Button(viewContext.getMessage(PREFIX + "buttonLabel"));
+        button.setId("login-button");
         button.addSelectionListener(new SelectionListener<ComponentEvent>()
             {
                 @Override
@@ -77,10 +80,12 @@ public class LoginWidget extends VerticalPanel
     
     private void login(final GenericViewContext viewContext)
     {
+        System.out.println("LoginWidget.login()");
         viewContext.getService().tryToLogin(userField.getValue(), passwordField.getValue(),
                 new AbstractAsyncCallback<SessionContext>(viewContext)
                     {
-                        public void onSuccess(SessionContext sessionContext)
+                        @Override
+                        public void process(SessionContext sessionContext)
                         {
                             viewContext.getPageController().reload();
                         }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/PersonsView.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/PersonsView.java
index 14183ba0bae7bea29a6fe97f2ed5da7c48dab5ba..ea68e39461ea339629091a5eff9b678f94b84272 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/PersonsView.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/PersonsView.java
@@ -173,7 +173,8 @@ public class PersonsView extends LayoutContainer
         add(new Text("data loading..."));
         viewContext.getService().listPersons(new AbstractAsyncCallback<List<Person>>(viewContext)
             {
-                public void onSuccess(List<Person> persons)
+                @Override
+                public void process(List<Person> persons)
                 {
                     display(persons);
                 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/RolesView.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/RolesView.java
index 46c631a744eee6a15f7f6d0cbc333e3ebea5a824..95b5e64c863e8f01580de4a8a06de57616118b3b 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/RolesView.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/RolesView.java
@@ -132,7 +132,8 @@ public class RolesView extends LayoutContainer
                     final AbstractAsyncCallback<Void> roleListRefreshCallback =
                             new AbstractAsyncCallback<Void>(viewContext)
                                 {
-                                    public void onSuccess(Void result)
+                                    @Override
+                                    public void process(Void result)
                                     {
                                         roleList.refresh();
                                     }
@@ -183,7 +184,8 @@ public class RolesView extends LayoutContainer
         viewContext.getService().listRoles(
                 new AbstractAsyncCallback<List<RoleAssignment>>(viewContext)
                     {
-                        public void onSuccess(List<RoleAssignment> roles)
+                        @Override
+                        public void process(List<RoleAssignment> roles)
                         {
                             display(roles);
                         }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/TopMenu.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/TopMenu.java
index f5ad8f73129747c358e5bba52eeba4332e495c84..cf8fa1303ee7b62feb70507857ccfbea2cf3f0ee 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/TopMenu.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/TopMenu.java
@@ -65,7 +65,8 @@ public class TopMenu extends LayoutContainer
                         viewContext.getService().logout(
                                 new AbstractAsyncCallback<Void>(viewContext)
                                     {
-                                        public void onSuccess(Void result)
+                                        @Override
+                                        public void process(Void result)
                                         {
                                             viewContext.getPageController().reload();
                                         }