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;
   }
}