/*
* RHQ Management Platform
* Copyright (C) 2005-2011 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2, as
* published by the Free Software Foundation, and/or the GNU Lesser
* General Public License, version 2.1, also as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License and the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License
* and the GNU Lesser General Public License along with this program;
* if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.rhq.coregui.client;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.InputElement;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.Response;
import com.google.gwt.http.client.URL;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.FormPanel;
import com.smartgwt.client.types.Alignment;
import com.smartgwt.client.types.FormErrorOrientation;
import com.smartgwt.client.types.VerticalAlignment;
import com.smartgwt.client.widgets.Canvas;
import com.smartgwt.client.widgets.HTMLFlow;
import com.smartgwt.client.widgets.IButton;
import com.smartgwt.client.widgets.Img;
import com.smartgwt.client.widgets.Label;
import com.smartgwt.client.widgets.Window;
import com.smartgwt.client.widgets.events.ClickEvent;
import com.smartgwt.client.widgets.events.ClickHandler;
import com.smartgwt.client.widgets.form.DynamicForm;
import com.smartgwt.client.widgets.form.events.SubmitValuesEvent;
import com.smartgwt.client.widgets.form.events.SubmitValuesHandler;
import com.smartgwt.client.widgets.form.fields.CanvasItem;
import com.smartgwt.client.widgets.form.fields.FormItem;
import com.smartgwt.client.widgets.form.fields.HeaderItem;
import com.smartgwt.client.widgets.form.fields.PasswordItem;
import com.smartgwt.client.widgets.form.fields.RowSpacerItem;
import com.smartgwt.client.widgets.form.fields.SpacerItem;
import com.smartgwt.client.widgets.form.fields.SubmitItem;
import com.smartgwt.client.widgets.form.fields.TextItem;
import com.smartgwt.client.widgets.form.fields.events.KeyPressEvent;
import com.smartgwt.client.widgets.form.fields.events.KeyPressHandler;
import com.smartgwt.client.widgets.layout.HStack;
import org.rhq.core.domain.auth.Subject;
import org.rhq.core.domain.common.ProductInfo;
import org.rhq.core.domain.configuration.PropertySimple;
import org.rhq.core.domain.resource.ResourceType;
import org.rhq.coregui.client.gwt.GWTServiceLookup;
import org.rhq.coregui.client.inventory.resource.type.ResourceTypeRepository;
import org.rhq.coregui.client.util.Log;
import org.rhq.coregui.client.util.enhanced.EnhancedIButton;
import org.rhq.coregui.client.util.enhanced.EnhancedVLayout;
import org.rhq.coregui.client.util.message.Message;
import org.rhq.coregui.client.util.validator.EmailValidator;
/**
* @author Greg Hinkle
* @author Joseph Marques
*/
public class LoginView extends Canvas {
private static boolean loginShowing = false;
private static final Messages MSG = CoreGUI.getMessages();
private static final String LOGIN_VIEW = "login";
private Window window;
private FormPanel fakeForm;
private static DynamicForm form;
private DynamicForm inputForm;
private SubmitItem loginButton;
// registration fields
private TextItem first;
private TextItem last;
private TextItem email;
private TextItem phone;
private TextItem department;
private static final String FIRST = "first";
private static final String LAST = "last";
static final String USERNAME = "ldap.username";
private static final String EMAIL = "email";
private static final String PHONE = "phone";
private static final String DEPARTMENT = "department";
private static final String SESSIONID = "ldap.sessionid";
static final String PASSWORD = "ldap.password";
// html login form
private static final String LOGINFORM_ID = "loginForm";
private static final String LOGINBUTTON_ID = "loginSubmit";
private static final String USERNAME_ID = "inputUsername";
private static final String PASSWORD_ID = "inputPassword";
private static final String LOGIN_DIV_ID = "patternFlyLogin";
private static final String LOGIN_ERROR_DIV_ID = "loginError";
private static final String ERROR_FEEDBACK_DIV_ID = "errorFeedback";
private static final String HTML_ID = "htmlId";
private static String errorMessage;
private static volatile boolean isLoginView = true;
private ProductInfo productInfo;
public void showLoginDialog(final String message) {
if (!loginShowing) {
errorMessage = message;
if (!isLoginView()) {
redirectTo(LOGIN_VIEW);
return;
}
showLoginDialog(false);
} else {
form.setErrorsPreamble(message);
form.setFieldErrors("login", message, true);
setLoginError(message);
setLoginButtonDisabled(false);
}
}
public void showLoginDialog(boolean isLogout) {
setLoginButtonDisabled(false);
if (!loginShowing) {
if (isLogout) {
UserSessionManager.logout();
}
if (!isLoginView()) {
redirectTo(LOGIN_VIEW);
return;
}
isLoginView = true;
loginShowing = true;
form = new DynamicForm();
form.setMargin(25);
form.setAutoFocus(true);
form.setShowErrorText(true);
form.setErrorOrientation(FormErrorOrientation.BOTTOM);
// NOTE: This image will either be an RHQ logo or a JON logo.
// but must be 80x40
Img logoImg = new Img("header/rhq_logo_40px.png", 80, 40);
CanvasItem logo = new CanvasItem();
logo.setShowTitle(false);
logo.setCanvas(logoImg);
HeaderItem header = new HeaderItem();
header.setValue(MSG.view_login_prompt());
TextItem user = new TextItem("user", MSG.common_title_user());
user.setRequired(true);
user.setAttribute("autoComplete", "native");
final PasswordItem password = new PasswordItem("password", MSG.common_title_password());
password.setRequired(true);
password.setAttribute("autoComplete", "native");
loginButton = new SubmitItem("login", MSG.view_login_login());
loginButton.setAlign(Alignment.CENTER);
loginButton.setColSpan(2);
user.addKeyPressHandler(new KeyPressHandler() {
public void onKeyPress(KeyPressEvent event) {
if ((event.getCharacterValue() != null)
&& (((event.getCharacterValue() == KeyCodes.KEY_ENTER)) || (event.getCharacterValue() == KeyCodes.KEY_TAB))) {
password.focusInItem(); // Work around the form not getting auto-fill values until the field is focused
}
}
});
password.addKeyPressHandler(new KeyPressHandler() {
public void onKeyPress(KeyPressEvent event) {
if ((event.getCharacterValue() != null) && (event.getCharacterValue() == KeyCodes.KEY_ENTER)) {
form.submit();
}
}
});
form.setFields(logo, header, new RowSpacerItem(), user, password, loginButton);
window = new Window();
window.setWidth(400);
window.setHeight(275);
window.setTitle(MSG.common_title_welcome());
// forced focused, static size, can't close / dismiss
window.setIsModal(true);
window.setShowModalMask(true);
window.setCanDragResize(false);
window.setCanDragReposition(false);
window.setShowCloseButton(false);
window.setShowMinimizeButton(false);
window.setAutoCenter(true);
window.addItem(form);
form.addSubmitValuesHandler(new SubmitValuesHandler() {
public void onSubmitValues(SubmitValuesEvent submitValuesEvent) {
if (form.validate()) {
setUsername(form.getValueAsString("user"));
setPassword(form.getValueAsString("password"));
fakeForm.submit();
}
}
});
// Get a handle to the form and set its action to __gwt_login() method
fakeForm = FormPanel.wrap(Document.get().getElementById(LOGINFORM_ID), false);
fakeForm.setVisible(true);
fakeForm.setAction("javascript:__gwt_login()");
// export the JSNI function
injectLoginFunction(this);
if (errorMessage != null) {
form.setErrorsPreamble(errorMessage);
form.setFieldErrors("login", errorMessage, true);
setLoginError(errorMessage);
errorMessage = null; // hide it next time
}
}
}
/** Duplicate modal Login mechanism to now show last registration screen before launching
* core gui.
*
* @param user prepopulate username field for LDAP registration
* @param sessionId pass in valid session id for LDAP registration steps.
* @param callback pass in callback reference to indicate success and launch of coreGUI
*/
public void showRegistrationDialog(final String user, final String sessionId, final String password,
final AsyncCallback<Subject> callback) {
if (!loginShowing) {
loginShowing = true;
//BZ:784873. To fix issue with users logging in by LDAP integration with clean browser cache.
if (CoreGUI.get().getProductInfo() == null) {
//We need to explicitly retrieve product info here as can't count on CoreGui to load it
//during LDAP registration. After registration CoreGui is loaded as usual.
GWTServiceLookup.getSystemService().getProductInfo(new AsyncCallback<ProductInfo>() {
public void onFailure(Throwable caught) {
CoreGUI.getErrorHandler().handleError(MSG.view_aboutBox_failedToLoad(), caught);
productInfo = null;
Log.warn("ProductInfo could not be retrieved for some reason. Proceeding anyway.");
buildRegistrationWindow(user, sessionId, password, callback);
}
public void onSuccess(ProductInfo result) {
productInfo = result;
Log.info("ProductInfo has been retrieved for LDAP registration.");
buildRegistrationWindow(user, sessionId, password, callback);
}
});
} else {//if productInfo has already been loaded, save a gwt call.
productInfo = CoreGUI.get().getProductInfo();
buildRegistrationWindow(user, sessionId, password, callback);
}
}
}
/** Duplicate modal Login mechanism to now show last registration screen before launching
* core gui.
*
* @param user prepopulate username field for LDAP registration
* @param sessionId pass in valid session id for LDAP registration steps.
* @param callback pass in callback reference to indicate success and launch of coreGUI
*/
private void buildRegistrationWindow(final String user, final String sessionId, final String password,
final AsyncCallback<Subject> callback) {
int fieldWidth = 120;
//Build registration window.
EnhancedVLayout column = new EnhancedVLayout();
column.setMargin(25);
HeaderItem header = new HeaderItem();
//Locate product info for registration screen.
if (productInfo != null) {
header.setValue(MSG.view_login_welcomeMsg(productInfo.getName()));
} else {//if not available, let registration continue. Errors already logged and no functionality lost.
header.setValue(MSG.view_login_welcomeMsg(""));
}
header.setWidth("100%");
//build ui elements for registration screen
first = new TextItem(FIRST, MSG.dataSource_users_field_firstName());
first.setRequired(true);
first.setWrapTitle(false);
first.setWidth(fieldWidth);
last = new TextItem(LAST, MSG.dataSource_users_field_lastName());
last.setWrapTitle(false);
last.setWidth(fieldWidth);
last.setRequired(true);
final TextItem username = new TextItem(USERNAME, MSG.common_title_username());
username.setValue(user);
username.setDisabled(true);
username.setWidth(fieldWidth);
email = new TextItem(EMAIL, MSG.dataSource_users_field_emailAddress());
email.setRequired(true);
email.setWidth(fieldWidth);
email.setWrapTitle(false);
phone = new TextItem(PHONE, MSG.dataSource_users_field_phoneNumber());
phone.setWidth(fieldWidth);
phone.setWrapTitle(false);
department = new TextItem(DEPARTMENT, MSG.dataSource_users_field_department());
department.setWidth(fieldWidth);
SpacerItem space = new SpacerItem();
space.setColSpan(1);
inputForm = new DynamicForm();
inputForm.setAutoFocus(true);
inputForm.setErrorOrientation(FormErrorOrientation.LEFT);
inputForm.setNumCols(4);
//moving header to it's own container for proper display. Didn't display right in production mode
inputForm.setFields(username, first, last, email, phone, department);
loadValidators(inputForm);
inputForm.setValidateOnExit(true);
DynamicForm headerWrapper = new DynamicForm();
headerWrapper.setFields(header);
column.addMember(headerWrapper);
column.addMember(inputForm);
HTMLFlow hr = new HTMLFlow("<br/><hr/><br/><br/>");
hr.setWidth(620);
hr.setAlign(Alignment.CENTER);
column.addMember(hr);
HStack row = new HStack();
row.setMembersMargin(5);
row.setAlign(VerticalAlignment.CENTER);
IButton okButton = new EnhancedIButton(MSG.common_button_ok());
okButton.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
//F5 refresh check? If they've reloaded the form for some reason then bail.
boolean credentialsEmpty = ((user == null) || (user.trim().isEmpty()) || (password == null) || (password
.trim().isEmpty()));
//check for session timeout
if (UserSessionManager.isLoggedOut() || (credentialsEmpty)) {
resetLogin();
return;
}
//validation
if (inputForm.validate()) {
Log.trace("Successfully validated all data for user registration.");
//populate form
if (first.getValue() != null)
inputForm.setValue(FIRST, String.valueOf(first.getValue()));
if (last.getValue() != null)
inputForm.setValue(LAST, String.valueOf(last.getValue()));
inputForm.setValue(USERNAME, String.valueOf(username.getValue()));
if (email.getValue() != null)
inputForm.setValue(EMAIL, String.valueOf(email.getValue()));
if (phone.getValue() != null)
inputForm.setValue(PHONE, String.valueOf(phone.getValue()));
if (department.getValue() != null)
inputForm.setValue(DEPARTMENT, String.valueOf(department.getValue()));
inputForm.setValue(SESSIONID, sessionId);
inputForm.setValue(PASSWORD, password);
registerLdapUser(inputForm, callback);
}
}
});
row.addMember(okButton);
//prepopulate form from user details returned.
Subject subject = UserSessionManager.getSessionSubject();
first.setValue(subject.getFirstName());
last.setValue(subject.getLastName());
email.setValue(subject.getEmailAddress());
phone.setValue(subject.getPhoneNumber());
department.setValue(subject.getDepartment());
IButton resetButton = new EnhancedIButton(MSG.common_button_reset());
resetButton.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
//F5 refresh check? If they've reloaded the form for some reason then bail.
boolean credentialsEmpty = ((user == null) || (user.trim().isEmpty()) || (password == null) || (password
.trim().isEmpty()));
if (UserSessionManager.isLoggedOut() || credentialsEmpty) {
resetLogin();
return;
}
//clear out all validation messages.
String empty = " ";
first.setValue(empty);
last.setValue(empty);
email.setValue("test@test.com");
inputForm.validate();
first.clearValue();
last.clearValue();
email.clearValue();
phone.clearValue();
department.clearValue();
}
});
row.addMember(resetButton);
IButton cancelButton = new EnhancedIButton(MSG.common_button_cancel());
cancelButton.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
UserSessionManager.logout();
resetLogin();
}
});
row.addMember(cancelButton);
Label logoutLabel = new Label(MSG.view_login_registerLater());
logoutLabel.setWrap(false);
row.addMember(logoutLabel);
column.addMember(row);
window = new Window();
window.setWidth(670);
window.setHeight(370);
window.setTitle(MSG.view_login_registerUser());
// forced focused, static size, can't close / dismiss
window.setIsModal(true);
window.setShowModalMask(true);
window.setCanDragResize(false);
window.setCanDragReposition(false);
window.setShowCloseButton(false);
window.setShowMinimizeButton(false);
window.setAutoCenter(true);
window.addItem(column);
window.show();
}
/** Go through steps of invalidating this login and piping them back to CoreGUI Login.
*/
private void resetLogin() {
window.destroy();
loginShowing = false;
showLoginDialog(true);
}
/**Uses the information from the populated form to create the Subject for the new LDAP user.
*
* @param populatedForm - validated data
* @param callback
*/
protected void registerLdapUser(final DynamicForm populatedForm,
final AsyncCallback<Subject> callback) {
final Subject newSubject = UserSessionManager.getSessionSubject();
//insert some required data checking
boolean proceed = true;
String retrieved = populatedForm.getValueAsString(USERNAME);
if ((retrieved == null) || retrieved.isEmpty() || retrieved.equalsIgnoreCase("null")) {
proceed = false;
}
retrieved = populatedForm.getValueAsString(SESSIONID);
if ((retrieved == null) || retrieved.isEmpty() || retrieved.equalsIgnoreCase("null")) {
proceed = false;
}
retrieved = populatedForm.getValueAsString(PASSWORD);
if ((retrieved == null) || retrieved.isEmpty() || retrieved.equalsIgnoreCase("null")) {
proceed = false;
}
newSubject.setName(populatedForm.getValueAsString(USERNAME));
newSubject.setSessionId(Integer.valueOf(populatedForm.getValueAsString(SESSIONID)));
//don't load null values not set or returned from ldap server
retrieved = populatedForm.getValueAsString(FIRST);
if ((retrieved != null) && (!retrieved.equalsIgnoreCase("null")))
newSubject.setFirstName(populatedForm.getValueAsString(FIRST));
retrieved = populatedForm.getValueAsString(LAST);
if ((retrieved != null) && (!retrieved.equalsIgnoreCase("null")))
newSubject.setLastName(populatedForm.getValueAsString(LAST));
retrieved = populatedForm.getValueAsString(DEPARTMENT);
if ((retrieved != null) && (!retrieved.equalsIgnoreCase("null")))
newSubject.setDepartment(populatedForm.getValueAsString(DEPARTMENT));
retrieved = populatedForm.getValueAsString(EMAIL);
if ((retrieved != null) && (!retrieved.equalsIgnoreCase("null")))
newSubject.setEmailAddress(populatedForm.getValueAsString(EMAIL));
retrieved = populatedForm.getValueAsString(PHONE);
if ((retrieved != null) && (!retrieved.equalsIgnoreCase("null")))
newSubject.setPhoneNumber(populatedForm.getValueAsString(PHONE));
// newSubject.setSmsAddress(populatedForm.getValueAsString("sms"));
newSubject.setFactive(true);
if (proceed) {
Log.trace("New LDAP user registration details valid for user '" + newSubject.getName() + "'.");
//proceed with LDAP processing request.
//clear out 'isNewUser' flag.
if (newSubject.getUserConfiguration() != null) {
PropertySimple simple = new PropertySimple("isNewUser", null);
newSubject.getUserConfiguration().put(simple);
}
Set<String> prefsChanges = new HashSet<String>();
prefsChanges.add("isNewUser");
GWTServiceLookup.getSubjectService().updateSubjectAndPreferences(newSubject, prefsChanges, new AsyncCallback<Subject>() {
public void onFailure(Throwable caught) {
Log.error("Failed to register LDAP subject '" + newSubject.getName() + "' " + caught.getMessage(),
caught);
//TODO: pass in warning message to Login Dialog.
showLoginDialog(false);
}
public void onSuccess(Subject checked) {
Log.info("Successfully registered LDAP subject '" + checked + "'.");
checked.setSessionId(Integer.valueOf(populatedForm.getValueAsString(SESSIONID)));
CoreGUI.getMessageCenter().notify(
new Message(MSG.view_login_registerLdapSuccess(), Message.Severity.Info));
window.destroy();
loginShowing = false;
//indicate to login callback success
callback.onSuccess(checked);
}
});
} else {//log them out then reload LoginView
Log.warn("Failed to locate required components to create LDAP subject.");
UserSessionManager.logout();
window.destroy();
loginShowing = false;
//TODO: pass informative message to login.
showLoginDialog(true);
}
}
private void loadValidators(DynamicForm form) {
if (form != null) {
for (FormItem item : form.getFields()) {
String name = item.getName();
if ((name != null) && (!name.isEmpty())) {
if (name.equals(EMAIL)) {
EmailValidator emailValidator = new EmailValidator();
emailValidator.setErrorMessage(MSG.view_login_invalidEmail());
item.setValidators(emailValidator);
}
}
}
}
}
private void login(final String username, final String password) {
setLoginError(null);
setLoginButtonDisabled(true);
try {
RequestBuilder requestBuilder = new RequestBuilder(RequestBuilder.POST, "/portal/j_security_check.do");
requestBuilder.setHeader("Content-Type", "application/x-www-form-urlencoded");
// URL-encode the username and password in case they contain URL special characters ('?', '&', '%', '+',
// etc.), which would corrupt the request if not encoded.
String encodedUsername = URL.encodeQueryString(username);
String encodedPassword = URL.encodeQueryString(password);
String requestData = "j_username=" + encodedUsername + "&j_password=" + encodedPassword;
requestBuilder.setRequestData(requestData);
requestBuilder.setCallback(new RequestCallback() {
public void onResponseReceived(Request request, Response response) {
int statusCode = response.getStatusCode();
if (statusCode == 200) {
window.destroy();
fakeForm.setVisible(false);
loginShowing = false;
UserSessionManager.login(username, password);
setLoginError(null);
} else {
handleError(statusCode);
}
}
public void onError(Request request, Throwable exception) {
handleError(0);
}
});
requestBuilder.send();
} catch (Exception e) {
handleError(0);
}
}
@SuppressWarnings("unused")
private void preloadAllTypeMetadata() {
ResourceTypeRepository.Cache.getInstance().getResourceTypes(null,
EnumSet.allOf(ResourceTypeRepository.MetadataType.class), new ResourceTypeRepository.TypesLoadedCallback() {
public void onTypesLoaded(Map<Integer, ResourceType> types) {
Log.info("Preloaded [" + types.size() + "] resource types");
}
});
}
private void handleError(int statusCode) {
if (statusCode == 401) {
form.setFieldErrors("login", MSG.view_login_noUser(), true);
setLoginError(MSG.view_login_noUser());
} else if (statusCode == 503) {
form.setFieldErrors("login", MSG.view_core_serverInitializing(), true);
setLoginError(MSG.view_core_serverInitializing());
} else {
form.setFieldErrors("login", MSG.view_login_noBackend(), true);
setLoginError(MSG.view_login_noBackend());
}
setLoginButtonDisabled(false);
}
/**
* Call this method to find out if the login dialog is shown
* @return true if it is shown
*/
public static boolean isLoginShowing() {
return loginShowing;
}
private void doSubmitForm() {
form.submit();
}
// called from EcmaScript method (__gwt_login)
private void doLogin() {
login(getUsername(), getPassword());
}
private String getPassword() {
return ((InputElement) Document.get().getElementById(PASSWORD_ID)).getValue();
}
private String getUsername() {
return ((InputElement) Document.get().getElementById(USERNAME_ID)).getValue();
}
private void setPassword(String password) {
((InputElement) Document.get().getElementById(PASSWORD_ID)).setValue(password);
}
private void setUsername(String username) {
((InputElement) Document.get().getElementById(USERNAME_ID)).setValue(username);
}
private void setLoginError(String error) {
Element errorDiv = DOM.getElementById(LOGIN_ERROR_DIV_ID);
Element feedbackDiv = DOM.getElementById(ERROR_FEEDBACK_DIV_ID);
if (errorDiv != null && feedbackDiv != null) {
errorDiv.setInnerHTML(error);
feedbackDiv.setClassName(error != null ? "showError" : "hideError");
}
}
private void setLoginButtonDisabled(boolean disabled) {
if (null != loginButton) {
loginButton.setDisabled(true);
}
Element button = DOM.getElementById(LOGINBUTTON_ID);
if (button != null) {
button.setClassName("btn btn-primary btn-lg" + (disabled ? " disabled" : ""));
}
}
// This is our JSNI method that will be called on form submit
private native void injectLoginFunction(LoginView view) /*-{
$wnd.__gwt_login = $entry(function(){
view.@org.rhq.coregui.client.LoginView::doLogin()();
});
}-*/;
public static boolean isLoginView() {
return isLoginView && com.google.gwt.user.client.Window.Location.getHref().contains(LOGIN_VIEW);
}
public static void redirectTo(final String path) {
new Timer() {
@Override
public void run() {
if (path != null && !("/coregui/" + path).equals(com.google.gwt.user.client.Window.Location.getPath())) {
if (path.isEmpty()) {
isLoginView = false;
}
String destinationFullPath = GWT.getHostPageBaseURL() + path
+ com.google.gwt.user.client.Window.Location.getQueryString()
+ com.google.gwt.user.client.Window.Location.getHash();
Log.info("redirecting to " + destinationFullPath);
com.google.gwt.user.client.Window.Location.assign(destinationFullPath);
}
}
}.schedule(20);
}
}