# LibTableView.java

    /*
    ** Module   : LibTableView.java
    ** Abstract : Libraries table widget.
    **
    ** Copyright (c) 2017, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --------------------------------Description-----------------------------------
    ** 001 HC  20170612 Initial version.
    */
    /*
    ** 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.runtime.customlib;

    import com.goldencode.p2j.admin.*;
    import com.goldencode.p2j.admin.client.*;
    import com.goldencode.p2j.admin.client.application.*;
    import com.goldencode.p2j.admin.client.widget.*;
    import com.goldencode.p2j.admin.client.widget.dialog.*;
    import com.goldencode.p2j.admin.shared.*;
    import com.google.gwt.event.dom.client.*;
    import com.google.gwt.uibinder.client.*;
    import com.google.gwt.user.client.rpc.*;
    import com.google.gwt.user.client.ui.*;
    import com.google.inject.*;
    import com.gwtplatform.mvp.client.*;
    import org.gwtbootstrap3.client.ui.Button;
    import org.gwtbootstrap3.client.ui.*;
    import org.gwtbootstrap3.client.ui.constants.*;
    import org.gwtbootstrap3.client.ui.gwt.*;

    import java.util.*;

    /**
     * Libraries table widget.
     */
    public class LibTableView
    extends ViewImpl
    implements LibTablePresenter.MyView
    {
       /**
        * GWT binder.
        */
       interface Binder extends UiBinder<Row, LibTableView>
       {}

       /** jars grid */
       @UiField(provided = true)
       DataGrid<TaggedName> jarGrid = new DataGrid<>(Integer.MAX_VALUE);

       /** apis grid */
       @UiField(provided = true)
       DataGrid<TaggedName> apisGrid = new DataGrid<>(Integer.MAX_VALUE);

       /** widget */
       @UiField
       Button jarRefresh;

       /** widget */
       @UiField
       Button jarReg;

       /** widget */
       @UiField
       Button jarDereg;

       /** widget */
       @UiField
       Button jarReload;

       /** widget */
       @UiField
       Button jarEditHook;

       /** widget */
       @UiField
       Button regAPI;

       /** widget */
       @UiField
       Button deregAPI;

       /** widget */
       @UiField
       SimplePanel listSelect;

       /** widget */
       @UiField
       SimplePanel terminateSessions;

       /** jars grid handle */
       private GridHandle<TaggedName> jarGridHandle;

       /** jars grid handle */
       private GridHandle<TaggedName> apisGridHandle;

       /** list select dialog provider */
       private Provider<ListSelect> listSelectProvider;

       /** terminate session dialog provider */
       private Provider<TerminateSessions> termSessionsProvider;

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

       /** Modal dialogs */
       private final ModalDialogs dialogs;

       /** Alarm provider */
       private Provider<Alarm> alarmProvider;

       /**
        * Ctor.
        *
        * @param   binder
        *          GWT binder.
        * @param   adm
        *          Admin service reference.
        * @param   dialogs
        *          Modal dialogs.
        * @param   alarmProvider
        *          Alarm provider.
        * @param   listSelectProvider
        *          List select dialog provider.
        * @param   termSessionsProvider
        *          Terminate session dialog provider.
        */
       @Inject
       LibTableView(Binder binder,
                    com.goldencode.p2j.admin.shared.AdminServiceAsync adm,
                    ModalDialogs dialogs,
                    Provider<Alarm> alarmProvider,
                    Provider<ListSelect> listSelectProvider,
                    Provider<TerminateSessions> termSessionsProvider)
       {
          this.adm = adm;
          this.dialogs = dialogs;
          this.alarmProvider = alarmProvider;
          this.listSelectProvider = listSelectProvider;
          this.termSessionsProvider = termSessionsProvider;
          initWidget(binder.createAndBindUi(this));
          initView();
       }

       /**
        * Called when the view is about to be revealed.
        */
       @Override
       public void onReveal()
       {
          refreshJarList(false);
       }

       /**
        * Initializes the view.
        */
       private void initView()
       {
          //noinspection NonJREEmulationClassesInClientCode
          jarGridHandle = GridHelper.initListGrid(jarGrid,
             new GridHelper.ColumnInfo<TaggedName>("Jar File Name", tn -> tn.get(0), (tn1, tn2) -> tn1.get(0).compareTo(tn2.get(0))),
             new GridHelper.ColumnInfo<TaggedName>("Active Hook", tn -> tn.get(1), (tn1, tn2) -> tn1.get(1).compareTo(tn2.get(1)))
          );

          jarGridHandle.getSelectionModel().addSelectionChangeHandler(event ->
          {
             refreshApiList();
             refreshJarButtonsState();
             refreshApiButtonsState();
          });

          //noinspection NonJREEmulationClassesInClientCode
          apisGridHandle = GridHelper.initListGrid(apisGrid,
             new GridHelper.ColumnInfo<TaggedName>("Interface", tn -> tn.get(0), (tn1, tn2) -> tn1.get(0).compareTo(tn2.get(0))),
             new GridHelper.ColumnInfo<TaggedName>("Implementation Class", tn -> tn.get(1), (tn1, tn2) -> tn1.get(1).compareTo(tn2.get(1)))
          );

          apisGridHandle.getSelectionModel().addSelectionChangeHandler(event ->
          {
             refreshApiButtonsState();
          });
       }

       /**
        * Click handler.
        *
        * @param   e
        *          Click event.
        */
       @UiHandler("jarRefresh")
       protected void jarRefreshClicked(ClickEvent e)
       {
          refreshJarList(true);
       }

       /**
        * Click handler.
        *
        * @param   e
        *          Click event.
        */
       @UiHandler("jarReg")
       protected void jarRegClicked(ClickEvent e)
       {
          handleRegJarStep1();
       }

       /**
        * Click handler.
        *
        * @param   e
        *          Click event.
        */
       @UiHandler("regAPI")
       protected void apiRegClicked(ClickEvent e)
       {
          handleRegAPIStep1();
       }

       /**
        * Click handler.
        *
        * @param   e
        *          Click event.
        */
       @UiHandler("jarDereg")
       public void jarDeregClick(ClickEvent e)
       {
          handleDeregisterJar();
       }

       /**
        * Click handler.
        *
        * @param   e
        *          Click event.
        */
       @UiHandler("deregAPI")
       public void deregAPIClick(ClickEvent e)
       {
          handleDeregisterAPI();
       }

       /**
        * Click handler.
        *
        * @param   e
        *          Click event.
        */
       @UiHandler("jarReload")
       public void jarReloadClick(ClickEvent e)
       {
          handleReloadJarStep1();
       }

       /**
        * Click handler.
        *
        * @param   e
        *          Click event.
        */
       @UiHandler("jarEditHook")
       public void jarEditHookClick(ClickEvent e)
       {
          handleEditHooks();
       }

       /**
        * Step one of the register jar wizard.
        */
       private void handleRegJarStep1()
       {
          adm.getUnregisteredJars(new AsyncCallback<String[]>()
          {
             @Override
             public void onFailure(Throwable caught)
             {
                dialogs.showError(
                "Cannot get the list of unregistered jars.");
                return;
             }

             @Override
             public void onSuccess(String[] jars)
             {
                String targetJar = null;

                if (jars != null)
                {
                   if (jars.length == 0)
                   {
                      adm.getManagedLibsDirInformation(new AsyncCallback<TaggedName>()
                      {
                         @Override
                         public void onFailure(Throwable caught)
                         {
                            dialogs.showError("Cannot get managed libraries directory information.");
                         }

                         @Override
                         public void onSuccess(TaggedName dirInfo)
                         {
                            if (dirInfo == null)
                            {
                               dialogs.showError(
                               "Cannot get managed libraries directory information.");
                               return;
                            }

                            StringBuilder sb = new StringBuilder();
                            if (dirInfo.getName() == null)
                            {
                               sb.append("<span style=\"color:red\">Customer libraries directory is not specified!</span><br>");
                               sb.append("You can set the directory by specifying the <i>-Dmanaged.libs.dir=some/path/</i> java option or<br>");
                               sb.append("by setting the <i>/server/default/managed-libs-dir</i> parameter in the configuration directory.<br>");
                            }
                            else
                            {
                               sb.append("There are no unregistered customer libraries in the managed customer libraries directory!<br>");
                               sb.append("The current path to the directory is <i>");
                               sb.append(dirInfo.getName());
                               sb.append("true".equals(dirInfo.getTag()) ? "</i>.<br>" :
                               "</i>, but it <span style=\"color:red\"> does not exist or is inaccessible</span>!<br>");
                               sb.append("You can change the directory by specifying the <i>-Dmanaged.libs.dir=some/path/</i> java option or<br>");
                               sb.append("by setting the <i>/server/default/managed-libs-dir</i> parameter in the configuration directory.<br>");
                            }
                            sb.append("Also, managed customer libraries should not be present in the server class path.");

                            dialogs.showInfo("No Unregistered Libraries", sb.toString(), null);
                            return;
                         }
                      });

                   }
                   else
                   {
                      TaggedName[] data = toTaggedNames(jars);
                      ListSelect ls = listSelectProvider.get();
                      ls.show(listSelect, data,
                              "Register New Customer Library (Step 1 / 2)",
                              "Customer libraries that can be registered",
                              new String[]{"Jar file name"},
                              "Select the appropriate customer library. If you are not seeing the " +
                              "expected library then it may be already registered or presents in " +
                              "the server class path.",
                              taggedName ->
                              {
                                 if (taggedName == null || taggedName.getName() == null)
                                 {
                                    // dialog canceled
                                    return;
                                 }

                                 handleRegJarStep2(taggedName.getName());
                              }
                      );
                   }
                }
             }
          });

       }

       /**
        * Step two of the register jar wizard.
        *
        * @param   targetJar
        *          Target jar.
        */
       private void handleRegJarStep2(String targetJar)
       {
          adm.getAvailableJarHookClasses(targetJar, new AsyncCallback<String[]>()
          {
             @Override
             public void onFailure(Throwable caught)
             {
                dialogs.showError("Cannot get the list of jar hook classes.");
             }

             @Override
             public void onSuccess(String[] hooks)
             {
                if (hooks != null)
                {
                   if (hooks.length == 0)
                   {
                      dialogs.showInfo("No Hook Classes Found for the Selected Jar",
                                       "There are no classes in the selected library that can serve" +
                                       " as a hook class which performs jar initialization. No " +
                                       "hook class will be defined for this library.",
                                       null);
                   }
                   else
                   {
                      TaggedName[] data = toTaggedNames(hooks);
                      ListSelect ls = listSelectProvider.get();
                      ls.show(listSelect, data,
                              "Register New Customer Library (Step 2 / 2)",
                              "Hook classes availible in the selected jar",
                              new String[]{"Hook class name"},
                              "Select an appropriate hook class. This class will be used for jar " +
                              "initialization. Note that you can skip this step.",
                              taggedName ->
                              {
                                 if (taggedName == null || taggedName.getName() == null)
                                 {
                                    // dialog canceled
                                    return;
                                 }

                                 registerJar(targetJar, taggedName.getName());
                              }
                      );
                   }
                }
             }
          });
       }

       /**
        * Handles API deregistration.
        */
       private void handleDeregisterAPI()
       {
          String targetJar = getSelectedJar();
          TaggedName targetApi = getSelectedApi();

          if (targetJar == null || targetApi == null)
             return;

          String errDereg = "An error occured while deregistering the API.";

          dialogs.showYesNo("Deregister API", "Are you sure you want to deregister the selected " +
          "API?", MessageType.QUESTION, res ->
          {
             if (res == 0)
             {
                adm.deregisterJarAPI(targetJar, targetApi.getName(), false, new
                AsyncCallback<JarOpErrorCode>()
                {
                   @Override
                   public void onFailure(Throwable caught)
                   {
                      dialogs.showError(errDereg);
                   }

                   @Override
                   public void onSuccess(JarOpErrorCode result)
                   {
                      if (result == JarOpErrorCode.ACTIVE_SESSIONS_PRESENT)
                      {
                         // there are active session related to the selected API, display
                         // the "terminate sessions" dialog.
                         TerminateSessions ts = termSessionsProvider.get();
                         ts.show(terminateSessions,
                                 TerminateSessions.JarActionType.API_DEREGISTER,
                                 targetJar, targetApi.getName(), res -> refreshApiList());
                      }
                      else if (result != JarOpErrorCode.SUCCESS)
                      {
                         dialogs.showError(errDereg + " " + result);
                      }
                      else
                      {
                         refreshApiList();
                      }
                   }
                });
             }
          });
       }

       /**
        * Handle "reload customer library" button.
        */
       private void handleReloadJarStep1()
       {
          String targetJar = getSelectedJar();

          if (targetJar == null)
          {
             return;
          }

          String err = "An error occured while deregistering customer library.";

          dialogs.showYesNo("Reload Customer Library",
                            "Are you sure you want to reload the selected customer library?<br> " +
                            "Note that you should replace the customer library on the disk only " +
                            "when you will be asked for it on the next step.",
                            MessageType.QUESTION, res ->
                            {
                               if (res == 0)
                               {
                                  adm.getStoredJarHookClass(targetJar, new AdminCallback<String>(adm, alarmProvider.get())

                                  {
                                     @Override
                                     public void onDone(String storedHookClass)
                                     {
                                        if (!isSuccess())
                                        {
                                           return;
                                        }

                                        adm.deregisterJar(targetJar, false, false, new
                                        AdminCallback<JarOpErrorCode>(adm, alarmProvider.get())
                                        {
                                           @Override
                                           public void onDone(JarOpErrorCode result)
                                           {
                                              if (!isSuccess())
                                              {
                                                 refreshJarList(true);
                                                 return;
                                              }

                                              if (result == JarOpErrorCode.ACTIVE_SESSIONS_PRESENT)
                                              {
                                                 TerminateSessions ts = termSessionsProvider.get();
                                                 ts.show(terminateSessions,
                                                         TerminateSessions.JarActionType.JAR_RELOAD,
                                                         targetJar,
                                                         null, res ->
                                                         {
                                                            if (res)
                                                            {
                                                               handleReloadJarStep2(targetJar,
                                                                                    storedHookClass);
                                                            }
                                                            else
                                                            {
                                                               refreshJarList(true);
                                                            }
                                                         });
                                              }
                                              else if (result != JarOpErrorCode.SUCCESS)
                                              {
                                                 dialogs.showError(err);
                                                 refreshJarList(true);
                                              }
                                              else
                                              {
                                                 handleReloadJarStep2(targetJar, storedHookClass);
                                              }
                                           }
                                        });
                                     }
                                  });
                               }
                            }
          );
       }

       /**
        * Second step of the reload jar wizard.
        *
        * @param   targetJar
        *          Target jar.
        * @param   hookClass
        *          Hook class.
        */
       private void handleReloadJarStep2(String targetJar, String hookClass)
       {
          String regErr = "An error occured while registering customer library.";
          String deregErr = "An error occured while deregistering customer library after re-registration error.";

          dialogs.showMessage(
          "Replace Customer Library",
          "Now you can replace the customer library on the disk (unless you " +
          "just want to reload the hook).",
          MessageType.DEFAULT,
          new String[]{"Continue"},
          new ButtonType[]{ButtonType.PRIMARY},
          res ->
          {
             adm.registerJar(targetJar, hookClass, false, new AdminCallback<JarOpErrorCode>(adm, alarmProvider.get())
             {
                @Override
                public void onDone(JarOpErrorCode result)
                {
                   if (!isSuccess())
                   {
                      refreshJarList(true);
                      return;
                   }

                   if (result == JarOpErrorCode.SUCCESS)
                   {
                      dialogs.showInfo(
                      "Operation Completed",
                      "The customer library was successfully reloaded.",
                      null);
                      refreshJarList(true);
                   }
                   else
                   {
                      dialogs.showError("An error occured while registering customer library.", () ->
                      {
                         adm.deregisterJarFromDirectory(targetJar, hookClass, new
                         AdminCallback<JarOpErrorCode>(adm, alarmProvider.get())
                         {
                            @Override
                            public void onDone(JarOpErrorCode result)
                            {
                               if (!isSuccess())
                               {
                                  refreshJarList(true);
                                  return;
                               }

                               if (result == JarOpErrorCode.SUCCESS)
                               {
                                  StringBuilder sb = new StringBuilder();
                                  sb.append("Because of re-registration error, registration has ");
                                  sb.append("been removed for customer library ");
                                  sb.append(targetJar);
                                  if (hookClass == null)
                                     sb.append(" (no hook class).");
                                  else
                                  {
                                     sb.append(" (hook class ");
                                     sb.append(hookClass);
                                     sb.append(").");
                                  }

                                  dialogs.showWarning("Operation Completed", sb.toString(), null);
                               }
                               else
                               {
                                  dialogs.showError(deregErr + " " + result);
                               }

                               refreshJarList(true);
                            }
                         });
                      });
                   }
                }
             });
          });
       }

       /**
        * Handle "edit hook class" button.
        */
       private void handleEditHooks()
       {
          final String targetJar = getSelectedJar();
          if (targetJar == null)
          {
             return;
          }

          adm.getStoredJarHookClass(targetJar, new AsyncCallback<String>()
          {
             @Override
             public void onFailure(Throwable caught)
             {

             }

             @Override
             public void onSuccess(String hookClass)
             {
                dialogs.showMessage(
                "Edit Customer Library Hook Class",
                "You are editing the hook class specified in the directory, so it may" +
                " not match the active hook class displayed in the customer libraries" +
                " table. Hook is reinitialized when the customer library is reloaded.",
                MessageType.DEFAULT,
                new String[]{"Edit", "Remove", "Close"},
                new ButtonType[]{ButtonType.PRIMARY, ButtonType.PRIMARY,
                ButtonType.DEFAULT},
                res ->
                {
                   if (res == 0)
                   {
                      handleEditHookClass(hookClass, targetJar);
                   }
                   else if (res == 1)
                   {
                      handleRemoveHookClass(targetJar);
                   }
                });
             }
          });
       }

       /**
        * Handle "edit hook class" buttons.
        *
        * @param  currentHook
        *         Current hook class (server-wide or global) for the target jar
        *         file.
        * @param  targetJar
        *         Target jar file.
        */
       private void handleEditHookClass(String currentHook, String targetJar)
       {
          String err = "Cannot get available hook classes.";

          // get hook classes available for selection
          adm.getAvailableJarHookClasses(targetJar, new AsyncCallback<String[]>()
          {
             @Override
             public void onFailure(Throwable caught)
             {
                dialogs.showError(err);
             }

             @Override
             public void onSuccess(String[] availHooks)
             {
                if (availHooks == null)
                {
                   dialogs.showError(err);
                }
                else if (availHooks.length == 0)
                {
                   dialogs.showInfo("No Hook Classes Found for the Selected Jar", "There are no " +
                   "classes in the selected jar that can serve as a hook class which performs jar " +
                   "initialization.", null);
                }
                else
                {
                   TaggedName[] data = toTaggedNames(availHooks);
                   ListSelect ls = listSelectProvider.get();
                   ls.show(listSelect, data,
                           "Edit hook class",
                           "Hook classes availible in the selected jar",
                           new String[]{"Hook class name"},
                           "Select an appropriate hook class. This class will be used for jar " +
                           "initialization.",
                           currentHook == null ? null : new TaggedName(currentHook, null),
                           taggedName ->
                           {
                              if (taggedName == null || taggedName.getName() == null)
                              {
                                 // dialog canceled
                                 return;
                              }

                              changeJarHookClass(targetJar, taggedName.getName());
                           }
                   );
                }
             }
          });
       }

       /**
        * Implements remove hook class.
        *
        * @param   targetJar
        *          Target jar.
        */
       private void handleRemoveHookClass(String targetJar)
       {
          String err = "Cannot change hook class.";

          dialogs.showYesNo(
          "Remove Hook",
          "Are you sure you want to remove the hook?",
          MessageType.QUESTION, res ->
          {
             if (res == 0)
             {
                adm.changeJarHookClass(targetJar, null, new AsyncCallback<JarOpErrorCode>()
                {
                   @Override
                   public void onFailure(Throwable caught)
                   {
                      dialogs.showError(err);
                   }

                   @Override
                   public void onSuccess(JarOpErrorCode result)
                   {
                      if (result != JarOpErrorCode.SUCCESS)
                      {
                         dialogs.showError(err + " " + result);
                      }
                      else
                      {
                         refreshJarList(true);
                      }
                   }
                });
             }
          });
       }

       /**
        * Implements register jar.
        *
        * @param   targetJar
        *          Target jar.
        * @param   targetHook
        *          Target hook.
        */
       private void registerJar(String targetJar, String targetHook)
       {
          String err = "An error occured while registering the customer library.";

          adm.registerJar(targetJar, targetHook, true, new AsyncCallback<JarOpErrorCode>()
          {
             @Override
             public void onFailure(Throwable caught)
             {
                dialogs.showError(err);
             }

             @Override
             public void onSuccess(JarOpErrorCode result)
             {
                refreshJarList(false);
                if (result == JarOpErrorCode.SUCCESS)
                {
                   dialogs.showInfo("The customer library was successfully registered.",
                                    "Operation Completed",
                                    null);
                }
                else
                {
                   dialogs.showError(err + " " + result);
                }
             }
          });
       }

       /**
        * Implements change jar hook class.
        *
        * @param   targetJar
        *          Target jar.
        * @param   targetHook
        *          Target hook.
        */
       private void changeJarHookClass(String targetJar, String targetHook)
       {
          String err = "Cannot change hook class.";

          adm.changeJarHookClass(targetJar, targetHook, new AsyncCallback<JarOpErrorCode>()
          {
             @Override
             public void onFailure(Throwable caught)
             {
                dialogs.showError(err);
             }

             @Override
             public void onSuccess(JarOpErrorCode result)
             {
                if (result != JarOpErrorCode.SUCCESS)
                {
                   dialogs.showError(err + " " + result);
                }
                else
                {
                   refreshJarList(true);
                }
             }
          });
       }

       /**
        * First step of the register api wizard.
        */
       private void handleRegAPIStep1()
       {
          String targetJar = getSelectedJar();
          if (targetJar == null)
          {
             return;
          }

          String errAvailFaces = "Cannot get the list of interfaces.";

          adm.getAvailableJarAPIInterfaces(targetJar, false, new AsyncCallback<String[]>()
          {
             @Override
             public void onFailure(Throwable caught)
             {
                dialogs.showError(errAvailFaces);
             }

             @Override
             public void onSuccess(String[] interfaces)
             {
                if (interfaces != null)
                {
                   if (interfaces.length == 0)
                   {
                      dialogs.showInfo("No Interfaces Were Found",
                                       "In the selected jar there are no unregistered interfaces " +
                                       "which can define an API.", null);
                   }
                   else
                   {
                      TaggedName[] data = toTaggedNames(interfaces);
                      ListSelect ls = listSelectProvider.get();
                      ls.show(listSelect, data, "Register New API (Step 1 / 2)",
                              "Interfaces",
                              new String[]{"Interface name"},
                              "Select the interface which defines the API you want to register " +
                              "(this list contains only unregistered interfaces).",
                              taggedName ->
                              {
                                 String targetInterface = taggedName.getName();
                                 if (targetInterface != null)
                                 {
                                    handleRegAPIStep2(targetJar, targetInterface);
                                 }
                              }
                      );
                   }
                }
                else
                {
                   dialogs.showError(errAvailFaces);
                }
             }
          });
       }

       /**
        * Second step of the register api wizard.
        *
        * @param   targetJar
        *          Target jar.
        * @param   targetInterface
        *          Target interface.
        */
       private void handleRegAPIStep2(String targetJar, String targetInterface)
       {
          String errAvailClasses = "Cannot get the list of implementation classes.";

          adm.getAvailableJarAPIImplClasses(targetJar, targetInterface, new AsyncCallback<String[]>()
          {
             @Override
             public void onFailure(Throwable caught)
             {
                dialogs.showError(errAvailClasses);
             }

             @Override
             public void onSuccess(String[] implementations)
             {
                if (implementations != null)
                {
                   if (implementations.length == 0)
                   {
                      dialogs.showInfo("No Implementation Classes Were Found", "In the selected " +
                      "customer library there are no classes which statically implement the " +
                      "selected interface. API registration was cancelled.", null);
                   }
                   else
                   {
                      TaggedName[] data = toTaggedNames(implementations);

                      ListSelect ls = listSelectProvider.get();
                      ls.show(listSelect, data, "Register New API (Step 2 / 2)",
                              "Implementation classes",
                              new String[]{"Class name"},
                              "Select the class which implements the API you want to register.",
                              taggedName ->
                              {
                                 String targetImplementation = taggedName.getName();
                                 if (targetImplementation != null)
                                 {
                                    registerJarAPI(targetInterface, targetImplementation);
                                 }
                              }
                      );
                   }
                }
                else
                {
                   dialogs.showError(errAvailClasses);
                }
             }
          });
       }

       /**
        * Handle "deregister jar" button.
        */
       private void handleDeregisterJar()
       {
          String targetJar = getSelectedJar();

          if (targetJar == null)
          {
             return;
          }

          String errDereg = "An error occured while deregistering the jar.";

          dialogs.showYesNo("Deregister Customer Library", "Are you sure you want to deregister the" +
          " selected customer library?", MessageType.QUESTION, res ->
          {
             // YES
             if (res == 0)
             {
                adm.deregisterJar(targetJar, false, true, new AsyncCallback<JarOpErrorCode>()
                {
                   @Override
                   public void onFailure(Throwable caught)
                   {
                      dialogs.showError(errDereg);
                   }

                   @Override
                   public void onSuccess(JarOpErrorCode result)
                   {
                      if (result == JarOpErrorCode.ACTIVE_SESSIONS_PRESENT)
                      {
                         // there are active session related to the selected jar, display
                         // the "terminate sessions" dialog.
                         TerminateSessions ts = termSessionsProvider.get();
                         ts.show(terminateSessions,
                                 TerminateSessions.JarActionType.JAR_DEREGISTER,
                                 targetJar, null, res -> refreshJarList(false));
                      }
                      else if (result != JarOpErrorCode.SUCCESS)
                      {
                         dialogs.showError(errDereg + " " + result);
                      }
                      else
                      {
                         refreshJarList(false);
                      }
                   }
                });
             }
          });
       }

       /**
        * Implements register jar API.
        *
        * @param   targetInterface
        *          Target interface.
        * @param   targetImplementation
        *          Target implementation.
        */
       private void registerJarAPI(String targetInterface, String targetImplementation)
       {
          String err = "An error occured while registering the API.";

          adm.registerJarAPI(targetInterface, targetImplementation, new AsyncCallback<JarOpErrorCode>()
          {
             @Override
             public void onFailure(Throwable caught)
             {
                dialogs.showError(err);
             }

             @Override
             public void onSuccess(JarOpErrorCode result)
             {
                if (result == JarOpErrorCode.SUCCESS)
                {
                   refreshApiList();
                   dialogs.showInfo("Operation Completed", "The API was successfully added.", null);
                }
                else
                {
                   dialogs.showError(err + " " + result);
                }
             }
          });
       }

       /**
        * Refresh the list of jars.
        *
        * @param preserveSelection
        *        <code>true</code> if current selection should be preserved.
        */
       private void refreshJarList(boolean preserveSelection)
       {
          TaggedName selected = preserveSelection ? jarGridHandle.getSelectedSingle() : null;

          adm.getRegisteredJars(new AsyncCallback<TaggedName[]>()
          {
             @Override
             public void onFailure(Throwable caught)
             {
                dialogs.showError("Error loading registered libraries.");
             }

             @Override
             public void onSuccess(TaggedName[] result)
             {
                jarGridHandle.clearGrid();
                List<TaggedName> list = jarGridHandle.getDataProvider().getList();
                if (list != null)
                {
                   list.addAll(Arrays.asList(result));
                }
                refreshJarButtonsState();
                refreshApiButtonsState();
                if (selected != null && jarGridHandle.getDataProvider().getList().contains(selected))
                {
                   jarGridHandle.getSelectionModel().setSelected(selected, true);
                }
             }
          });
       }

       /**
        * Refresh API list.
        */
       private void refreshApiList()
       {
          String selectedJar = getSelectedJar();

          if (selectedJar == null)
          {
             apisGridHandle.clearGrid();
             return;
          }

          adm.getRegisteredJarAPIs(selectedJar, new AsyncCallback<TaggedName[]>()
          {
             @Override
             public void onFailure(Throwable caught)
             {
                dialogs.showError("Error loading registered APIs.");
             }

             @Override
             public void onSuccess(TaggedName[] result)
             {
                apisGridHandle.clearGrid();
                if (result != null)
                {
                   apisGridHandle.getDataProvider().getList().addAll(Arrays.asList(result));
                }
             }
          });
       }

       /**
        * Update the state of jar handling buttons.
        */
       private void refreshJarButtonsState()
       {
          boolean selected = getSelectedJar() != null;

          jarDereg.setEnabled(selected);
          jarReload.setEnabled(selected);
          jarEditHook.setEnabled(selected);
       }

       /**
        * Update the state of API handling buttons.
        */
       private void refreshApiButtonsState()
       {
          if (getSelectedJar() == null)
          {
             regAPI.setEnabled(false);
             deregAPI.setEnabled(false);
          }
          else
          {
             regAPI.setEnabled(true);
             deregAPI.setEnabled(getSelectedApi() != null);
          }
       }

       /**
        * Get selected jar.
        *
        * @return the name of the selected jar or <code>null</code> if there is
        *         no selected jar.
        */
       private String getSelectedJar()
       {
          Set<TaggedName> jar = jarGridHandle.getSelected();
          return jar.isEmpty() ? null : jar.iterator().next().get(0);
       }

       /**
        * Get selected API.
        *
        * @return the TaggedName which represents the selected API or
        *         <code>null</code> if there is no selected API.
        */
       private TaggedName getSelectedApi()
       {
          Set<TaggedName> api = apisGridHandle.getSelected();
          return api.isEmpty() ? null : api.iterator().next();
       }

       /**
        * Convert an array of strings to the array of TaggedNames objects which
        * names match the specified strings.
        *
        * @param  strings
        *         Source array of strings.
        * @return array of TaggedNames objects which names match the specified
        *         strings.
        */
       private TaggedName[] toTaggedNames(String[] strings)
       {
          TaggedName[] dest = new TaggedName[strings.length];
          for (int i = 0; i < strings.length; i++)
          {
             dest[i] = new TaggedName(strings[i]);
          }
          return dest;
       }
    }