/**
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.apache.catalina.authenticator.jaspic;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.message.AuthException;
import javax.security.auth.message.MessageInfo;
import javax.security.auth.message.config.ServerAuthConfig;
import javax.security.auth.message.config.ServerAuthContext;
import javax.security.auth.message.module.ServerAuthModule;

import org.apache.tomcat.util.res.StringManager;

/**
 * Basic implementation primarily intended for use when using third-party
 * {@link ServerAuthModule} implementations that only provide the module. This
 * implementation supports configuring the {@link ServerAuthContext} with
 * multiple modules.
 */
public class SimpleServerAuthConfig implements ServerAuthConfig {

    private static StringManager sm = StringManager.getManager(SimpleServerAuthConfig.class);

    private static final String SERVER_AUTH_MODULE_KEY_PREFIX =
            "org.apache.catalina.authenticator.jaspic.ServerAuthModule.";

    private final String layer;
    private final String appContext;
    private final CallbackHandler handler;
    private final Map<String,String> properties;

    private volatile ServerAuthContext serverAuthContext;

    public SimpleServerAuthConfig(String layer, String appContext, CallbackHandler handler,
            Map<String,String> properties) {
        this.layer = layer;
        this.appContext = appContext;
        this.handler = handler;
        this.properties = properties;
    }


    @Override
    public String getMessageLayer() {
        return layer;
    }


    @Override
    public String getAppContext() {
        return appContext;
    }


    @Override
    public String getAuthContextID(MessageInfo messageInfo) {
        return messageInfo.toString();
    }


    @Override
    public void refresh() {
        serverAuthContext = null;
    }


    @Override
    public boolean isProtected() {
        return false;
    }


    @SuppressWarnings({"rawtypes", "unchecked"}) // JASPIC API uses raw types
    @Override
    public ServerAuthContext getAuthContext(String authContextID, Subject serviceSubject,
            Map properties) throws AuthException {
        ServerAuthContext serverAuthContext = this.serverAuthContext;
        if (serverAuthContext == null) {
            synchronized (this) {
                if (this.serverAuthContext == null) {
                    Map<String,String> mergedProperties = new HashMap<>();
                    if (this.properties != null) {
                        mergedProperties.putAll(this.properties);
                    }
                    if (properties != null) {
                        mergedProperties.putAll(properties);
                    }

                    List<ServerAuthModule> modules = new ArrayList<>();
                    int moduleIndex = 1;
                    String key = SERVER_AUTH_MODULE_KEY_PREFIX + moduleIndex;
                    String moduleClassName = mergedProperties.get(key);
                    while (moduleClassName != null) {
                        try {
                            Class<?> clazz = Class.forName(moduleClassName);
                            ServerAuthModule module =
                                    (ServerAuthModule) clazz.getConstructor().newInstance();
                            module.initialize(null, null, handler, mergedProperties);
                            modules.add(module);
                        } catch (ClassNotFoundException | InstantiationException |
                                IllegalAccessException | IllegalArgumentException |
                                InvocationTargetException | NoSuchMethodException |
                                SecurityException e) {
                            AuthException ae = new AuthException();
                            ae.initCause(e);
                            throw ae;
                        }

                        // Look for the next module
                        moduleIndex++;
                        key = SERVER_AUTH_MODULE_KEY_PREFIX + moduleIndex;
                        moduleClassName = mergedProperties.get(key);
                    }

                    if (modules.size() == 0) {
                        throw new AuthException(sm.getString("simpleServerAuthConfig.noModules"));
                    }

                    this.serverAuthContext = createServerAuthContext(modules);
                }
                serverAuthContext = this.serverAuthContext;
            }
        }

        return serverAuthContext;
    }


    protected ServerAuthContext createServerAuthContext(List<ServerAuthModule> modules) {
        return new SimpleServerAuthContext(modules);
    }
}
