Java tutorial
/* * Copyright (C) 2011 University of Washington * * 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 org.opendatakit.aggregate.client; import java.util.ArrayList; import java.util.HashMap; import org.opendatakit.aggregate.client.exception.RequestFailureException; import org.opendatakit.aggregate.client.handlers.RefreshCloseHandler; import org.opendatakit.aggregate.client.handlers.RefreshOpenHandler; import org.opendatakit.aggregate.client.handlers.RefreshSelectionHandler; import org.opendatakit.aggregate.client.preferences.Preferences; import org.opendatakit.aggregate.client.preferences.Preferences.PreferencesCompletionCallback; import org.opendatakit.aggregate.constants.common.HelpSliderConsts; import org.opendatakit.aggregate.constants.common.SubTabs; import org.opendatakit.aggregate.constants.common.Tabs; import org.opendatakit.aggregate.constants.common.UIConsts; import org.opendatakit.common.security.client.RealmSecurityInfo; import org.opendatakit.common.security.client.UserSecurityInfo; import org.opendatakit.common.security.common.GrantedAuthorityName; import com.google.gwt.core.client.EntryPoint; import com.google.gwt.core.client.GWT; import com.google.gwt.event.logical.shared.BeforeSelectionEvent; import com.google.gwt.event.logical.shared.BeforeSelectionHandler; import com.google.gwt.event.logical.shared.SelectionEvent; import com.google.gwt.event.logical.shared.SelectionHandler; import com.google.gwt.safehtml.shared.SafeHtmlUtils; import com.google.gwt.user.client.Cookies; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.rpc.InvocationException; import com.google.gwt.user.client.ui.DecoratedTabPanel; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.ScrollPanel; import com.google.gwt.user.client.ui.TabPanel; import com.google.gwt.user.client.ui.Tree; import com.google.gwt.user.client.ui.TreeItem; import com.google.gwt.user.client.ui.Widget; public class AggregateUI implements EntryPoint { private UrlHash hash; private Label notSecureMsgLabel; private FlowPanel notSecurePanel; private Label errorMsgLabel; private FlowPanel errorPanel; private FlowPanel wrappingLayoutPanel; private HorizontalPanel layoutPanel; private ScrollPanel helpPanel; private TreeItem rootItem; private NavLinkBar settingsBar; private DecoratedTabPanel mainNav; // tab datastructures private HashMap<Tabs, AggregateTabBase> tabMap; private ArrayList<Tabs> tabPosition; private RefreshTimer timer; private static AggregateUI singleton = null; // session variables for tab visibility private static boolean manageVisible = false; private static boolean adminVisible = false; private static boolean odkTablesVisible = false; // hack... public static final String QUOTA_EXCEEDED = "Quota exceeded"; /*********************************** ***** SINGLETON FETCHING ****** ***********************************/ public static synchronized final AggregateUI getUI() { if (singleton == null) { // if you get here, you've put something in the AggregateUI() // constructor that should have been put in the onModuleLoad() // method. GWT.log("AggregateUI.getUI() called before singleton has been initialized"); } return singleton; } public RefreshTimer getTimer() { return timer; } /*********************************** ***** INITIALIZATION ****** ***********************************/ private AggregateUI() { /* * CRITICAL NOTE: Do not do **anything** in this constructor that might * cause something underneath to call AggregateUI.get() * * The singleton is not yet assigned!!! */ singleton = null; timer = new RefreshTimer(this); // define the not-secure message info... notSecureMsgLabel = new Label(); notSecureMsgLabel.setStyleName("not_secure_message"); notSecurePanel = new FlowPanel(); notSecurePanel.add(notSecureMsgLabel); notSecurePanel.setVisible(false); // define the error message info... errorMsgLabel = new Label(); errorMsgLabel.setStyleName("error_message"); // put the not-secure and error messages in an error panel errorPanel = new FlowPanel(); errorPanel.add(errorMsgLabel); errorPanel.setVisible(false); // create tab datastructures tabMap = new HashMap<Tabs, AggregateTabBase>(); tabPosition = new ArrayList<Tabs>(); wrappingLayoutPanel = new FlowPanel(); // vertical wrappingLayoutPanel.setStylePrimaryName(UIConsts.VERTICAL_FLOW_PANEL_STYLENAME); layoutPanel = new HorizontalPanel(); mainNav = new DecoratedTabPanel(); mainNav.getElement().setId("mainNav"); mainNav.addSelectionHandler(new RefreshSelectionHandler<Integer>()); // add to layout layoutPanel.add(mainNav); layoutPanel.getElement().setId("layout_panel"); wrappingLayoutPanel.add(layoutPanel); helpPanel = new ScrollPanel(); // Create help panel Tree helpTree = new Tree(); rootItem = new TreeItem(); helpTree.addItem(rootItem); helpTree.addOpenHandler(new RefreshOpenHandler<TreeItem>()); helpTree.addCloseHandler(new RefreshCloseHandler<TreeItem>()); helpTree.getElement().setId("help_tree"); helpPanel.add(helpTree); helpPanel.getElement().setId("help_panel"); helpPanel.setVisible(false); wrappingLayoutPanel.add(helpPanel); settingsBar = new NavLinkBar(); RootPanel.get("not_secure_content").add(notSecurePanel); RootPanel.get("error_content").add(errorPanel); RootPanel.get("dynamic_content").add( new HTML("<img src=\"images/odk_color.png\" id=\"odk_aggregate_logo\" class=\"gwt-Image\" />")); RootPanel.get("dynamic_content").add(settingsBar); RootPanel.get("dynamic_content").add(wrappingLayoutPanel); } private void addTabToDatastructures(AggregateTabBase tabPanel, Tabs tab) { int insertIndex = tabPosition.size(); // add tabPanel into position tabPosition.add(insertIndex, tab); tabMap.put(tab, tabPanel); } @Override public void onModuleLoad() { // Get url hash. hash = UrlHash.getHash(); hash.get(); userInfo = null; // assign the singleton here... singleton = this; beforeContentLoaded(); // start the refresh timer... timer.setInitialized(); // Update the user security info. // This gets the identity and privileges of the // user to the UI and the realm of that user. // The success callback then renders the requested // page and warms up the various sub-tabs and // displays the highlighted tab. initializeUserAndPreferences(); } private void initializeUserAndPreferences() { lastUserInfoUpdateAttemptTimestamp = lastRealmInfoUpdateAttemptTimestamp = System.currentTimeMillis(); // the first realm request will often fail because the cookie // should/could be null (e.g., for anonymous users). SecureGWT.getSecurityService().getRealmInfo(Cookies.getCookie("JSESSIONID"), new AsyncCallback<RealmSecurityInfo>() { @Override public void onFailure(Throwable caught) { if (caught instanceof RequestFailureException) { RequestFailureException e = (RequestFailureException) caught; if (e.getMessage().equals(QUOTA_EXCEEDED)) { return; // Don't retry -- we have a quota problem... } } // OK. So it failed. If it did, just try doing everything // again. We should have a valid session cookie after the // first failure, so these should all then work. SecureGWT.getSecurityService().getUserInfo(new AsyncCallback<UserSecurityInfo>() { @Override public void onFailure(Throwable caught) { reportError(caught); } @Override public void onSuccess(UserSecurityInfo result) { userInfo = result; if (realmInfo != null && userInfo != null) { Preferences.updatePreferences(new PreferencesCompletionCallback() { @Override public void refreshFromUpdatedPreferences() { commonUserInfoUpdateCompleteAction(); } @Override public void failedRefresh() { commonUserInfoUpdateCompleteAction(); } }); } } }); SecureGWT.getSecurityService().getRealmInfo(Cookies.getCookie("JSESSIONID"), new AsyncCallback<RealmSecurityInfo>() { @Override public void onFailure(Throwable caught) { reportError(caught); } @Override public void onSuccess(RealmSecurityInfo result) { realmInfo = result; updateNotSecureInfo(); if (realmInfo != null && userInfo != null) { commonUserInfoUpdateCompleteAction(); } } }); } @Override public void onSuccess(RealmSecurityInfo result) { realmInfo = result; updateNotSecureInfo(); // it worked the first time! Now do the user info request. SecureGWT.getSecurityService().getUserInfo(new AsyncCallback<UserSecurityInfo>() { @Override public void onFailure(Throwable caught) { reportError(caught); } @Override public void onSuccess(UserSecurityInfo result) { userInfo = result; if (realmInfo != null && userInfo != null) { Preferences.updatePreferences(new PreferencesCompletionCallback() { @Override public void refreshFromUpdatedPreferences() { commonUserInfoUpdateCompleteAction(); } @Override public void failedRefresh() { commonUserInfoUpdateCompleteAction(); } }); } } }); } }); } public void updateOdkTablesFeatureVisibility() { if (authorizedForTab(Tabs.ODKTABLES)) { odkTablesVisible = Preferences.getOdkTablesEnabled(); AggregateTabBase odkTables = getTab(Tabs.ODKTABLES); if (odkTables != null) { odkTables.setVisible(odkTablesVisible); } for (int i = 0; i < mainNav.getWidgetCount(); i++) { Widget w = mainNav.getWidget(i); if (w != null && w instanceof OdkTablesTabUI) { w.setVisible(odkTablesVisible); ((Widget) mainNav.getTabBar().getTab(i)).setVisible(odkTablesVisible); ((OdkTablesTabUI) w).updateVisibilityOdkTablesSubTabs(); } } if (authorizedForTab(Tabs.ADMIN)) { AggregateTabBase AminTab = AggregateUI.getUI().getTab(Tabs.ADMIN); if (AminTab != null && AminTab instanceof AdminTabUI) { AdminTabUI adminTab = (AdminTabUI) AminTab; if (odkTablesVisible) { adminTab.displayOdkTablesSubTab(); } else { adminTab.hideOdkTablesSubTab(); } } else { AggregateUI.getUI().reportError(new Throwable("ERROR: SOME HOW CAN'T FIND ADMIN TAB")); } } } else { AggregateTabBase odkTables = getTab(Tabs.ODKTABLES); if (odkTables != null) { odkTables.setVisible(false); } for (int i = 0; i < mainNav.getWidgetCount(); i++) { Widget w = mainNav.getWidget(i); if (w != null && w instanceof OdkTablesTabUI) { w.setVisible(false); ((Widget) mainNav.getTabBar().getTab(i)).setVisible(false); ((OdkTablesTabUI) w).updateVisibilityOdkTablesSubTabs(); } } if (authorizedForTab(Tabs.ADMIN)) { AggregateTabBase AminTab = AggregateUI.getUI().getTab(Tabs.ADMIN); if (AminTab != null && AminTab instanceof AdminTabUI) { AdminTabUI adminTab = (AdminTabUI) AminTab; adminTab.hideOdkTablesSubTab(); } else { AggregateUI.getUI().reportError(new Throwable("ERROR: SOME HOW CAN'T FIND ADMIN TAB")); } } } } private void commonUserInfoUpdateCompleteAction() { settingsBar.update(); SubmissionTabUI submissions = new SubmissionTabUI(this); addTabToDatastructures(submissions, Tabs.SUBMISSIONS); ManageTabUI management = new ManageTabUI(this); addTabToDatastructures(management, Tabs.MANAGEMENT); OdkTablesTabUI odkTables = new OdkTablesTabUI(this); addTabToDatastructures(odkTables, Tabs.ODKTABLES); AdminTabUI admin = new AdminTabUI(this); addTabToDatastructures(admin, Tabs.ADMIN); // Create the only tab that ALL users can see sub menu navigation mainNav.add(submissions, Tabs.SUBMISSIONS.getTabLabel()); mainNav.getElement().getFirstChildElement().getFirstChildElement().addClassName("tab_measure_1"); if (userInfo != null) { if (authorizedForTab(Tabs.MANAGEMENT)) { mainNav.add(management, Tabs.MANAGEMENT.getTabLabel()); manageVisible = true; } if (authorizedForTab(Tabs.ODKTABLES)) { mainNav.add(odkTables, Tabs.ODKTABLES.getTabLabel()); } if (authorizedForTab(Tabs.ADMIN)) { mainNav.add(admin, Tabs.ADMIN.getTabLabel()); adminVisible = true; } // Select the correct menu item based on url hash. int selected = 0; String mainMenu = hash.get(UrlHash.MAIN_MENU); for (int i = 0; i < tabPosition.size() && i < mainNav.getWidgetCount(); i++) { if (mainMenu.equals(tabPosition.get(i).getHashString())) { selected = i; } } mainNav.selectTab(selected); } updateOdkTablesFeatureVisibility(); // AND schedule an async operation to // refresh the tabs that are not selected. Timer t = new Timer() { @Override public void run() { // warm up the underlying UI tabs... for (AggregateTabBase tab : tabMap.values()) { tab.warmUp(); } } }; t.schedule(1000); contentLoaded(); resize(); } // Let's JavaScript know that the GWT content has been loaded // Currently calls into javascript/resize.js, if we add more JavaScript // then that should be changed. public native void contentLoaded() /*-{ $wnd.gwtContentLoaded(); }-*/; public native void beforeContentLoaded() /*-{ $wnd.gwtBeforeContentLoaded(); }-*/; public native static void resize() /*-{ $wnd.onAggregateResize(); }-*/; /*********************************** ****** NAVIGATION ****** ***********************************/ public void redirectToSubTab(SubTabs subTab) { for (Tabs tab : tabPosition) { AggregateTabBase tabObj = tabMap.get(tab); if (tabObj == null) { continue; } SubTabInterface subTabObj = tabObj.getSubTab(subTab); if (subTabObj != null) { // found the tab int index = tabPosition.indexOf(tab); mainNav.selectTab(index); tabObj.selectTab(tabObj.findSubTabIndex(subTab)); } } resize(); } public SubTabInterface getSubTab(SubTabs subTabType) { SubTabInterface subTab = null; for (AggregateTabBase tab : tabMap.values()) { subTab = tab.getSubTab(subTabType); if (subTab != null) { return subTab; } } return subTab; } public AggregateTabBase getTab(Tabs tabType) { return tabMap.get(tabType); } void setSubMenuSelectionHandler(final TabPanel menuTab, final Tabs menu, final SubTabs[] subMenus) { // add the mainNav selection handler for this menu... mainNav.addSelectionHandler(new SelectionHandler<Integer>() { @Override public void onSelection(SelectionEvent<Integer> event) { if (userInfo == null) { GWT.log("getSubMenuSelectionHandler: No userInfo - not setting selection"); return; } int selected = event.getSelectedItem(); String tabHash = menu.getHashString(); Tabs tab = tabPosition.get(selected); if (tab == null) { return; } if (tabHash.equals(tab.getHashString())) { if (!authorizedForTab(tab)) { return; } // and select the appropriate subtab... AggregateTabBase tabObj = tabMap.get(menu); if (tabObj != null) { tabObj.selectTab(tabObj.findSubTabIndexFromHash(hash)); } } } }); menuTab.addBeforeSelectionHandler(new BeforeSelectionHandler<Integer>() { @Override public void onBeforeSelection(BeforeSelectionEvent<Integer> event) { // allow the currently-selected SubTab to refuse the tab selection. // refusal should only occur after user confirmation. if (!getTimer().canLeaveCurrentSubTab()) { event.cancel(); } } }); menuTab.addSelectionHandler(new SelectionHandler<Integer>() { @Override public void onSelection(SelectionEvent<Integer> event) { if (userInfo == null) { GWT.log("getSubMenuSelectionHandler: No userInfo - not setting subMenu selection"); return; } int selected = event.getSelectedItem(); clearError(); hash.clear(); hash.set(UrlHash.MAIN_MENU, menu.getHashString()); hash.set(UrlHash.SUB_MENU, subMenus[selected].getHashString()); getTimer().setCurrentSubTab(subMenus[selected]); hash.put(); changeHelpPanel(subMenus[selected]); } }); } /*********************************** ****** SECURITY ****** ***********************************/ private boolean authorizedForTab(Tabs tab) { switch (tab) { case SUBMISSIONS: return userInfo.getGrantedAuthorities().contains(GrantedAuthorityName.ROLE_DATA_VIEWER); case MANAGEMENT: return userInfo.getGrantedAuthorities().contains(GrantedAuthorityName.ROLE_DATA_OWNER); case ADMIN: return userInfo.getGrantedAuthorities().contains(GrantedAuthorityName.ROLE_SITE_ACCESS_ADMIN); case ODKTABLES: return userInfo.getGrantedAuthorities().contains(GrantedAuthorityName.ROLE_SYNCHRONIZE_TABLES) || userInfo.getGrantedAuthorities().contains(GrantedAuthorityName.ROLE_ADMINISTER_TABLES); default: return false; } } private long lastUserInfoUpdateAttemptTimestamp = 0L; private UserSecurityInfo userInfo = null; private long lastRealmInfoUpdateAttemptTimestamp = 0L; private RealmSecurityInfo realmInfo = null; public UserSecurityInfo getUserInfo() { if (userInfo == null) { GWT.log("AggregateUI.getUserInfo: userInfo is null"); } if (lastUserInfoUpdateAttemptTimestamp + RefreshTimer.SECURITY_REFRESH_INTERVAL < System .currentTimeMillis()) { // record the attempt lastUserInfoUpdateAttemptTimestamp = System.currentTimeMillis(); GWT.log("AggregateUI.getUserInfo: triggering refresh of userInfo"); SecureGWT.getSecurityService().getUserInfo(new AsyncCallback<UserSecurityInfo>() { @Override public void onFailure(Throwable caught) { reportError(caught); } @Override public void onSuccess(UserSecurityInfo result) { userInfo = result; } }); } return userInfo; } public void updateNotSecureInfo() { if (realmInfo != null) { if (!realmInfo.isSuperUsernamePasswordSet()) { notSecureMsgLabel.setText( "This server and its data are not secure! Please change the super-user's password!"); notSecurePanel.setVisible(true); resize(); } else if (notSecurePanel.isVisible()) { notSecureMsgLabel.setText(""); notSecurePanel.setVisible(false); resize(); } } } public void forceUpdateNotSecureInfo() { lastRealmInfoUpdateAttemptTimestamp = 0L; getRealmInfo(); } public RealmSecurityInfo getRealmInfo() { if (realmInfo == null) { GWT.log("AggregateUI.getRealmInfo: realmInfo is null"); } if (lastRealmInfoUpdateAttemptTimestamp + RefreshTimer.SECURITY_REFRESH_INTERVAL < System .currentTimeMillis()) { // record the attempt lastRealmInfoUpdateAttemptTimestamp = System.currentTimeMillis(); GWT.log("AggregateUI.getRealmInfo: triggering refresh of realmInfo"); SecureGWT.getSecurityService().getRealmInfo(Cookies.getCookie("JSESSIONID"), new AsyncCallback<RealmSecurityInfo>() { @Override public void onFailure(Throwable caught) { reportError(caught); } @Override public void onSuccess(RealmSecurityInfo result) { realmInfo = result; updateNotSecureInfo(); } }); } return realmInfo; } /*********************************** ****** HELP STUFF ****** ***********************************/ private void changeHelpPanel(SubTabs subMenu) { // change root item rootItem.setText(subMenu + " Help"); rootItem.removeItems(); SubTabInterface subTabObj = getSubTab(subMenu); if (subTabObj != null) { HelpSliderConsts[] helpVals = subTabObj.getHelpSliderContent(); if (helpVals != null) { for (int i = 0; i < helpVals.length; i++) { TreeItem helpItem = new TreeItem(SafeHtmlUtils.fromString(helpVals[i].getTitle())); TreeItem content = new TreeItem(SafeHtmlUtils.fromString(helpVals[i].getContent())); helpItem.setState(false); helpItem.addItem(content); rootItem.addItem(helpItem); } } } rootItem.setState(true); resize(); } public void displayErrorPanel() { errorPanel.setVisible(true); } public void hideErrorPanel() { errorPanel.setVisible(false); } public void displayHelpPanel() { helpPanel.setVisible(true); resize(); } public void hideHelpPanel() { helpPanel.setVisible(false); resize(); } public Boolean displayingHelpBalloons() { return settingsBar.showHelpBalloons(); } /*********************************** ****** ERROR STUFF ****** ***********************************/ public void reportError(Throwable t) { reportError("Error: ", t); } public void reportError(String context, Throwable t) { String textMessage; if (t instanceof org.opendatakit.common.persistence.client.exception.DatastoreFailureException) { textMessage = context + t.getMessage(); } else if (t instanceof org.opendatakit.common.security.client.exception.AccessDeniedException) { textMessage = "You do not have permission for this action.\n" + context + t.getMessage(); } else if (t instanceof InvocationException) { // could occur if the cached JavaScript is out-of-sync with server UrlHash.redirect(); return; } else if (t.getMessage().contains("uuid:081e8b57-1698-4bbf-ba5b-ae31338b121d")) { // magic number for the service-error.html page. // Generally means an out-of-quota error. UrlHash.redirect(); return; } else { textMessage = context + t.getMessage(); } errorMsgLabel.setText(textMessage); displayErrorPanel(); resize(); Window.alert(textMessage); } public void clearError() { hideErrorPanel(); } }