package org.ovirt.engine.core.aaa.filters;

import java.io.IOException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.List;
import java.util.Observable;
import java.util.Observer;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.ovirt.engine.api.extensions.Base;
import org.ovirt.engine.api.extensions.ExtMap;
import org.ovirt.engine.api.extensions.aaa.Authn;
import org.ovirt.engine.core.aaa.AuthType;
import org.ovirt.engine.core.aaa.AuthenticationProfile;
import org.ovirt.engine.core.aaa.AuthenticationProfileRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This filter should be added to the {@code web.xml} file to the applications that want to use the authentication
 * mechanism implemented in this package.
 */
public class NegotiationFilter implements Filter {

    private static final Logger log = LoggerFactory.getLogger(NegotiationFilter.class);

    private static final String CAPABILITIES_PARAMETER = "capabilities";

    /**
     * In order to support several alternative authentication extension we
     * store their associated profiles in a stack inside the HTTP session,
     * this is the key for that stack.
     */
    private static final String STACK_ATTR = NegotiationFilter.class.getName() + ".stack";

    private volatile Collection<String> schemes;
    private volatile List<AuthenticationProfile> profiles;
    private long caps = 0;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        String capsParam = filterConfig.getInitParameter(CAPABILITIES_PARAMETER);
        if (capsParam != null) {
            for (String nego : capsParam.trim().split(" *\\| *")) {
                try {
                    caps |= Authn.Capabilities.class.getField(nego).getLong(null);
                } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException ex) {
                    log.error("Error calculating authn capabilities while accessing constant  {}", nego);
                }
            }
        }

        AuthenticationProfileRepository.getInstance().addObserver(
            new Observer() {
                @Override
                public void update(Observable o, Object arg) {
                    cacheNegotiatingProfiles();
                }
            }
        );
        cacheNegotiatingProfiles();
    }

    @Override
    public void destroy() {
    }

    private synchronized void cacheNegotiatingProfiles() {
        schemes = new ArrayList<String>();
        profiles = new ArrayList<AuthenticationProfile>();

        for (AuthenticationProfile profile : AuthenticationProfileRepository.getInstance().getProfiles()) {
            ExtMap authnContext = profile.getAuthn().getContext();
            if ((authnContext.<Long> get(Authn.ContextKeys.CAPABILITIES).longValue() & caps) != 0) {
                profiles.add(profile);
                schemes.addAll(authnContext.<Collection<String>>get(Authn.ContextKeys.HTTP_AUTHENTICATION_SCHEME, Collections.<String>emptyList()));
            }
        }

        Collections.sort(
            profiles,
            new Comparator<AuthenticationProfile>() {
                @Override
                public int compare(AuthenticationProfile o1, AuthenticationProfile o2) {
                    return Integer.valueOf(o1.getNegotiationPriority()).compareTo(o2.getNegotiationPriority());
                }
            }
        );
    }

    @SuppressWarnings("unchecked")
    @Override
    public void doFilter(ServletRequest req, ServletResponse rsp, FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest httpreq = (HttpServletRequest)req;

        if (FiltersHelper.isAuthenticated(httpreq) || httpreq.getAttribute(FiltersHelper.Constants.REQUEST_AUTH_RECORD_KEY) != null) {
            chain.doFilter(req, rsp);
        } else {
            ((HttpServletRequest) req).setAttribute(FiltersHelper.Constants.REQUEST_SCHEMES_KEY, schemes);
            HttpSession session = httpreq.getSession(false);
            Deque<AuthenticationProfile> stack = null;
            if (session != null) {
                stack = (Deque<AuthenticationProfile>)session.getAttribute(STACK_ATTR);
            }
            if (stack == null) {
                stack = new ArrayDeque<AuthenticationProfile>();
                stack.addAll(profiles);
            }
            doAuth(httpreq, (HttpServletResponse) rsp, stack);
            if (!stack.isEmpty()) {
                httpreq.getSession(true).setAttribute(STACK_ATTR, stack);
            } else {
                if (session != null) {
                    session.removeAttribute(STACK_ATTR);
                }
                chain.doFilter(req, rsp);
            }
        }
    }

    private void doAuth(HttpServletRequest req, HttpServletResponse rsp, Deque<AuthenticationProfile> stack)
            throws IOException, ServletException {

        boolean stop = false;
        while (!stop && !stack.isEmpty()) {
            AuthenticationProfile profile = stack.peek();

            ExtMap output = profile.getAuthn().invoke(
                new ExtMap().mput(
                    Base.InvokeKeys.COMMAND,
                    Authn.InvokeCommands.AUTHENTICATE_NEGOTIATE
                ).mput(
                    Authn.InvokeKeys.HTTP_SERVLET_REQUEST,
                    req
                ).mput(
                    Authn.InvokeKeys.HTTP_SERVLET_RESPONSE,
                    rsp
                )
            );

            switch (output.<Integer> get(Authn.InvokeKeys.RESULT)) {
            case Authn.AuthResult.SUCCESS:
                req.setAttribute(FiltersHelper.Constants.REQUEST_AUTH_RECORD_KEY,
                    output.<ExtMap> get(Authn.InvokeKeys.AUTH_RECORD));
                req.setAttribute(FiltersHelper.Constants.REQUEST_AUTH_TYPE_KEY,
                    AuthType.NEGOTIATION);
                req.setAttribute(FiltersHelper.Constants.REQUEST_PROFILE_KEY, profile.getName());
                stack.clear();
                break;

            case Authn.AuthResult.NEGOTIATION_UNAUTHORIZED:
                stack.pop();
                break;

            case Authn.AuthResult.NEGOTIATION_INCOMPLETE:
                stop = true;
                break;

            default:
                log.error("Unexpected authentication result. AuthResult code is {}",
                    output.<Integer> get(Authn.InvokeKeys.RESULT));
                stack.pop();
                break;
            }
        }
    }

}

@@ -3741,15 +3741,15 @@
 ger.
-valueOf
+compare
 (o1.
@@ -3776,20 +3776,10 @@
 ty()
-).compareTo(
+, 
 o2.g

Use "Integer.compare" in "NegotiationFilter"