# 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;
       }-*/;

    }