package org.apereo.cas.web.flow.resolver.impl;

import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import org.apereo.cas.CentralAuthenticationService;
import org.apereo.cas.authentication.AuthenticationException;
import org.apereo.cas.authentication.AuthenticationResultBuilder;
import org.apereo.cas.authentication.AuthenticationSystemSupport;
import org.apereo.cas.authentication.Credential;
import org.apereo.cas.authentication.principal.Service;
import org.apereo.cas.services.MultifactorAuthenticationProviderSelector;
import org.apereo.cas.services.RegisteredService;
import org.apereo.cas.services.RegisteredServiceAccessStrategyUtils;
import org.apereo.cas.services.ServicesManager;
import org.apereo.cas.ticket.AbstractTicketException;
import org.apereo.cas.ticket.registry.TicketRegistrySupport;
import org.apereo.cas.validation.AuthenticationRequestServiceSelectionStrategy;
import org.apereo.cas.web.flow.CasWebflowConstants;
import org.apereo.cas.web.flow.resolver.CasDelegatingWebflowEventResolver;
import org.apereo.cas.web.flow.resolver.CasWebflowEventResolver;
import org.apereo.cas.web.support.WebUtils;
import org.springframework.http.HttpStatus;
import org.springframework.web.util.CookieGenerator;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;

import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * This is {@link InitialAuthenticationAttemptWebflowEventResolver},
 * which handles the initial authentication attempt and calls upon a number of
 * embedded resolvers to produce the next event in the authentication flow.
 *
 * @author Misagh Moayyed
 * @since 5.0.0
 */
public class InitialAuthenticationAttemptWebflowEventResolver extends AbstractCasWebflowEventResolver
        implements CasDelegatingWebflowEventResolver {

    private final List<CasWebflowEventResolver> orderedResolvers = new ArrayList<>();

    private CasWebflowEventResolver selectiveResolver;

    public InitialAuthenticationAttemptWebflowEventResolver(final AuthenticationSystemSupport authenticationSystemSupport,
                                                            final CentralAuthenticationService centralAuthenticationService,
                                                            final ServicesManager servicesManager, final TicketRegistrySupport ticketRegistrySupport,
                                                            final CookieGenerator warnCookieGenerator,
                                                            final List<AuthenticationRequestServiceSelectionStrategy> authenticationSelectionStrategies,
                                                            final MultifactorAuthenticationProviderSelector selector) {
        super(authenticationSystemSupport, centralAuthenticationService, servicesManager, ticketRegistrySupport, warnCookieGenerator,
                authenticationSelectionStrategies, selector);
    }

    @Override
    public Set<Event> resolveInternal(final RequestContext context) {
        try {
            final Credential credential = getCredentialFromContext(context);
            if (credential != null) {
                final AuthenticationResultBuilder builder =
                        this.authenticationSystemSupport.handleInitialAuthenticationTransaction(credential);

                if (builder.getInitialAuthentication().isPresent()) {
                    WebUtils.putAuthenticationResultBuilder(builder, context);
                    WebUtils.putAuthentication(builder.getInitialAuthentication().get(), context);
                }
            }
            final Service service = WebUtils.getService(context);
            if (service != null) {

                logger.debug("Locating service {} in service registry to determine authentication policy", service);
                final RegisteredService registeredService = this.servicesManager.findServiceBy(service);
                RegisteredServiceAccessStrategyUtils.ensureServiceAccessIsAllowed(service, registeredService);

                final Set<Event> resolvedEvents = resolveCandidateAuthenticationEvents(context, service, registeredService);
                if (!resolvedEvents.isEmpty()) {
                    putResolvedEventsAsAttribute(context, resolvedEvents);
                    final Event finalResolvedEvent = this.selectiveResolver.resolveSingle(context);
                    if (finalResolvedEvent != null) {
                        return ImmutableSet.of(finalResolvedEvent);
                    }
                }
            }

            final AuthenticationResultBuilder builder = WebUtils.getAuthenticationResultBuilder(context);
            if (builder == null) {
                throw new IllegalArgumentException("No authentication result builder can be located in the context");
            }
            return ImmutableSet.of(grantTicketGrantingTicketToAuthenticationResult(context, builder, service));
        } catch (final Exception e) {
            Event event = returnAuthenticationExceptionEventIfNeeded(e);
            if (event == null) {
                logger.warn(e.getMessage(), e);
                event = newEvent(CasWebflowConstants.TRANSITION_ID_ERROR, e);
            }
            final HttpServletResponse response = WebUtils.getHttpServletResponse(context);
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            return ImmutableSet.of(event);
        }
    }

    /**
     * Resolve candidate authentication events set.
     *
     * @param context           the context
     * @param service           the service
     * @param registeredService the registered service
     * @return the set
     */
    protected Set<Event> resolveCandidateAuthenticationEvents(final RequestContext context, final Service service,
                                                              final RegisteredService registeredService) {

        final Set<Event> eventBuilder = Sets.newLinkedHashSet();
        this.orderedResolvers
                .stream()
                .filter(r -> r != null)
                .forEach(r -> {
                    logger.debug("Evaluating authentication policy via {} for registered service {} and service {}",
                            r.getName(), registeredService.getServiceId(), service);
                    final Event result = r.resolveSingle(context);

                    if (result != null) {
                        logger.debug("Recorded the resulting event {} for {}", result, r.getName());
                        eventBuilder.add(result);
                    } else {
                        logger.debug("Resulting event for {} is blank/ignored", r.getName());
                    }
                });

        return eventBuilder;
    }

    @Override
    public void addDelegate(final CasWebflowEventResolver r) {
        orderedResolvers.add(r);
    }

    @Override
    public void addDelegate(final CasWebflowEventResolver r, final int index) {
        orderedResolvers.add(index, r);
    }

    public void setSelectiveResolver(final CasWebflowEventResolver r) {
        this.selectiveResolver = r;
    }

    private Event returnAuthenticationExceptionEventIfNeeded(final Exception e) {
        final Exception ex;
        if (e instanceof AuthenticationException || e instanceof AbstractTicketException) {
            ex = e;
        } else if (e.getCause() instanceof AuthenticationException || e.getCause() instanceof AbstractTicketException) {
            ex = (Exception) e.getCause();
        } else {
            return null;
        }

        logger.debug(ex.getMessage(), ex);
        return newEvent(CasWebflowConstants.TRANSITION_ID_AUTHENTICATION_FAILURE, ex);
    }
}

@@ -92,47 +92,8 @@
 et;%0A
-import com.google.common.collect.Sets;%0A
 impo
@@ -1437,19 +1437,81 @@
 va.util.
-Set
+Objects;%0Aimport java.util.Set;%0Aimport java.util.stream.Collectors
 ;%0A%0A/**%0A 
@@ -5929,70 +5929,8 @@
 ice,
-%0A                                                             
  fin
@@ -5971,25 +5971,24 @@
 vice) %7B%0A
-%0A
         
 final Se
@@ -5983,72 +5983,14 @@
     
-final Set%3CEvent%3E eventBuilder = Sets.newLinkedHashSet();%0A       
+return
  thi
@@ -6007,33 +6007,16 @@
 esolvers
-%0A                
 .stream(
@@ -6045,19 +6045,21 @@
 ter(
-r -%3E r != n
+Objects::nonN
 ull)
@@ -6080,266 +6080,31 @@
    .
-forEach(r -%3E %7B%0A                    logger.debug(%22Evaluating authentication policy via %7B%7D for registered service %7B%7D and service %7B%7D%22,%0A                            r.getName(), registeredService.getServiceId(), service);%0A                    final Event result = 
+map(resolver -%3E resolve
 r.re
@@ -6123,18 +6123,17 @@
 context)
-;%0A
+)
 %0A       
@@ -6145,378 +6145,78 @@
     
-    if (result != null) %7B%0A                        logger.debug(%22Recorded the resulting event %7B%7D for %7B%7D%22, result, r.getName());%0A                        eventBuilder.add(result);%0A                    %7D else %7B%0A                        logger.debug(%22Resulting event for %7B%7D is blank/ignored%22, r.getName());%0A                    %7D%0A                %7D);%0A%0A        return eventBuilder
+.filter(Objects::nonNull)%0A                .collect(Collectors.toSet())
 ;%0A  

Replace with an stream (#2203)