HomePresenter.java

/*
** Module   : HomePresenter.java
** Abstract : HomePresenter is a main presenter.
**
** Copyright (c) 2017, Golden Code Development Corporation.
**
** -#- -I- --Date-- ----------------Description----------------------
** 001 HC 20170228 Created initial version.
** 002 EVL 20170926 Javadoc fixes.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** 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 Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
** 
** Additional terms under GNU Affero GPL version 3 section 7:
** 
**   Under Section 7 of the GNU Affero GPL version 3, the following additional
**   terms apply to the works covered under the License.  These additional terms
**   are non-permissive additional terms allowed under Section 7 of the GNU
**   Affero GPL version 3 and may not be removed by you.
** 
**   0. Attribution Requirement.
** 
**     You must preserve all legal notices or author attributions in the covered
**     work or Appropriate Legal Notices displayed by works containing the covered
**     work.  You may not remove from the covered work any author or developer
**     credit already included within the covered work.
** 
**   1. No License To Use Trademarks.
** 
**     This license does not grant any license or rights to use the trademarks
**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
**     of Golden Code Development Corporation. You are not authorized to use the
**     name Golden Code, FWD, or the names of any author or contributor, for
**     publicity purposes without written authorization.
** 
**   2. No Misrepresentation of Affiliation.
** 
**     You may not represent yourself as Golden Code Development Corporation or FWD.
** 
**     You may not represent yourself for publicity purposes as associated with
**     Golden Code Development Corporation, FWD, or any author or contributor to
**     the covered work, without written authorization.
** 
**   3. No Misrepresentation of Source or Origin.
** 
**     You may not represent the covered work as solely your work.  All modified
**     versions of the covered work must be marked in a reasonable way to make it
**     clear that the modified work is not originating from Golden Code Development
**     Corporation or FWD.  All modified versions must contain the notices of
**     attribution required in this license.
*/

package com.goldencode.p2j.admin.client.application.home;

import java.util.*;
import java.util.function.Consumer;
import java.util.logging.Level;

import com.goldencode.p2j.admin.*;
import com.goldencode.p2j.admin.client.*;
import com.goldencode.p2j.admin.client.application.*;
import com.goldencode.p2j.admin.client.application.PrintingContext.PreviewDialogParameters;
import com.goldencode.p2j.admin.client.application.PrintingContext.Range;
import com.goldencode.p2j.admin.client.application.home.accounts.AccountType;
import com.goldencode.p2j.admin.client.widget.dialog.*;
import com.goldencode.p2j.admin.shared.*;
import com.google.gwt.core.shared.GWT;
import com.google.gwt.dom.client.*;
import com.google.gwt.dom.client.Element;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.*;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.*;
import com.google.inject.*;
import com.google.web.bindery.event.shared.*;
import com.gwtplatform.mvp.client.*;
import com.gwtplatform.mvp.client.annotations.*;
import com.gwtplatform.mvp.client.presenter.slots.*;
import com.gwtplatform.mvp.client.proxy.*;
import com.gwtplatform.mvp.shared.proxy.*;

/**
 * Home presenter.
 */
public class HomePresenter
extends BasePresenter<HomePresenter.MyView, HomePresenter.MyProxy>
implements MenuService
{
   /**
    * Presenter's view.
    */
   interface MyView
   extends View
   {
      /**
       * Returns the modal widget parent.
       *
       * @return  See above.
       */
      HasWidgets getModalParent();
   }

   /**
    * The presenter's proxy.
    */
   @ProxyStandard
   @NameToken(NameTokens.HOME)
   interface MyProxy
   extends ProxyPlace<HomePresenter>
   {
   }

   /** Menu slot */
   public static final PermanentSlot<MenuPresenter> SLOT_MENU = new PermanentSlot<>();

   /** Content slot */
   public static final NestedSlot SLOT_CONTENT = new NestedSlot();

   /** Callback called at the time the menu needs to be initialized */
   private static MenuSetupCallback menuSetupCallback;

   /** Flag that is set to true when the menu is built */
   private static boolean menuBuilt = false;

   /** Menu presenter */
   private MenuPresenter menu;

   /** Input dialog provider */
   private Provider<InputDialog> inputDialog;

   /** Admin service reference */
   private AdminServiceAsync adm;

   /** Alarm service reference */
   private Alarm alarm;

   /** Modal dialogs */
   private ModalDialogs modalDialogs;

   /** Place token formatter reference */
   private TokenFormatter tokenFormatter;

   /**
    * Ctor.
    *
    * @param   eventBus
    *          Event bus reference.
    * @param   view
    *          Presenter's view.
    * @param   proxy
    *          Presenter's proxy.
    * @param   menu
    *          Menu presenter.
    * @param   inputDialog
    *          Input dialog presenter.
    * @param   adm
    *          Admin service reference.
    * @param   alarm
    *          Alarm service reference.
    * @param   modalDialogs
    *          Modal dialogs.
    * @param   placeManager
    *          Place manager reference.
    * @param   tokenFormatter
    *          Token formatter reference.
    */
   @Inject
   HomePresenter(EventBus eventBus,
                 MyView view,
                 MyProxy proxy,
                 MenuPresenter menu,
                 Provider<InputDialog> inputDialog,
                 AdminServiceAsync adm,
                 Alarm alarm,
                 ModalDialogs modalDialogs,
                 PlaceManager placeManager,
                 TokenFormatter tokenFormatter)
   {
      super(eventBus, view, proxy, ApplicationPresenter.SLOT_MAIN, placeManager);
      this.menu = menu;
      this.inputDialog = inputDialog;
      this.adm = adm;
      this.alarm = alarm;
      this.modalDialogs = modalDialogs;
      this.tokenFormatter = tokenFormatter;
   }

   /**
    * Sets the menu setup callback.
    *
    * @param   callback
    *          An instance of {@link MenuSetupCallback}.
    */
   public static void registerMenuSetupCallback(MenuSetupCallback callback)
   {
      menuSetupCallback = callback;
   }

   /**
    * Adds menu entry into the main menu at the specified position.
    *
    * @param   position
    *          The position where the new menu entry will be added.
    * @param   menuDef
    *          The new menu entry.
    */
   @Override
   public void addMenu(StandardMenu position, MenuDef menuDef)
   {
      Document doc = Document.get();
      Element menu = doc.getElementById(position.getId()).getParentElement();
      Element menuParent = menu.getParentElement();
      LIElement newMenu = doc.createLIElement();

      StringBuilder sb = new StringBuilder();
      sb.append("<a href=\"#\" class=\"dropdown-toggle\" data-toggle=\"dropdown\">");
      sb.append(menuDef.label);
      sb.append("<b class=\"caret\"></b></a>");
      sb.append("<ul class=\"dropdown-menu multi-level\">");

      if (menuDef.subMenus != null)
      {
         for (MenuDef subMenu : menuDef.subMenus)
         {
            // <li><a href="#{nameTokens.getSessions}">Sessions</a></li>
            sb.append("<li><a href=");
            if (subMenu.placeToken != null)
            {
               sb.append("\"#" + subMenu.placeToken + "\"");
            }
            else
            {
               sb.append("\"javascript:void(0)\"");
               sb.append(" id=\"ext-menu_" + subMenu.hashCode() +"\"");
            }
            sb.append("\">");
            sb.append(subMenu.label);
            sb.append("</a></li>");
         }
      }

      sb.append("</ul>");
      newMenu.setInnerHTML(sb.toString());
      menuParent.insertAfter(newMenu, menu);

      // bind handlers
      if (menuDef.subMenus != null)
      {
         for (MenuDef subMenu : menuDef.subMenus)
         {
            String id = "" + subMenu.hashCode();
            Element el = doc.getElementById("ext-menu_" + id);
            if (el == null)
            {
               continue;
            }

            Event.sinkEvents(el, Event.ONCLICK | Event.ONTOUCHSTART);
            Event.setEventListener(el, event ->
            {
               if (menuDef.handler != null)
               {
                  menuDef.handler.run();
               }
            });
         }
      }
   }

   /**
    * Appends menu entry under the given standard menu at the specified position that defines
    * the target site within the given standard menu. If the specified position is out of the
    * existing range or a negative value, then the menu entry is appended at the end of the menu
    * item list of the given standard menu.
    *
    * @param   position
    *          Defines the standard menu to which the new menu entry will be added as a sub menu.
    * @param   subMenuPosition
    *          Defines the menu items site where the new menu entry will be added.
    * @param   menuDef
    *          The new menu entry.
    */
   @Override
   public void appendNewItemTo(StandardMenu position, int subMenuPosition, MenuDef menuDef)
   {
      Document doc = Document.get();
      Element menu = doc.getElementById(position.getId()).getParentElement();
      Element[] subMenuArray = findDescendants(menu, "li.dropdown-submenu");
      
      boolean outOfRange = (subMenuArray.length <= subMenuPosition) || (subMenuPosition < 0);
      
      Element site;
      
      if (outOfRange)
      {
         Element[] holder = findDescendants(menu, "ul.dropdown-menu");
         site = holder[0];
      }
      else
      {
         site = subMenuArray[subMenuPosition];
         Element[] holder = findDescendants(site, "ul.dropdown-menu");
         site = holder[0];
      }
      
      LIElement newMenu = doc.createLIElement();

      StringBuilder sb = new StringBuilder();

      sb.append("<a href=");
      if (menuDef.placeToken != null)
      {
         sb.append("\"#" + menuDef.placeToken + "\"");
      }
      else
      {
         sb.append("\"javascript:void(0)\"");
         sb.append(" id=\"ext-menu_" + menuDef.hashCode() +"\"");
      }
      sb.append("\">");
      sb.append(menuDef.label);
      sb.append("</a>");
      
      newMenu.setInnerHTML(sb.toString());
      
      site.insertAfter(newMenu, null);

      // bind handler
      String id = "" + menuDef.hashCode();
      Element el = doc.getElementById("ext-menu_" + id);
      if (el != null)
      {
         Event.sinkEvents(el, Event.ONCLICK | Event.ONTOUCHSTART);
         Event.setEventListener(el, event ->
         {
            if (menuDef.handler != null)
            {
               menuDef.handler.run();
            }
         });
      }
   }

   /**
    * Lifecycle method called when binding the object.
    * <p>
    * <b>Important :</b> Make sure you call your parent class {@link #onBind()}. Also, do
    * not call directly, call {@link #bind()} instead.
    * <p>
    * Any event handler should be
    * initialised here rather than in the constructor. Also, it is good practice to
    * perform any costly initialisation here.
    */
   @Override
   protected void onBind()
   {
      super.onBind();
      setInSlot(SLOT_MENU, menu);

      // do this very early in home presenter to check valid authenticated session. if the
      // session is not authenticated server will return 401 and AdminCallback will redirect
      // to login
      adm.getServerName(new AdminCallback<String>()
      {
         @Override
         public void onDone(String result)
         {
            // no-op
         }
      });
   }

   /**
    * Lifecycle method called whenever this presenter is about to be
    * revealed.
    */
   @Override
   protected void onReveal()
   {
      super.onReveal();

      if (menuBuilt)
      {
         return;
      }

      setupLogOffHandler();
      setupBackupDirectoryHandler();
      setupReloadDirectoryHandler();
      setupShutdownHandler();
      setupShowACLByAccountHandler();
      setupBulkAuthModeUpdateHandler();
      setupClearMessagesHandler();
      setupAboutAdminClientHandler();
      setupPrintHandler();
      setupTargetRefreshHandler();
      setupTargetLiveHandler();

      buildMenu();

      if (menuSetupCallback != null)
      {
         menuSetupCallback.onMenuInitialized(this);
      }

      menuBuilt = true;
   }

   /**
    * Initilized the logoff link handler.
    */
   private void setupLogOffHandler()
   {
      Element el = Document.get().getElementById("menu-logoff");
      Event.sinkEvents(el, Event.ONCLICK | Event.ONTOUCHSTART);
      Event.setEventListener(el, event ->
      {
         modalDialogs.showYesNo("Logoff Confirmation",
                                "You are about to be logged off. Continue?",
                                MessageType.QUESTION,
         res ->
         {
            if (res != 0)
            {
               return;
            }

            adm.isRefreshPending(new AdminCallback<Boolean>()
            {
               @Override
               public void onDone(Boolean result)
               {
                  if (result != null && result)
                  {
                     modalDialogs.showYesNo("Refresh Confirmation",
                                            "Warning: there are changes to the " +
                                            "directory which have not yet been re-read into " +
                                            "the security cache. Refresh cache now?",
                                            MessageType.QUESTION,
                                            button ->
                                            {
                                               if (button == 0)
                                               {
                                                  logoff();
                                               }
                                            });
                  }
                  else
                  {
                     logoff();
                  }
               }
            });
         });
      });
   }

   /**
    * Initializes the backup directory menu item click handler.
    */
   private void setupBackupDirectoryHandler()
   {
      Element el = Document.get().getElementById("menu-backup-directory");
      Event.sinkEvents(el, Event.ONCLICK | Event.ONTOUCHSTART);
      Event.setEventListener(el, event ->
      {
         InputDialog.Field fInput;
         InputDialog dlg = inputDialog.get();
         dlg.build("Backing up Directory",
                   "Enter the backup file name and press the Backup button",
                   new InputDialog.Field[]
         {
            fInput = new InputDialog.Field(null, String.class)
         });

         dlg.setSaveButtonText("Backup");

         dlg.show(getView().getModalParent(), vals ->
         {
            if (vals != null)
            {
               String path = (String) vals.get(fInput);
               adm.backupDirectory(path, new AdminCallback<Boolean>(adm, alarm)
               {
                  @Override
                  public void onDone(Boolean result)
                  {
                     // no-op
                  }
               });
            }
         });
      });
   }

   /**
    * Initializes the reload directory menu item click handler.
    */
   private void setupReloadDirectoryHandler()
   {
      Element el = Document.get().getElementById("menu-reload-directory");
      Event.sinkEvents(el, Event.ONCLICK | Event.ONTOUCHSTART);
      Event.setEventListener(el, event ->
      {
         InputDialog.Field fInput;
         InputDialog dlg = inputDialog.get();
         dlg.build("Reloading Directory",
                   "Enter the backup file name and press the Reload button",
         new InputDialog.Field[]
         {
            fInput = new InputDialog.Field(null, String.class)
         });

         dlg.setSaveButtonText("Reload");

         dlg.show(getView().getModalParent(), vals ->
         {
            if (vals != null)
            {
               String path = (String) vals.get(fInput);
               adm.backupDirectory(path, new AdminCallback<Boolean>(adm, alarm)
               {
                  @Override
                  public void onDone(Boolean result)
                  {
                     // no-op
                  }
               });
            }
         });
      });
   }

   /**
    * Initializes the server shutdown menu item click handler.
    */
   private void setupShutdownHandler()
   {
      Element el = Document.get().getElementById("menu-shutdown");
      Event.sinkEvents(el, Event.ONCLICK | Event.ONTOUCHSTART);
      Event.setEventListener(el, event ->
      {
         modalDialogs.showYesNo("Server Shutdown Confirmation",
                                "You are about to shut the server down. Continue?",
                                MessageType.QUESTION,
                                res ->
                                {
                                   if (res != 0)
                                   {
                                      return;
                                   }

                                   adm.shutdown(new AdminCallback<Boolean>(adm, alarm)
                                   {
                                      @Override
                                      public void onDone(Boolean result)
                                      {
                                         if (result != null && result)
                                         {
                                            PlaceRequest req = new PlaceRequest.Builder()
                                                               .nameToken("").build();

                                            getPlaceManager().revealPlace(req);
                                         }
                                      }
                                   });
                                });
      });
   }

   /**
    * Initializes the bulk auth mode menu item click handler.
    */
   private void setupBulkAuthModeUpdateHandler()
   {
      Element el = Document.get().getElementById("mitem-bulk-auth-mode-update");
      Event.sinkEvents(el, Event.ONCLICK | Event.ONTOUCHSTART);
      Event.setEventListener(el, event -> {
         modalDialogs.showYesNo(
                  "Confirm Bulk Update",
                  "WARNING, authentication mode will be updated for ALL "
                           + "accounts of the specified type.\nDo you want to continue?",
                  MessageType.QUESTION,
                  res -> {
                     if (res != 0)
                     {
                        return;
                     }
                     BulkAuthModeUpdateView.Binder binder = GWT
                              .create(BulkAuthModeUpdateView.Binder.class);

                     BulkAuthModeUpdateView bulkAuthModeUpdateView = new BulkAuthModeUpdateView(
                              binder);

                     getView().getModalParent().add(bulkAuthModeUpdateView.getModal());
                     
                     AsyncAction<Boolean, TaggedName[]> action1
                        = new AsyncAction<Boolean, TaggedName[]>()
                     {
                        @Override
                        public void accept(Boolean v)
                        {
                           adm.listAuthPlugins(delegate);
                        }

                        @Override
                        public void onFailure(Throwable caught)
                        {
                           logger.log(Level.SEVERE, "Failed to call listAuthPlugins", caught);
                        }
                     };

                     action1.then(new Consumer<TaggedName[]>(){

                        @Override
                        public void accept(TaggedName[] result)
                        {
                           bulkAuthModeUpdateView.setCustomAuthPlugins(result);
                           bulkAuthModeUpdateView.getModal().show();

                           bulkAuthModeUpdateView.addHideHandler(() -> {
                              getView().getModalParent().remove(bulkAuthModeUpdateView.getModal());
                           });

                           bulkAuthModeUpdateView.getUpdateAction().addClickHandler(new ClickHandler()
                           {

                              @Override
                              public void onClick(ClickEvent event)
                              {
                                 int targetAccounts = AccountType.valueOf(
                                          bulkAuthModeUpdateView.getAccountsType()
                                          .getSelectedValue()).getType();
                                 ClientContext ctxt = ClientContext.instance();
                                 String identity = ctxt.getIdentity();
                                 String[] excludedAccounts = new String[] {identity};
                                 int authMode = bulkAuthModeUpdateView.getModes().getSelectedIndex();
                                 String authPlugin;
                                 if (bulkAuthModeUpdateView.getPlugins().isEnabled())
                                 {
                                    authPlugin = bulkAuthModeUpdateView.getPlugins().getSelectedValue();
                                 }
                                 else
                                 {
                                    authPlugin = "";
                                 }
                                 adm.bulkAuthModeUpdate(
                                          targetAccounts,
                                          excludedAccounts,
                                          authMode,
                                          authPlugin,
                                          new AdminCallback<Integer>(adm, alarm)
                                          {
                                             @Override
                                             public void onDone(Integer result)
                                             {
                                                logger.info("updated #accounts: " + result);
                                                
                                                if (result != null && result >= 0)
                                                {
                                                   bulkAuthModeUpdateView.getModal().hide();
                                                }
                                             }
                                          });
                              }
                           });
                        }
                     });
                     action1.accept(true);
                  });
      });
   }

   /**
    * Initializes the clear server messages menu item click handler.
    */
   private void setupClearMessagesHandler()
   {
      Element el = Document.get().getElementById("menu-clear-messages");
      Event.sinkEvents(el, Event.ONCLICK | Event.ONTOUCHSTART);
      Event.setEventListener(el, event ->
      {
         PlaceRequest req = new PlaceRequest.Builder().nameToken(NameTokens.SERVER_MESSAGES).with("action", "clear").build();
         getPlaceManager().revealPlace(req);
      });
   }

   /**
    * Initializes the about menu item click handler.
    */
   private void setupAboutAdminClientHandler()
   {
      Element el = Document.get().getElementById("menu-about");
      Event.sinkEvents(el, Event.ONCLICK | Event.ONTOUCHSTART);
      Event.setEventListener(el, event ->
      {
         adm.getVersionInfo(new AsyncCallback<String>()
         {
            
            @Override
            public void onSuccess(String result)
            {
               AboutDialogView view = new AboutDialogView(GWT.create(
                        AboutDialogView.Binder.class));
               view.setLogo("fwd.png");
               view.setTitle("About Administration Console");
               StringBuilder about = new StringBuilder();
               about.append("GWT Administration client to manage<br/> the FWD server.<br/>");
               about.append("The current version: ").append(result);
               view.setAboutHtml(about.toString());
               getView().getModalParent().add(view.getModal());
               view.addHideHandler(() -> {getView().getModalParent().remove(view.getModal());});
               view.getModal().show();
            }
            
            @Override
            public void onFailure(Throwable caught)
            {
               logger.log(Level.SEVERE, "Failed to call getVersionInfo", caught);
            }
         });
      });
   }

   /**
    * Initializes the preview selection dialog menu item click handler.
    * 
    * @param    pctx
    *           The printing context
    * @param    done
    *           The consumer of this action
    */
   private void displayPreviewSelectionDialog(PrintingContext pctx, Consumer<Range> done)
   {
      int selectedRecordsNumber = pctx.getDataSize(Range.SELECTION);
      int allRecordsNumber = pctx.getDataSize(Range.ALL);
      
      final InputDialog.Field fRange;
      InputDialog inputDialog = this.inputDialog.get();

      Map<String, String> translationMap = new HashMap<String, String>();
      
      String allRangeLabel = pctx.getPreviewDialogParameter(PreviewDialogParameters.RANGE_ALL_LABEL);
      translationMap.put(PrintingContext.Range.ALL.name(), allRangeLabel);
      
      String selRangeLabel = pctx.getPreviewDialogParameter(PreviewDialogParameters.RANGE_SELECTION_LABEL);
      translationMap.put(PrintingContext.Range.SELECTION.name(), selRangeLabel);
      
      inputDialog.setTranslationMap(translationMap);
      
      InputDialog.Field[] fields;
      String hlpText = pctx.getPreviewDialogParameter(PreviewDialogParameters.HELP_TEXT);
      if (selectedRecordsNumber > 0 && allRecordsNumber > 1)
      {
         PrintingContext.Range currentValue = PrintingContext.Range.SELECTION;
         fields = new InputDialog.Field[]
         {
            fRange = new InputDialog.Field("Range", currentValue)
         };
      }
      else
      {
         fRange = null;
         fields = new InputDialog.Field[]
         {
            //empty fields
         };
      }
      
      String title = pctx.getPreviewDialogParameter(PreviewDialogParameters.TITLE);

      inputDialog.setSaveButtonText("OK");
      inputDialog.setLabelSize(1);
      inputDialog.build(title, hlpText, fields);
      inputDialog.show(getView().getModalParent(), true, values ->
      {
         if (values != null)
         {
            PrintingContext.Range selectedRange;
            if (fRange != null)
            {
               selectedRange = (PrintingContext.Range) values.get(fRange);
            }
            else
            {
               selectedRange = PrintingContext.Range.ALL;
            }
            done.accept(selectedRange);
         }
      });
   }

   /**
    * Used by {@link #setupPrintHandler()} to build print query parameters from the print context.
    *
    * @param   printingContext
    *          Print context.
    *
    * @return  Print query parameters.
    */
   private Map<String, String> buildSelectionURLParameters(PrintingContext printingContext)
   {
      HashMap<String, String> parameters = new HashMap<String, String>();
      Object selection = printingContext.getReportParameters().get(
      com.goldencode.p2j.admin.shared.ReportParameters.SELECTION);
      if (selection instanceof TaggedName[])
      {
         TaggedName[] selectedRows = (TaggedName[]) selection;
         
         for(int i = 0; i < selectedRows.length; i++)
         {
            parameters.put(
            com.goldencode.p2j.admin.shared.ReportParameters.SELECTION.getParameter() + String.valueOf(i),
            selectedRows[i].getName());
         }
      }
      
      return parameters;
   }

   /**
    * Initializes the print menu item click handler.
    */
   private void setupPrintHandler()
   {
      Element el = Document.get().getElementById("menu-print");
      Event.sinkEvents(el, Event.ONCLICK | Event.ONTOUCHSTART);
      Event.setEventListener(el, event ->
      {
         Presenter p = HomePresenter.this.getChild(HomePresenter.SLOT_CONTENT);
         if (p != null && p.getView() instanceof PrintingContext)
         {
            PrintingContext printingContext = (PrintingContext) p.getView();
            
            displayPreviewSelectionDialog(printingContext, (range)-> {
               PlaceRequest.Builder builder = new PlaceRequest.Builder().nameToken(NameTokens.PRINT_PREVIEW)
                  .with(com.goldencode.p2j.admin.shared.ReportParameters.TITLE.getParameter(),
                        printingContext.getReportParameters().get(
                        com.goldencode.p2j.admin.shared.ReportParameters.TITLE).toString())
                  .with(com.goldencode.p2j.admin.shared.ReportParameters.REPORT_ID.getParameter(),
                        printingContext.getReportParameters().get(
                        com.goldencode.p2j.admin.shared.ReportParameters.REPORT_ID)
                        .toString())
                  .with(com.goldencode.p2j.admin.shared.ReportParameters.PAPER_FORMAT.getParameter(),
                        printingContext.getReportParameters().get(
                        com.goldencode.p2j.admin.shared.ReportParameters.PAPER_FORMAT).toString())
                  .with(com.goldencode.p2j.admin.shared.ReportParameters.PAPER_ORIENT.getParameter(),
                        printingContext.getReportParameters().get(
                        com.goldencode.p2j.admin.shared.ReportParameters.PAPER_ORIENT).toString())
                  .with(NameTokens.getTokenParameter(), printingContext.getPathToPrintPreviewOwner());
               if (range == Range.SELECTION)
               {
                  builder.with(buildSelectionURLParameters(printingContext));
               }
               
               PlaceRequest req = builder.build();
               
               getPlaceManager().revealPlace(req);
            });
         }
         else
         {
            modalDialogs.showInfo("Printing", "No print service found.", () -> {
               
            });
         }
      });
   }

   /**
    * Logs off currently logged in user.
    */
   private void logoff()
   {
      ClientContext.instance().reset("", "");
      Cookies.removeCookie("JSESSIONID");

      PlaceRequest req = new PlaceRequest.Builder().nameToken(NameTokens.LOGIN).build();
      getView().setInSlot(SLOT_CONTENT, null);
      getPlaceManager().revealPlace(req);
   }

   /**
    * Initializes the show ACL by account menu item click handler.
    */
   private void setupShowACLByAccountHandler()
   {
      Element el = Document.get().getElementById("menu-acl-showbyacc");
      Event.sinkEvents(el, Event.ONCLICK | Event.ONTOUCHSTART);
      Event.setEventListener(el, event ->
      {
         InputDialog.Field fInput;
         InputDialog dlg = inputDialog.get();
         dlg.build("Account Name", new InputDialog.Field[]
         {
            fInput = new InputDialog.Field(null, String.class)
         });

         dlg.setSaveButtonText("OK");

         dlg.show(getView().getModalParent(), vals ->
         {
            if (vals == null)
            {
               return;
            }

            String acctName = (String) vals.get(fInput);
            PlaceRequest req = NameTokens.buildACLPlaceRequest(acctName);
            getPlaceManager().revealPlace(req);
         });
      });
   }

   /**
    * Initializes the show ACL by resource name menu item click handler.
    * 
    * @param    adminDef
    *           The administrator plugins definitions
    */
   private void buildShowACLByResourceMenu(AdminDef adminDef)
   {
      Element el = Document.get().getElementById("menu-acl-showbyresource");
      StringBuilder sb = new StringBuilder();

      TreeSet<String> resTypes = new TreeSet<>();
      resTypes.addAll(adminDef.customAclEditors);

      // make sure system, admin, net, directory appear at the top in this order
      String[] predefined = new String[] {"system", "admin", "net", "directory"};
      List<String> customOrder = new ArrayList<>(resTypes.size());

      for (String resType : predefined)
      {
         if (resTypes.contains(resType))
         {
            customOrder.add(resType);
            resTypes.remove(resType);
         }
      }

      customOrder.addAll(resTypes);

      for (String key : customOrder)
      {
         PlaceRequest req = NameTokens.buildACLResourcePlaceRequest(key);
         sb.append("<li><a href=\"#");
         sb.append(tokenFormatter.toPlaceToken(req));
         sb.append("\">");
         sb.append(key);
         sb.append("</a></li>");
      }

      el.setInnerHTML(sb.toString());
   }

   /**
    * Enables or disables individual menu items according to the permissions of the logged in
    * user.
    * 
    * @param    adminDef
    *           The administrator plugins definitions
    */
   private void updateMenuAuthorization(AdminDef adminDef)
   {
      AdminProfile[] profile = adminDef.profile;

      // process profile and enable/disable the menu items
      if (profile != null)
      {
         Document doc = Document.get();
         Element cns = doc.getElementById("menu-console");
         Element acc = doc.getElementById("menu-accounts");
         Element acl = doc.getElementById("menu-acl");
         Element rts = doc.getElementById("menu-runset");
         Element gen = doc.getElementById("menu-config");
         Element tgt = doc.getElementById("menu-target");

         for (int i = 0; i < profile.length; i++)
         {
            setEnabled(cns, profile, "/console");
            setEnabled(acc, profile, "/accounts");
            setEnabled(acl, profile, "/access_control");
            setEnabled(rts, profile, "/runtime");
            setEnabled(gen, profile, "/configuration");
            setEnabled(tgt, profile, "/target");
         }
      }
   }

   /**
    * Enables a menu item.
    *
    * @param   el
    *          A DOM element representing the menu item to enable.
    * @param   profiles
    *          Admin profile.
    * @param   name
    *          Admin profile name.
    */
   private void setEnabled(Element el, AdminProfile[] profiles, String name)
   {
      boolean found = false;

      for (AdminProfile profile : profiles)
      {
         if (profile.name.equals(name))
         {
            found = true;
         }
      }

      if (found)
      {
         el.removeClassName("disabled");
      }
      else
      {
         el.addClassName("disabled");
      }
   }

   /**
    * Fetches admin definition data from the server and builds the main menu from this data.
    */
   private void buildMenu()
   {
      adm.getAdminDef(new AdminCallback<AdminDef>()
      {
         @Override
         public void onDone(AdminDef result)
         {
            if (result == null)
            {
               return;
            }

            buildShowACLByResourceMenu(result);
            updateMenuAuthorization(result);
         }
      });
   }
   
   /**
    * Initializes the live menu item click handler.
    */
   private void setupTargetLiveHandler()
   {
      Element el = Document.get().getElementById("menu-target-live");
      Event.sinkEvents(el, Event.ONCLICK | Event.ONTOUCHSTART);
      Event.setEventListener(el, event ->
      {
         modalDialogs.showYesNo("Confirmation",
                                "Sets the live server's directory as the target. Continue?",
                                MessageType.QUESTION,
                                res ->
                                {
                                   if (res != 0)
                                   {
                                      return;
                                   }

                                   adm.setTargetLive(true, new AdminCallback<Integer>(adm, alarm)
                                   {
                                      @Override
                                      public void onDone(Integer result)
                                      {
                                         ClientContext context = ClientContext.instance();
                                         
                                         if (result == -1)
                                         {
                                            alarm.ring();
                                         }
                                         else
                                         {
                                            context.setSecurityCacheSerialNumber(result);
                                            logger.log(Level.INFO, "live[" + result + "]");
                                         }
                                      }
                                   });
                                });
      });
   }

   /**
    * Initializes the refresh menu item click handler.
    */
   private void setupTargetRefreshHandler()
   {
      adm.canRefresh(new AdminCallback<Boolean>(adm, alarm)
                     {
                        @Override
                        public void onDone(Boolean result)
                        {
                           Element el = Document.get().getElementById("menu-target-refresh");
                           
                           if (result != null && result)
                           {
                              el.removeClassName("disabled");
                              attachRefreshHandler();
                           }
                           else
                           {
                              el.addClassName("disabled");
                           }
                        }
                     });
   }

   /**
    * Attach click handler to the refresh menu item.
    */
   private void attachRefreshHandler()
   {
      Element el = Document.get().getElementById("menu-target-refresh");
      Event.sinkEvents(el, Event.ONCLICK | Event.ONTOUCHSTART);
      Event.setEventListener(el, event ->
      {
         modalDialogs.showYesNo("Confirmation",
                                "You are about to refresh the security cache. Continue?",
                                MessageType.QUESTION,
                                res ->
                                {
                                   if (res != 0)
                                   {
                                      return;
                                   }

                                   adm.targetRefresh(new AdminCallback<Integer>(adm, alarm)
                                   {
                                      @Override
                                      public void onDone(Integer result)
                                      {
                                         ClientContext context = ClientContext.instance();
                                         int serialNumber;
                                         try
                                         {
                                            serialNumber = context.getSecurityCacheSerialNumber();
                                         }
                                         catch(NumberFormatException ex)
                                         {
                                            logger.log(Level.SEVERE,
                                                       "Corrupted 'securityCacheSerialNumber' cookie", ex);
                                            serialNumber = -1;
                                         }
                                         
                                         if (result >= serialNumber)
                                         {
                                            context.setSecurityCacheSerialNumber(result);
                                            logger.log(Level.INFO, "live[" + result + "]");
                                         }
                                         else
                                         {
                                            alarm.ring();
                                         }
                                      }
                                   });
                                });
      });
   }
   
   /**
    * Finds the descendants of the given element filtered by the given selector. Uses the jQuery
    * find function.
    * 
    * @param    e
    *           The given ancestor html element
    * @param    selector
    *           The given selector
    * 
    * @return   The descendants of the given element filtered by the given selector
    */
   private native Element[] findDescendants(final Element e, final String selector) /*-{
      
      var jqResult = $wnd.jQuery(e).find(selector);
      
      var result = [];
      
      for(var i = 0; i < jqResult.length; i++)
      {
         result.push(jqResult[i]);
      }
      
      return result;
   }-*/;

}