/**
   * @see
   *     com.alfaariss.oa.api.IComponent#start(com.alfaariss.oa.api.configuration.IConfigurationManager,
   *     org.w3c.dom.Element)
   */
  public void start(IConfigurationManager oConfigurationManager, Element eConfig)
      throws OAException {
    try {
      // read organizations config
      Element eOrganizations = oConfigurationManager.getSection(eConfig, "idps");
      if (eOrganizations == null) {
        _logger.error(
            "No 'idps' section found in 'method' section in configuration from SAML authentication method");
        throw new OAException(SystemErrors.ERROR_CONFIG_READ);
      }

      IIDPStorage idpStorage = createStorage(oConfigurationManager, eOrganizations);
      idpStorage.start(oConfigurationManager, eOrganizations);

      IDPStorageManager idpStorageManager = Engine.getInstance().getIDPStorageManager();
      if (idpStorageManager.existStorage(idpStorage.getID())) {
        _logger.error("Storage not unique: " + idpStorage.getID());
        throw new OAException(SystemErrors.ERROR_INIT);
      }
      idpStorageManager.addStorage(idpStorage);

      // to start the super class, first an organization storage must be created
      super.start(oConfigurationManager, eConfig, idpStorage);

      if (_bIsEnabled) {
        String sFallback = _configurationManager.getParam(eOrganizations, "fallback");
        if (sFallback != null) {
          if (sFallback.equalsIgnoreCase("TRUE")) _bEnableFallback = true;
          else if (!sFallback.equalsIgnoreCase("FALSE")) {
            _logger.error(
                "Unknown value in 'fallback' configuration item (in organizations): " + sFallback);
            throw new OAException(SystemErrors.ERROR_CONFIG_READ);
          }

          _logger.debug("Optional organization fallback set to " + _bEnableFallback);
        }

        _profileWebBrowserSSO = new WebBrowserSSOProfile();
        _profileWebBrowserSSO.init(
            _configurationManager,
            eConfig,
            SAML2Exchange.getEntityDescriptor(_sLinkedIDPProfile),
            _idMapper,
            _organizationStorage,
            _sMethodId,
            _sLinkedIDPProfile,
            _conditionsWindow,
            _oAuthnInstantWindow,
            _oRemoteSAMLUserProvisioningProfile);
      }
    } catch (OAException e) {
      throw e;
    } catch (Exception e) {
      _logger.fatal("Internal error during start", e);
      throw new OAException(SystemErrors.ERROR_INTERNAL);
    }
  }
  /** @see com.alfaariss.oa.authentication.remote.saml2.BaseSAML2AuthenticationMethod#stop() */
  public void stop() {
    if (_profileWebBrowserSSO != null) _profileWebBrowserSSO.destroy();

    if (_mRemoteIDPLists != null) _mRemoteIDPLists.clear();

    if (_organizationStorage != null) {
      Engine.getInstance().getIDPStorageManager().removeStorage(_organizationStorage.getID());
      _organizationStorage.stop();
    }

    super.stop();
  }
  /**
   * Requestor selection based on RemoteASelectMethod.authenticate.
   *
   * <p>When implementing Synchronous (querying) authentication, this method should be adapted. -
   * Warnings
   *
   * @see
   *     com.alfaariss.oa.sso.authentication.web.IWebAuthenticationMethod#authenticate(javax.servlet.http.HttpServletRequest,
   *     javax.servlet.http.HttpServletResponse, com.alfaariss.oa.api.session.ISession)
   */
  @SuppressWarnings("unchecked")
  public UserEvent authenticate(
      HttpServletRequest request, HttpServletResponse response, ISession session)
      throws OAException {
    try {
      ISessionAttributes oAttributes = session.getAttributes();

      // check proxy att:
      Integer intCnt = (Integer) oAttributes.get(ProxyAttributes.class, ProxyAttributes.PROXYCOUNT);
      if (intCnt != null && intCnt <= 0) {
        _logger.debug("No more authentication proxying allowed: " + intCnt);
        _eventLogger.info(
            new UserEventLogItem(
                session,
                request.getRemoteAddr(),
                UserEvent.AUTHN_METHOD_FAILED,
                this,
                "ProxyCount <= 0"));
        return UserEvent.AUTHN_METHOD_FAILED;
      }

      SAML2IDP organization = null;
      List<Warnings> warnings = null;

      // Figure out whether there exists a pre-selected organization in the URLPath context
      URLPathContext oURLPathContext =
          (URLPathContext)
              oAttributes.get(
                  com.alfaariss.oa.util.session.ProxyAttributes.class,
                  com.alfaariss.oa.util.session.ProxyAttributes.PROXY_URLPATH_CONTEXT);

      if (oURLPathContext != null) {
        organization = processURLPathContext(oAttributes, oURLPathContext);
      }

      if (organization != null) {
        _logger.info("Established organization from URLPathContext: " + organization.getID());

        // Add to Session context if it is not there yet
        if (!oAttributes.contains(
            SAML2AuthenticationMethod.class, _sMethodId + "." + SELECTED_ORGANIZATION)) {
          oAttributes.put(
              SAML2AuthenticationMethod.class, _sMethodId, SELECTED_ORGANIZATION, organization);
        }
      } else {
        if (oAttributes.contains(
            SAML2AuthenticationMethod.class, _sMethodId + "." + SELECTED_ORGANIZATION)) {
          organization =
              (SAML2IDP)
                  oAttributes.get(
                      SAML2AuthenticationMethod.class, _sMethodId + "." + SELECTED_ORGANIZATION);
        } else {
          List<SAML2IDP> listSelectableOrganizations = null;

          if (oAttributes.contains(
              SAML2AuthenticationMethod.class, _sMethodId + "." + LIST_AVAILABLE_ORGANIZATIONS)) {
            // The selected organization was not available, select again:
            listSelectableOrganizations =
                (List<SAML2IDP>)
                    oAttributes.get(
                        SAML2AuthenticationMethod.class,
                        _sMethodId + "." + LIST_AVAILABLE_ORGANIZATIONS);

            warnings = new Vector<Warnings>();
            warnings.add(Warnings.WARNING_ORGANIZATION_UNAVAILABLE);
          } else {
            IUser oUser = session.getUser();
            if (oUser != null) {
              // verify if user that was identified in previous authn method may use this SAML2
              // authn method
              if (!oUser.isAuthenticationRegistered(_sMethodId)) {
                _eventLogger.info(
                    new UserEventLogItem(
                        session,
                        request.getRemoteAddr(),
                        UserEvent.AUTHN_METHOD_NOT_REGISTERED,
                        this,
                        null));

                return UserEvent.AUTHN_METHOD_NOT_REGISTERED;
              }
            }

            listSelectableOrganizations = new Vector<SAML2IDP>();
            Vector fallbackList = new Vector<String>();

            Collection<String> cForcedOrganizations = getForcedIDPs(session);
            if (cForcedOrganizations != null && !cForcedOrganizations.isEmpty())
              oAttributes.put(
                  SAML2AuthNConstants.class,
                  SAML2AuthNConstants.FORCED_ORGANIZATIONS,
                  cForcedOrganizations);

            List<SAML2IDP> listIDPs = _organizationStorage.getAll();
            for (SAML2IDP saml2IDP : listIDPs) {
              fallbackList.add(saml2IDP);
              if (cForcedOrganizations == null || cForcedOrganizations.contains(saml2IDP.getID())) {
                // if no forced organizations are defined or organization is in the forced
                // organization list: Add to select organization list.
                listSelectableOrganizations.add(saml2IDP);
              }
            }

            if (listSelectableOrganizations.isEmpty()) {
              // DD if no forced orgs are known locally, add all and let user decide.
              // Make sure proxy orgs are send with AuthN request
              listSelectableOrganizations = fallbackList;
            }
          }

          if (listSelectableOrganizations.size() == 0) {
            _logger.debug("No organizations available to choose from");
            _eventLogger.info(
                new UserEventLogItem(
                    session,
                    request.getRemoteAddr(),
                    UserEvent.AUTHN_METHOD_NOT_SUPPORTED,
                    this,
                    null));

            return UserEvent.AUTHN_METHOD_NOT_SUPPORTED;
          }

          if (_oSelector == null) {
            organization = listSelectableOrganizations.get(0);

            _logger.debug("No selector configured, using: " + organization.getID());
          } else {
            try {
              // Select requestor
              organization =
                  _oSelector.resolve(
                      request,
                      response,
                      session,
                      listSelectableOrganizations,
                      _sFriendlyName,
                      warnings);
            } catch (OAException e) {
              _eventLogger.info(
                  new UserEventLogItem(
                      session,
                      request.getRemoteAddr(),
                      UserEvent.INTERNAL_ERROR,
                      this,
                      "selecting organization"));
              throw e;
            }
          }

          if (organization == null) {
            // Page is shown
            _eventLogger.info(
                new UserEventLogItem(
                    session,
                    request.getRemoteAddr(),
                    UserEvent.AUTHN_METHOD_IN_PROGRESS,
                    this,
                    null));

            return UserEvent.AUTHN_METHOD_IN_PROGRESS;
          }

          oAttributes.put(
              SAML2AuthenticationMethod.class, _sMethodId, SELECTED_ORGANIZATION, organization);

          listSelectableOrganizations.remove(organization);
          oAttributes.put(
              SAML2AuthenticationMethod.class,
              _sMethodId,
              LIST_AVAILABLE_ORGANIZATIONS,
              listSelectableOrganizations);
        }
      }

      UserEvent event = null;

      if (_profileWebBrowserSSO != null) {
        event =
            _profileWebBrowserSSO.process(
                request, response, session, organization, _htAttributeMapper);

        _eventLogger.info(
            new UserEventLogItem(session, request.getRemoteAddr(), event, this, null));
      } else {
        _eventLogger.info(
            new UserEventLogItem(
                session,
                request.getRemoteAddr(),
                UserEvent.AUTHN_METHOD_FAILED,
                this,
                "No suitable SAML2 profile could be found for authentication"));
        event = UserEvent.AUTHN_METHOD_FAILED;
      }

      if (event == UserEvent.AUTHN_METHOD_FAILED && _bEnableFallback) {
        // fallback
        event = UserEvent.AUTHN_METHOD_IN_PROGRESS;
        oAttributes.remove(
            SAML2AuthenticationMethod.class, _sMethodId + "." + SELECTED_ORGANIZATION);

        _eventLogger.info(
            new UserEventLogItem(
                session,
                request.getRemoteAddr(),
                UserEvent.AUTHN_METHOD_IN_PROGRESS,
                this,
                "Fallback mechanism activated"));

        event = authenticate(request, response, session);
      }

      return event;
    } catch (OAException oae) {
      _eventLogger.info(
          new UserEventLogItem(
              session,
              request.getRemoteAddr(),
              UserEvent.AUTHN_METHOD_FAILED,
              this,
              oae.getLocalizedMessage()));

      throw oae;
    }
  }