/**
 * 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.jaxrs.spring;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collections;
import java.util.Map;

import javax.ws.rs.core.Application;

import org.apache.cxf.common.util.ClassHelper;
import org.apache.cxf.helpers.CastUtils;
import org.apache.cxf.jaxrs.lifecycle.ResourceProvider;
import org.apache.cxf.jaxrs.model.ProviderInfo;
import org.apache.cxf.jaxrs.utils.InjectionUtils;
import org.apache.cxf.jaxrs.utils.ResourceUtils;
import org.apache.cxf.message.Message;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

/**
 * The ResourceProvider implementation which delegates to 
 * ApplicationContext to manage the life-cycle of the resource
 */
public class SpringResourceFactory implements ResourceProvider, ApplicationContextAware {

    private Constructor<?> c;
    private Class<?> type;
    private ApplicationContext ac;
    private String beanId;
    private Method postConstructMethod;
    private Method preDestroyMethod;
    private boolean isSingleton;
    private boolean isPrototype;
    private boolean callPostConstruct;
    private boolean callPreDestroy = true;
    private String postConstructMethodName;
    private String preDestroyMethodName;
    private Object singletonInstance; 
    
    public SpringResourceFactory() {
        
    }
    
    public SpringResourceFactory(String name) {
        beanId = name;
    }
    
    private void init() {
        type = ClassHelper.getRealClassFromClass(ac.getType(beanId));
        if (Proxy.isProxyClass(type)) {
            type = ClassHelper.getRealClass(ac.getBean(beanId));
        }
        isSingleton = ac.isSingleton(beanId);
        postConstructMethod = ResourceUtils.findPostConstructMethod(type, postConstructMethodName);
        preDestroyMethod = ResourceUtils.findPreDestroyMethod(type, preDestroyMethodName);
        
        if (isSingleton()) {
            try {
                singletonInstance = ac.getBean(beanId);
            } catch (BeansException ex) {
                // ignore for now, try resolving resource constructor later
            }
            if (singletonInstance != null) {
                return;
            }
        } else {
            isPrototype = ac.isPrototype(beanId);
        }
        c = ResourceUtils.findResourceConstructor(type, !isSingleton());
        if (c == null) {
            throw new RuntimeException("Resource class " + type
                                       + " has no valid constructor");
        }
        
    }
    
    /**
     * {@inheritDoc}
     */
    public Object getInstance(Message m) {
        if (singletonInstance != null) {
            return singletonInstance;
        } else {
            ProviderInfo<?> application = m == null ? null
                : (ProviderInfo<?>)m.getExchange().getEndpoint().get(Application.class.getName());
            Map<Class<?>, Object> mapValues = CastUtils.cast(application == null ? null 
                : Collections.singletonMap(Application.class, application.getProvider()));
            Object[] values = ResourceUtils.createConstructorArguments(c, m, !isSingleton(), mapValues);
            Object instance = values.length > 0 ? ac.getBean(beanId, values) : ac.getBean(beanId);
            initInstance(m, instance);
            return instance;
        }
    }

    protected void initInstance(Message m, Object instance) {
        if (isCallPostConstruct()) {
            InjectionUtils.invokeLifeCycleMethod(ClassHelper.getRealObject(instance), postConstructMethod);
        }
    }

    
    
    /**
     * {@inheritDoc}
     */
    public boolean isSingleton() {
        return isSingleton; 
    }

    /**
     * {@inheritDoc}
     */
    public void releaseInstance(Message m, Object o) {
        if (doCallPreDestroy()) {
            InjectionUtils.invokeLifeCycleMethod(o, preDestroyMethod);
        }
    }
    
    protected boolean doCallPreDestroy() {
        return isCallPreDestroy() && isPrototype;
    }

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        ac = applicationContext;
        init();
    }

    public void setBeanId(String serviceBeanId) {
        this.beanId = serviceBeanId;
    }

    public ApplicationContext getApplicationContext() {
        return ac;    
    }
    
    Constructor<?> getBeanConstructor() {
        return c;
    }

    /**
     * {@inheritDoc}
     */
    public Class<?> getResourceClass() {
        return type;
    }

    public void setCallPostConstruct(boolean callPostConstruct) {
        this.callPostConstruct = callPostConstruct;
    }
    
    public boolean isCallPostConstruct() {
        return this.callPostConstruct;
    }

    public void setCallPreDestroy(boolean callPreDestroy) {
        this.callPreDestroy = callPreDestroy;
    }

    public boolean isCallPreDestroy() {
        return this.callPreDestroy;
    }
    
    public void setPreDestroyMethodName(String preDestroyMethodName) {
        this.preDestroyMethodName = preDestroyMethodName;
    }

    public void setPostConstructMethodName(String postConstructMethodName) {
        this.postConstructMethodName = postConstructMethodName;
    }
}
