/**
 * 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.cxf.tools.java2wsdl.processor.internal;

import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import org.apache.cxf.BusFactory;
import org.apache.cxf.bus.spring.BusApplicationContext;
import org.apache.cxf.common.classloader.ClassLoaderUtils;
import org.apache.cxf.common.util.StringUtils;
import org.apache.cxf.databinding.DataBinding;
import org.apache.cxf.frontend.AbstractServiceFactory;
import org.apache.cxf.service.ServiceBuilder;
import org.apache.cxf.tools.common.ToolConstants;
import org.apache.cxf.tools.common.ToolException;
import org.apache.cxf.tools.java2wsdl.processor.FrontendFactory;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.UrlResource;

/**
 * This class constructs ServiceBuilder objects using Spring. These objects are used to access the services
 * and the data bindings to generate the wsdl.
 */
public final class SpringServiceBuilderFactory extends ServiceBuilderFactory {

    private List<String> beanDefinitions;

    public SpringServiceBuilderFactory(List<String> beanDefinitions) {
        super();
        this.beanDefinitions = beanDefinitions;
    }

    public SpringServiceBuilderFactory() {
        super();
        this.beanDefinitions = new ArrayList<>(0);
    }

    /**
     * Convert a parameter value to the name of a bean we'd use for a data binding.
     *
     * @param databindingName
     * @return
     */
    public static String databindingNameToBeanName(String databindingName) {
        return StringUtils.capitalize(databindingName.toLowerCase()) + ToolConstants.DATABIND_BEAN_NAME_SUFFIX;
    }

    @Override
    public ServiceBuilder newBuilder(FrontendFactory.Style s) {
        ApplicationContext applicationContext = getApplicationContext(beanDefinitions);
        DataBinding dataBinding;
        String databindingBeanName = databindingNameToBeanName(databindingName);
        try {
            dataBinding = (DataBinding)applicationContext.getBean(databindingBeanName);
        } catch (RuntimeException e) {
            throw new ToolException("Cannot get databinding bean " + databindingBeanName
                                    + " for databinding " + databindingName, e);
        }

        String beanName = getBuilderBeanName(s);
        final ServiceBuilder builder;

        try {
            builder = applicationContext.getBean(beanName, ServiceBuilder.class);
            AbstractServiceFactory serviceFactory = (AbstractServiceFactory)builder;
            serviceFactory.setDataBinding(dataBinding);
        } catch (RuntimeException e) {
            throw new ToolException("Can not get ServiceBuilder bean " + beanName
                                    + "to initialize the ServiceBuilder for style: " + s + " Reason: \n"
                                    + e.getMessage(), e);
        }
        builder.setServiceClass(serviceClass);
        return builder;
    }

    /**
     * Return the name of a prototype bean from Spring that can provide the service. The use of a bean allows
     * for the possibility of an override.
     *
     * @param s Style of service
     * @return name of bean.
     */
    protected String getBuilderBeanName(FrontendFactory.Style s) {
        return s + "ServiceBuilderBean";
    }

    /**
     * This is factored out to permit use in a unit test.
     *
     * @param additionalFilePathnames
     * @return
     */
    public static ApplicationContext getApplicationContext(List<String> additionalFilePathnames) {
        BusApplicationContext busApplicationContext = BusFactory.getDefaultBus()
            .getExtension(BusApplicationContext.class);
        GenericApplicationContext appContext = new GenericApplicationContext(busApplicationContext);
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(appContext);
        List<URL> urls = ClassLoaderUtils.getResources("META-INF/cxf/java2wsbeans.xml",
                                                       SpringServiceBuilderFactory.class);
        for (URL url : urls) {
            reader.loadBeanDefinitions(new UrlResource(url));
        }

        for (String pathname : additionalFilePathnames) {
            try {
                reader.loadBeanDefinitions(new FileSystemResource(pathname));
            } catch (BeanDefinitionStoreException bdse) {
                throw new ToolException("Unable to open bean definition file " + pathname, bdse.getCause());
            }
        }
        appContext.refresh();
        return appContext;
    }

    public void setBeanDefinitions(List<String> beanDefinitions) {
        this.beanDefinitions = beanDefinitions;
    }

}
