CertificatesPresenter.java

/*
** Module   : CertificatesPresenter.java
** Abstract : CertificatesPresenter is a view controller that provides the business methods.
**
** Copyright (c) 2017, Golden Code Development Corporation.
**
** -#- -I- --Date-- ----------------Description----------------------
** 001 SBI 20170601 Created 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.accounts.certificates;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.logging.Level;

import javax.inject.Named;

import com.goldencode.p2j.admin.AdminHelper;
import com.goldencode.p2j.admin.CertDef;
import com.goldencode.p2j.admin.TaggedName;
import com.goldencode.p2j.admin.client.AdminCallback;
import com.goldencode.p2j.admin.client.MultiCallback;
import com.goldencode.p2j.admin.client.NameTokens;
import com.goldencode.p2j.admin.client.application.Alarm;
import com.goldencode.p2j.admin.client.application.home.AsyncAction;
import com.goldencode.p2j.admin.client.application.home.HomePresenter;
import com.goldencode.p2j.admin.client.application.home.PresenterWithStateMachine;
import com.goldencode.p2j.admin.client.application.home.ViewStateMachine;
import com.goldencode.p2j.admin.client.application.home.accounts.CertificateValidity;
import com.goldencode.p2j.admin.client.application.home.accounts.CertificatesOptions;
import com.goldencode.p2j.admin.client.widget.dialog.MessageType;
import com.goldencode.p2j.admin.client.widget.dialog.ModalDialogs;
import com.goldencode.p2j.admin.shared.AdminServiceAsync;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.HasEnabled;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.web.bindery.event.shared.EventBus;
import com.gwtplatform.mvp.client.HasUiHandlers;
import com.gwtplatform.mvp.client.View;
import com.gwtplatform.mvp.client.annotations.NameToken;
import com.gwtplatform.mvp.client.annotations.ProxyStandard;
import com.gwtplatform.mvp.client.presenter.slots.NestedSlot;
import com.gwtplatform.mvp.client.proxy.PlaceManager;
import com.gwtplatform.mvp.client.proxy.ProxyPlace;
import com.gwtplatform.mvp.shared.proxy.PlaceRequest;

/**
 * CertificatesPresenter represents a certificate view controller that provides all business for
 * certificates views and dialogs.
 */
public class CertificatesPresenter
extends PresenterWithStateMachine<CertificatesPresenter.MyView, CertificatesPresenter.MyProxy>
implements CertificatesUIHandlers
{
   /**
    * Defines GET Request parameters identifying the current certificate view 
    */
   private static interface RequestParameters
   {
      /** Certificate alias parameter*/
      String ALIAS  = "alias";
      
      /** Certificate role parameter */
      String OPTION = "CertificatesOptions";
      
      /** Certificate unused only flag parameter */
      String UNUSED = "unusedOnly";
   };
   
   /** Transitional state that defines a set of widgets to which the current state is applied */
   private static final boolean[] MASK_WEST =
   {
            /*refreshCerts  */ true,
            /*addCert       */ true,
            /*deleteCerts   */ true,
            /*changeAlias   */ true,
            /*viewCert      */ true,
            /*unassignedOnly*/ true
   };
   
   /** state definition - initial empty */
   private static final boolean[] STATE_INITIAL_EMPTY =
   {
            /*refreshCerts  */ true,
            /*addCert       */ true,
            /*deleteCerts   */ false,
            /*changeAlias   */ false,
            /*viewCert      */ false,
            /*unassignedOnly*/ true
   };

   /** state definition - single selection in certificate list */
   private  static final boolean[] STATE_CERTS_SINGLE =
   {
      /*refreshCerts  */ true,
      /*addCert       */ true,
      /*deleteCerts   */ true,
      /*changeAlias   */ true,
      /*viewCert      */ true,
      /*unassignedOnly*/ true
   };

   /** state definition - multiple selection in certificate list */
   private static final boolean[] STATE_CERTS_MULTI =
   {
      /*refreshCerts  */ true,
      /*addCert       */ true,
      /*deleteCerts   */ true,
      /*changeAlias   */ false,
      /*viewCert      */ false,
      /*unassignedOnly*/ true
   };
   
   /**
    * Describes the certificate view visual states.
    */
   public static enum CertificatesViewStates
   implements ViewStateMachine.TransitionState
   {
      /** Certificate list has been refreshed */
      CERTS_LIST_REFRESHED(MASK_WEST, STATE_INITIAL_EMPTY),
      
      /** Single certificate has been selected */
      SINGLE_CERT_SELECTED(MASK_WEST, STATE_CERTS_SINGLE),
      
      /** More than one certificate have been selected */
      MULTI_CERTS_SELECTED(MASK_WEST, STATE_CERTS_MULTI);

      /** The current components states to be enabled or disabled */
      private final boolean[] state;
      
      /** The current mask that defines components to which the current state is applied */
      private final boolean[] mask;
      
      /**
       * Creates a target view state.
       * 
       * @param    mask
       *           The current mask
       * @param    state
       *           The current state
       */
      private CertificatesViewStates(boolean[] mask, boolean[] state)
      {
         this.mask  = mask;
         this.state = state;
      }
      
      /**
       * Gets the current state that defines the affected components states.
       * 
       * @return   The current state
       */
      @Override
      public boolean[] getState()
      {
         return this.state;
      }

      /**
       * Gets the current mask that defines the set of affected components (widgets)
       * 
       * @return   The current mask
       */
      @Override
      public boolean[] getMask()
      {
         return mask;
      }
   }
   
   /**
    * This view interface that must be implemented by the concrete view according to GWTP
    * framework. Defines the exposed business methods.
    */
   public interface MyView
   extends View, HasUiHandlers<CertificatesUIHandlers>, Provider<HasEnabled[]>
   {
      /**
       * Changes accounts table header depending on the selected certificate role.
       * 
       * @param    option
       *           The selected certificate role
       */
      void toggleAccountsHeader(CertificatesOptions option);
      
      /**
       * Sets certificates
       * 
       * @param    certs
       *           The certificates data (model)
       */
      void setCerts(TaggedName[] certs);
      
      /**
       * Sets accounts
       * 
       * @param    accounts
       *           The accounts data (model)
       */
      void setAccounts(TaggedName[] accounts);
      
      /**
       * Tests if this set of certificates is selected
       * 
       * @param    selected
       *           The set of certificates
       * 
       * @return   True iff theses certificates have been selected, otherwise false.
       */
      boolean isSelectedCertificates(Set<TaggedName> selected);
      
      /**
       * Tests if this certificate is selected
       * 
       * @param    selected
       *           The target certificate
       * 
       * @return   True iff this target certificate has been selected, otherwise false.
       */
      boolean isSelectedCertificate(TaggedName selected);
      
      /**
       * Select the given certificate by its tagged name.
       * 
       * @param    cert
       *           The certificate tagged name.
       */
      void selectCertificate(TaggedName cert);
      
      /**
       * Select the given certificate by its alias.
       * 
       * @param    alias
       *           The certificate alias
       */
      void selectCertificate(String alias);
      
      /**
       * Returns the certificate definition view.
       * 
       * @return   The certificate definition view
       */
      CertificateDefinitionView getCertificateDefinitionView();
      
      /**
       * Returns the certificate details view.
       * 
       * @return   The certificate details view
       */
      CertificateDetailsView getCertificateDetailsView();
   }
   
   /**
    * Defines the proxy place for GWTP framework.
    */
   @ProxyStandard
   @NameToken(NameTokens.CERTS)
   public interface MyProxy
   extends ProxyPlace<CertificatesPresenter>
   {}
   
   /** The attachment point for modal dialogs on this view*/
   public static final NestedSlot MODAL_CONTENT = new NestedSlot();
   
   /** Administration server interface */
   private final AdminServiceAsync adminService;

   /** Common modal dialogs controller */
   private final ModalDialogs modalDialogsManager;
   
   /** The server alarm manager */
   private final Alarm serverAlarmsManager;
   
   /**
    * Defines the Certificates presenter.
    * 
    * @param    eventBus
    *           The event bus
    * @param    view
    *           The associated view
    * @param    proxy
    *           The proxy place
    * @param    placeManager
    *           The place manager
    * @param    viewStateMachine
    *           The view state machine
    * @param    adminService
    *           The administration service
    * @param    modalDialogsManager
    *           The common modal dialogs manager
    * @param    serverAlarmsManager
    *           The server alarms
    */
   @Inject
   public CertificatesPresenter(EventBus eventBus,
                                MyView view,
                                MyProxy proxy,
                                PlaceManager placeManager,
                                @Named("CertsViewStateMachine") ViewStateMachine viewStateMachine,
                                AdminServiceAsync adminService,
                                ModalDialogs modalDialogsManager,
                                Alarm serverAlarmsManager)
   {
      super(eventBus, view, proxy, HomePresenter.SLOT_CONTENT, placeManager, viewStateMachine);
      this.adminService = adminService;
      this.modalDialogsManager = modalDialogsManager;
      this.serverAlarmsManager = serverAlarmsManager;
      getView().setUiHandlers(this);
   }
   
   /**
    * Loads the data before the associated view will be displayed.
    * 
    * @param    request
    *           The current request
    */
   @Override
   public void prepareFromRequest(PlaceRequest request)
   {
      super.prepareFromRequest(request);
      
      CertificatesOptions opt;
      try
      {
         opt = CertificatesOptions.valueOf(
                  request.getParameter(RequestParameters.OPTION, CertificatesOptions.CA.name()));
      }
      catch (IllegalArgumentException ex)
      {
         opt = CertificatesOptions.CA;
      }
      
      boolean unusedOnly = Boolean.valueOf(request.getParameter(RequestParameters.UNUSED, "false"));
      
      String alias = request.getParameter(RequestParameters.ALIAS, "");
      
      final CertificatesOptions value = opt;
      
      updateCerts(value, unusedOnly, alias);
   }
   
   /**
    * Updates certificates model depending on the provided request data.
    * 
    * @param    opt
    *           The given certificate role
    * @param    unusedOnly
    *           The given flag to display unused only certificates
    * @param    command
    *           The command that will be executed after the server has responded with the data.
    */
   private void updateCerts(CertificatesOptions opt, boolean unusedOnly, Runnable command)
   {
      switch(opt)
      {
         case CA:
            updateCACerts(unusedOnly, command);
            break;
         case PEER:
            updatePeerCerts(unusedOnly, command);
            break;
         default:
      }
   }
   
   /**
    * Updates peer certificates model depending on the provided request data.
    * 
    * @param    unusedOnly
    *           The given flag to display unused only certificates
    * @param    command
    *           The command that will be executed after the server has been responded.
    */
   private void updatePeerCerts(boolean unusedOnly, Runnable command)
   {
      adminService.listPeerCerts(unusedOnly, new AsyncCallback<TaggedName[]>()
      {
         
         @Override
         public void onSuccess(TaggedName[] result)
         {
            getView().setCerts(result);
            getView().setAccounts(null);
            command.run();
         }
         
         @Override
         public void onFailure(Throwable caught)
         {
            command.run();
         }
      });
   }
   
   /**
    * Updates CA certificates model depending on the provided request data.
    * 
    * @param    unusedOnly
    *           The given flag to display unused only certificates
    * @param    command
    *           The command that will be executed after the server has been responded.
    */
   private void updateCACerts(boolean unusedOnly, Runnable command)
   {
      adminService.listAuthCerts(unusedOnly, new AsyncCallback<TaggedName[]>()
      {
         
         @Override
         public void onSuccess(TaggedName[] result)
         {
            getView().setCerts(result);
            getView().setAccounts(null);
            command.run();
         }
         
         @Override
         public void onFailure(Throwable caught)
         {
            command.run();
         }
      });
   }

   /**
    * This method is called if certificate's options are changed.
    * 
    * @param    value
    *           New certificate role.
    * @param    unusedOnly
    *           The new value of the unused checkbox.
    */
   @Override
   public void certificatesOptionsChanged(CertificatesOptions value, boolean unusedOnly)
   {
      logger.info("certificatesOptionsChanged " + value + " " + unusedOnly);
      clearRequestParameter(RequestParameters.ALIAS);
      updateCerts(value, unusedOnly, "");
   }

   /**
    * Refreshes list of certificates having these options.
    * 
    * @param    value
    *           New certificate role to display only certificates with this role.
    * @param    unusedOnly
    *           The unused checkbox value to display only unused(Unassigned) certificates.
    */
   @Override
   public void onRefreshCertificatesList(CertificatesOptions value, boolean unusedOnly)
   {
      clearRequestParameter(RequestParameters.ALIAS);
      updateCerts(value, unusedOnly, "");
   }

   /**
    * Updates certificates model depending on the provided request data.
    * 
    * @param    opt
    *           The given certificate role
    * @param    unusedOnly
    *           The given flag to display unused only certificates
    * @param    alias
    *           The certificate alias to select after the server has responded with the data.
    */
   private void updateCerts(CertificatesOptions opt, boolean unusedOnly, String alias)
   {
      updateCerts(opt, unusedOnly, ()->{
         logger.info("updateCerts " + opt.name() + " alias=" + alias);
         updateBrowserHistory(RequestParameters.OPTION, opt.name());
         updateBrowserHistory(RequestParameters.UNUSED, Boolean.toString(unusedOnly));
         getView().toggleAccountsHeader(opt);
         setState(CertificatesViewStates.CERTS_LIST_REFRESHED);
         if (!alias.isEmpty())
         {
            getView().selectCertificate(alias);
         }
      });
   }
   
   /**
    * Clears the alias parameter and updates certificates model.
    */
   private void updateCerts()
   {
      clearRequestParameter(RequestParameters.ALIAS);
      updateCerts(()->{});
   }
   
   /**
    * Updates certificates model depending on the provided request data.
    * 
    * @param    command
    *           The command that will be executed after the server has responded with the data.
    */
   private void updateCerts(Runnable command)
   {
      CertificatesOptions opt;
      try
      {
         opt = CertificatesOptions.valueOf(
                  getCurrentParameter(RequestParameters.OPTION, CertificatesOptions.CA.name()));
      }
      catch (IllegalArgumentException ex)
      {
         opt = CertificatesOptions.CA;
      }
      
      boolean unusedOnly = Boolean.valueOf(getCurrentParameter(RequestParameters.UNUSED, "false"));
      
      updateCerts(opt, unusedOnly, command);
   }
   
   /**
    * Adds new PEM certificate.
    * 
    * @param    alias
    *           The certificate name
    * @param    cert
    *           The array of certificate PEM strings
    * @param    value
    *           The certificate role.
    */
   @Override
   public void addNewCertificate(String alias, String[] cert, CertificatesOptions value)
   {
      CertDef certDef = new CertDef();
      certDef.alias = alias;
      certDef.certificate = cert;
      certDef.authority = (value == CertificatesOptions.CA);
      adminService.validateCertificate(certDef, new AsyncCallback<Integer>()
      {

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

         @Override
         public void onSuccess(Integer code)
         {
            CertificateValidity validity = CertificateValidity.get(code);
            if (validity == CertificateValidity.CERT_VALID)
            {
               if (certDef.authority)
               {
                  addCA(certDef);
               }
               else
               {
                  addNewCertificate(certDef);
               }
            }
            else
            {
               modalDialogsManager.showWarning(validity.getValidityMessage());
            }
         }
      });
   }
   
   /**
    * Adds new CA certificate given by its certificate definition object.
    * 
    * @param    certDef
    *           The certificate definition object
    */
   private void addCA(CertDef certDef)
   {
      adminService.isSelfSigned(certDef, new AsyncCallback<Boolean>()
      {

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

         @Override
         public void onSuccess(Boolean selfSigned)
         {
            if (selfSigned != null && selfSigned.booleanValue())
            {
               adminService.listAuthCerts(false, new AsyncCallback<TaggedName[]>()
               {

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

                  @Override
                  public void onSuccess(TaggedName[] authCerts)
                  {
                     if (authCerts.length == 0)
                     {
                        modalDialogsManager.showYesNo(
                                 "Add root certificate",
                                 "Are you sure you want to add a root CA certificate?",
                                 MessageType.QUESTION,
                                 (button) -> {
                                    if (button == 0)
                                    {
                                       addNewCertificate(certDef);
                                    }
                                 });
                      }
                      else
                      {
                         modalDialogsManager.showWarning("CA certificate list not empty -" +
                                  " adding root CA " + "certificate is not allowed",
                                  () -> {
                                     getView().getCertificateDefinitionView().hide();
                                  });
                      }
                  }
               });
            }
            else
            {
               addNewCertificate(certDef);
            }
            
         }
      });
   }
   
   /**
    * Adds new certificate given by its certificate definition object.
    * 
    * @param    certDef
    *           The certificate definition object
    */
   private void addNewCertificate(CertDef certDef)
   {
      adminService.addCert(certDef, new AsyncCallback<Boolean>()
      {

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

         @Override
         public void onSuccess(Boolean result)
         {
            if (result == null || !result.booleanValue())
            {
               serverAlarmsManager.ring();
            }
            else
            {
               getView().getCertificateDefinitionView().hide();
            }
            updateCerts();
         }
      });
   }

   /**
    * These certificates having the provided options have been selected by a user.
    * 
    * @param    selected
    *           The array of certificates selected by a user
    * @param    option
    *           The current certificate role.
    * @param    unusedOnly
    *           The current unused checkbox value.
    */
   @Override
   public void onCertsSelected(TaggedName[] selected, CertificatesOptions option, boolean unusedOnly)
   {
      logger.info("onCertsSelected " + selected.length + " option=" + option.name());
      if (selected == null || selected.length == 0)
      {
         getView().setAccounts(null);
         updateCerts(option, unusedOnly, "");
         return;
      }
      
      if (selected.length > 1)
      {
         getView().setAccounts(null);
         changeState(CertificatesViewStates.MULTI_CERTS_SELECTED);
      }
      else if (selected.length == 1)
      {
         changeState(CertificatesViewStates.SINGLE_CERT_SELECTED);
         if (option == CertificatesOptions.CA)
         {
            updateCertificateHierarchyAccounts(selected[0]);
         }
         else
         {
            updateProcessAccounts(selected[0]);
         }
      }
   }
   
   /**
    * Updates the process accounts signed by this certificate. 
    * 
    * @param    cert
    *           The certificate given by its tagged name
    */
   private void updateProcessAccounts(TaggedName cert)
   {
      adminService.getCertProcesses(cert.getName(), new AsyncCallback<TaggedName[]>()
      {

         @Override
         public void onFailure(Throwable caught)
         {
            logger.log(Level.SEVERE, "Failed to call getCertProcesses for alias " + cert.getName(), caught);
         }

         @Override
         public void onSuccess(TaggedName[] result)
         {
            getView().setAccounts(result);
         }
      });
   }
   
   /**
    * Updates the certificate hierarchy accounts.
    * 
    * @param    cert
    *           The certificate given by its tagged name
    */
   private void updateCertificateHierarchyAccounts(TaggedName cert)
   {
      adminService.getCertificateHierarchy(cert.getName(), new AsyncCallback<TaggedName[]>()
      {

         @Override
         public void onFailure(Throwable caught)
         {
            logger.log(Level.SEVERE, "Failed to call getCertificateHierarchy for alias " + cert.getName(), caught);
         }

         @Override
         public void onSuccess(TaggedName[] result)
         {
            getView().setAccounts(result);
         }
      });
   }

   /**
    * Notifies this set of selected certificates to delete.
    * 
    * @param    selected
    *           The selected certificates.
    */
   @Override
   public void onDeleteSelectedCertificates(Set<TaggedName> selected)
   {
      if (!getView().isSelectedCertificates(selected))
      {
         return;
      }
      int size = selected.size();
      String message;
      if (size > 1)
      {
         message = size + " certificates being deleted. Continue?";
      }
      else
      {
         message = "1 certificate being deleted. Continue?";
      }
      String title   = "Deleting Certificates";
      modalDialogsManager.showYesNo(title, message, MessageType.QUESTION, button -> {
         if (button == 0)
         {
            MultiCallback mc = new MultiCallback(adminService, serverAlarmsManager);
            LinkedHashMap<TaggedName, AdminCallback<TaggedName[]>> callbacks =
                     new LinkedHashMap<TaggedName, AdminCallback<TaggedName[]>>();
            for(TaggedName cert : selected)
            {
               AdminCallback<TaggedName[]> callback = mc.newCallback();
               callbacks.put(cert, callback);
            };
            for (Map.Entry<TaggedName, AdminCallback<TaggedName[]>> e : callbacks.entrySet())
            {
               this.adminService.getCertAccounts(e.getKey().getName(), e.getValue());
            }
            mc.onDone(() ->
            {
               Iterator<Map.Entry<TaggedName, AdminCallback<TaggedName[]>>> iter =
                        callbacks.entrySet().iterator();
               nextCertToDelete(iter).accept(true);
            });
         }
      });
   }

   private Consumer<Boolean> nextCertToDelete(Iterator<Map.Entry<TaggedName, AdminCallback<TaggedName[]>>> iter)
   {
      return new Consumer<Boolean>()
      {
         @Override
         public void accept(Boolean t)
         {
            if (t != null && t)
            {
               if (iter.hasNext())
               {
                  Map.Entry<TaggedName, AdminCallback<TaggedName[]>> e = iter.next();
                  TaggedName[] accounts = e.getValue().getResult();
                  if (accounts != null && accounts.length > 0)
                  {
                     // accounts exist, get their name into a string
                     String sacc = "";
                     for (int j = 0; j < accounts.length; j++)
                     {
                        sacc = sacc + accounts[j].getName();
                        if (j < accounts.length - 1)
                               sacc = sacc + ", ";
                     }
                     String alias = e.getKey().getName();
                     // display a Yes/No/Cancel dialog
                     String question = 
                        "Certificate &lt;" + alias + "&gt; is assigned to the following " +
                              "accounts: " + sacc + ".\n" + 
                        "Select Yes to delete it, No to skip it or Cancel to stop " +  
                        "deletion.";
                     String title = "Delete Certificate " + alias;
                     modalDialogsManager.showOKCancel(title, question, MessageType.WARNING, (button) -> {
                        if (button == 0)
                        {
                           AsyncAction<String, Boolean> delAction = deleteCertificate();
                           delAction.then(nextCertToDelete(iter));
                           delAction.accept(e.getKey().getName());
                        }
                        else if (button == 1)
                        {
                           nextCertToDelete(iter).accept(true);
                        }
                        else
                        {
                           return;
                        }
                     });
                  }
                  else
                  {
                     AsyncAction<String, Boolean> delAction = deleteCertificate();
                     delAction.then(nextCertToDelete(iter));
                     delAction.accept(e.getKey().getName());
                  }
               }
            }
            
         }
      };
   }
   
   /**
    * Gets the asynchronous action to delete certificate.
    * 
    * @return   The asynchronous action to delete certificate
    */
   private AsyncAction<String, Boolean> deleteCertificate()
   {
      return new AsyncAction<String, Boolean>()
      {

         @Override
         public void onFailure(Throwable caught)
         {
            logger.log(Level.SEVERE, "Failed to call deleteCert", caught);
            updateCerts(()->{setState(CertificatesViewStates.CERTS_LIST_REFRESHED);});
         }

         @Override
         public void onSuccess(Boolean result)
         {
            updateCerts(()->{setState(CertificatesViewStates.CERTS_LIST_REFRESHED);});
            if (result == null || !result)
            {
               serverAlarmsManager.ring();
            }
         }

         @Override
         public void accept(String alias)
         {
            adminService.deleteCert(alias, true, delegate);
         }
      };
   }

   /**
    * Change alias for the selected certificate.
    * 
    * @param    selected
    *           The selected certificate
    * @param    newAlias
    *           New certificate alias (name)
    */
   @Override
   public void changeAlias(TaggedName selected, String newAlias)
   {
      if (!getView().isSelectedCertificate(selected))
      {
         return;
      }
      
      adminService.moveCert(selected.getName(), newAlias, new AsyncCallback<Boolean>()
      {

         @Override
         public void onFailure(Throwable caught)
         {
            logger.log(Level.SEVERE, "Failed to call moveCert from " + selected.getName() +
                     " to " + newAlias, caught);
         }

         @Override
         public void onSuccess(Boolean result)
         {
            if (result == null || !result.booleanValue())
            {
               serverAlarmsManager.ring();
            }
            else
            {
               updateCerts(() -> { getView().selectCertificate(selected); });
            }
         }
      });
   }
   
   /**
    * Gets the asynchronous action to retrieve certificate definition.
    * 
    * @return   The asynchronous action to retrieve certificate definition
    */
   private AsyncAction<String, CertDef> getCertificate()
   {
      return new AsyncAction<String, CertDef>()
      {

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

         @Override
         public void onSuccess(CertDef result)
         {
            if (result == null)
            {
               serverAlarmsManager.ring();
            }
         }

         @Override
         public void accept(String alias)
         {
            logger.info("accept alias " +  alias);
            adminService.getCert(alias, delegate);
         }
      };
   }
   
   /**
    * Gets the asynchronous action to retrieve certificate details data.
    * 
    * @return   The asynchronous action to retrieve certificate details data
    */
   private AsyncAction<CertDef, Map<String, Object>> getCertificateDetails()
   {
      return new AsyncAction<CertDef, Map<String, Object>>()
      {

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

         @Override
         public void onSuccess(Map<String, Object> result)
         {
            if (result == null)
            {
               serverAlarmsManager.ring();
            }
            else
            {
               LinkedHashMap<TaggedName, TaggedName[]> tree = new LinkedHashMap<TaggedName, TaggedName[]>();
               Iterator<String> itr = result.keySet().iterator();
               while (itr.hasNext())
               {
                  String key = itr.next();
                  Object val = result.get(key);
                  String lbl = AdminHelper.getPropertyLabel(key);
                  
                  if (val instanceof Map)
                  {
                     ArrayList<TaggedName> list = AdminHelper.buildCertificateTreeNodes((Map) val);
                     tree.put(new TaggedName(lbl, ""), list.toArray(new TaggedName[list.size()]));
                  }
                  else
                  {
                     tree.put(new TaggedName(lbl, val.toString()), null);
                  }
               }
               getView().getCertificateDetailsView().setCertDetailsTree(tree);
               getView().getCertificateDetailsView().getModal().show();
            }
         }

         @Override
         public void accept(CertDef cd)
         {
            logger.info("accept CertDef " + cd.alias);
            adminService.getCertificateProperties(cd, delegate);
         }
      };
   }

   /**
    * View details of the given selected certificate.
    *  
    * @param    taggedName
    *           The selected certificate given by its tagged name
    */
   @Override
   public void viewCertificate(TaggedName taggedName)
   {
      AsyncAction<String, CertDef> action1 = getCertificate();
      AsyncAction<CertDef, Map<String, Object>> action2 = getCertificateDetails();
      action1.then(action2);
      action1.accept(taggedName.getName());
   }
}