/*
 * Copyright to the original author or authors.
 * 
 * Licensed 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.rioproject.impl.bean.spring;

import org.rioproject.impl.bean.BeanFactory;
import org.rioproject.impl.servicebean.DefaultServiceBeanManager;
import org.rioproject.servicebean.ServiceBeanContext;
import org.rioproject.servicebean.ServiceBeanManager;
import org.rioproject.deploy.ServiceBeanInstantiationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;

/**
 * Instantiates a Plain Old Java Object (POJO) using Spring, providing support
 * for remote invocations and administration by adapting the POJO into the
 * framework using the Spring framework.
 *
 * @see org.rioproject.servicebean.ServiceBeanFactory
 * @see org.rioproject.impl.bean.BeanFactory
 *
 * @author Dennis Reedy
 */
@SuppressWarnings("unused")
public class SpringBeanFactory extends BeanFactory {
    /** Codebase token to be replaced by the service's actual token */
    static final String CODEBASE_TOK = "$codebase";
    /** Codebase token to be replaced by the service's actual token */
    static final String CLASSPATH_TOK = "$classpath";
    private boolean useCodebase;
    /** A Logger */
    static final Logger logger = LoggerFactory.getLogger(SpringBeanFactory.class);

    /**
     * Get the bean object
     */
    protected Object getBean(ServiceBeanContext context) throws Exception {
        String[] configs = (String[])context.getConfiguration().getEntry("spring",
                                                                         "config",
                                                                         String[].class,
                                                                         new String[]{""});
        if(configs.length==0) {
            throw new ServiceBeanInstantiationException("No Spring service configuration");
        }
        String codebase = context.getServiceElement().getExportBundles()[0].getCodebase();
        for(int i=0; i<configs.length; i++) {
            if(configs[i].contains(CODEBASE_TOK + "/")) {
                configs[i] = replace(configs[i], CODEBASE_TOK+"/", codebase);
                useCodebase = true;
                logger.debug("Loading application context [{}]", configs[i]);
            } else if (configs[i].contains(CLASSPATH_TOK + "/")) {
                configs[i] = replace(configs[i], CLASSPATH_TOK+"/", codebase);
                logger.debug("Loading application context [{}]", configs[i]);
            } else {
                configs[i] = replace(configs[i], CODEBASE_TOK, codebase);
                logger.debug("Loading application context [{}]", configs[i]);
            }
        }
        final Thread currentThread = Thread.currentThread();
        ClassLoader cCL =
            AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
                public ClassLoader run() {
                    return (currentThread.getContextClassLoader());
                }
            });
        /*
         * Reflection is used here because Spring technology classes may be
         * loaded by a child classloader of the the classloader which loaded
         * this class. If this is the case then we will be facing
         * NoClassDefFoundError exceptions.
         */
        /*
        GenericApplicationContext ctx = new GenericApplicationContext();
        ctx.setClassLoader(cCL);
        */

        Class<?> ctxClass = Class.forName( "org.springframework.context.support.GenericApplicationContext", true, cCL);
        Object ctx = ctxClass.newInstance();

        Method ctx_setClassLoader = ctxClass.getMethod("setClassLoader", ClassLoader.class);
        ctx_setClassLoader.invoke(ctx, cCL);

        //XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(ctx);
        Class xmlReaderClass = Class.forName( "org.springframework.beans.factory.xml.XmlBeanDefinitionReader", true, cCL);
        Constructor[] cons = xmlReaderClass.getConstructors();
        Object xmlReader = null;
        for (Constructor con : cons) {
            Class[] types = con.getParameterTypes();
            if (types.length == 1 &&
                types[0].getName().contains("BeanDefinitionRegistry")) {
                xmlReader = con.newInstance(ctx);
                break;
            }
        }

        Method[] methods = xmlReaderClass.getMethods();
        Method xmlReader_loadBeanDefinitions = null;
        for (Method method : methods) {
            if (method.getName().equals("loadBeanDefinitions")) {
                Class[] types = method.getParameterTypes();
                for (Class type : types) {
                    if (type.getName().equals("org.springframework.core.io.Resource")) {
                        xmlReader_loadBeanDefinitions = method;
                        break;
                    }
                }
                if (xmlReader_loadBeanDefinitions != null)
                    break;
            }
        }

        String resourceClassName;
        if(useCodebase) {
            resourceClassName = "org.springframework.core.io.UrlResource";
        } else {
            resourceClassName = "org.springframework.core.io.ClassPathResource";
        }
        
        Class<?> resourceClass = Class.forName(resourceClassName, true, cCL);
        Constructor resourceCons = resourceClass.getConstructor(String.class);

        for (String config : configs) {
            Object resource = resourceCons.newInstance(config);
            assert xmlReader_loadBeanDefinitions != null;
            xmlReader_loadBeanDefinitions.invoke(xmlReader, resource);
        }

        /*
        for(int i=0; i<configs.length; i++) {
            xmlReader.loadBeanDefinitions(new UrlResource(configs[i]));
        }
        ctx.refresh();
        */

        Method ctx_refresh = ctxClass.getMethod("refresh", (Class[])null);
        ctx_refresh.invoke(ctx, (Object[])null);

        ServiceBeanManager mgr = context.getServiceBeanManager();
        if(mgr instanceof DefaultServiceBeanManager) {
            SpringDiscardManager sdm = new SpringDiscardManager(ctx, context.getServiceBeanManager().getDiscardManager());
            ((DefaultServiceBeanManager)mgr).setDiscardManager(sdm);
        } else {
            logger.warn("Unable to set Spring DiscardManager, unrecognized ServiceBeanManager");
        }
        String beanName = context.getServiceElement().getName();
        Method ctx_getBean = ctxClass.getMethod("getBean", String.class);
        Object bean = ctx_getBean.invoke(ctx, beanName);
        return(bean);

        //return(ctx.getBean(context.getServiceElement().getName()));

    }

    /*
     * Regular Expression Search and Replace
     */
    String replace(String str, String pattern, String replace) {
        int s = 0;
        int e;
        StringBuilder result = new StringBuilder();

        while((e = str.indexOf(pattern, s)) >= 0) {
            result.append(str.substring(s, e));
            result.append(replace);
            s = e+pattern.length();
        }
        result.append(str.substring(s));
        return result.toString();
    }
}
