/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2011 Bull S.A.S.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.web.jetty6;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;

import javax.annotation.Resource;
import javax.ejb.EJB;
import javax.naming.NamingException;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceUnit;
import javax.servlet.Filter;
import javax.servlet.Servlet;
import javax.xml.ws.WebServiceRef;

import org.mortbay.jetty.servlet.ServletHandler;
import org.ow2.util.ee.metadata.common.api.enc.IENCBindingHolder;
import org.ow2.util.ee.metadata.common.api.enc.IInjectionTargetEnc;

/**
 * This class manages the injection of EJB annotations. This classes is based on
 * the Jetty Plus ServletHandler and the Tomcat 6 DefaultAnnotationProcessor
 * enhanced in JOnAS.
 * @author Florent Benoit
 */
public class AnnotationProcessorServletHandler extends ServletHandler {

    /**
     * Env environment.
     */
    private javax.naming.Context context = null;

    /**
     * Annotation analysis is disabled or not ?
     */
    private boolean disabledAnnotation = false;

    /**
     * ENC Binding holder.
     */
    private IENCBindingHolder encBindingHolder = null;

    /**
     * Default constructor with the given ENC env.
     * @param context
     */
    public AnnotationProcessorServletHandler(final javax.naming.Context context) {
        this.context = context;
    }

    /**
     * Perform injection on the given filter.
     * @see org.mortbay.jetty.servlet.ServletHandler#customizeFilter(javax.servlet.Filter)
     */
    @Override
    public Filter customizeFilter(final Filter filter) throws Exception {
        processAnnotations(filter);
        return super.customizeFilter(filter);
    }

    /**
     * Perform injection on the given servlet.
     * @see org.mortbay.jetty.servlet.ServletHandler#customizeServlet(javax.servlet.Servlet)
     */
    @Override
    public Servlet customizeServlet(final Servlet servlet) throws Exception {
        processAnnotations(servlet);
        return super.customizeServlet(servlet);
    }

    /**
     * Inject resources in specified instance.
     */
    public void processAnnotations(final Object instance) throws IllegalAccessException, InvocationTargetException,
            NamingException {

        if (context == null) {
            // No resource injection
            return;
        }

        Class<?> clazz = instance.getClass();

        while (clazz != null) {
            // Initialize fields annotations
            Field[] fields = clazz.getDeclaredFields();

            for (int i = 0; i < fields.length; i++) {

                // Injection target
                if (encBindingHolder != null) {
                    List<IInjectionTargetEnc> fieldInjections = encBindingHolder.getFieldInjectionTargets(clazz.getName());
                    if (fieldInjections != null) {
                        for (IInjectionTargetEnc fieldInjection : fieldInjections) {
                            // found a matching field
                            if (fields[i].getName().equals(fieldInjection.getName())) {
                                lookupFieldResource(context, instance, fields[i], fieldInjection.getEncName(), clazz);
                            }
                        }
                    }
                }

                if (!disabledAnnotation) {
                    if (fields[i].isAnnotationPresent(Resource.class)) {
                        Resource annotation = fields[i].getAnnotation(Resource.class);
                        lookupFieldResource(context, instance, fields[i], annotation.name(), clazz);
                    }
                    if (fields[i].isAnnotationPresent(EJB.class)) {
                        EJB annotation = fields[i].getAnnotation(EJB.class);
                        lookupFieldResource(context, instance, fields[i], annotation.name(), clazz);
                    }
                    if (fields[i].isAnnotationPresent(WebServiceRef.class)) {
                        WebServiceRef annotation = fields[i].getAnnotation(WebServiceRef.class);
                        lookupFieldResource(context, instance, fields[i], annotation.name(), clazz);
                    }
                    if (fields[i].isAnnotationPresent(PersistenceContext.class)) {
                        PersistenceContext annotation = fields[i].getAnnotation(PersistenceContext.class);
                        lookupFieldResource(context, instance, fields[i], annotation.name(), clazz);
                    }
                    if (fields[i].isAnnotationPresent(PersistenceUnit.class)) {
                        PersistenceUnit annotation = fields[i].getAnnotation(PersistenceUnit.class);
                        lookupFieldResource(context, instance, fields[i], annotation.name(), clazz);
                    }

                    Annotation[] annotations = fields[i].getDeclaredAnnotations();
                    if (annotations != null) {
                        for (Annotation annotation : annotations) {
                            if ("org.ow2.easybeans.osgi.annotation.OSGiResource".equals(annotation.annotationType().getName())) {
                                lookupFieldResource(context, instance, fields[i], "BundleContext", clazz);
                            }
                        }
                    }

                }
            }

            // Initialize methods annotations
            Method[] methods = clazz.getDeclaredMethods();
            for (int i = 0; i < methods.length; i++) {

                // Injection target
                if (encBindingHolder != null) {
                    List<IInjectionTargetEnc> methoddInjections = encBindingHolder.getMethodInjectionTargets(clazz.getName());
                    if (methoddInjections != null) {
                        for (IInjectionTargetEnc methodInjection : methoddInjections) {
                            // found a matching field
                            if (methods[i].getName().equals(methodInjection.getName())) {
                                lookupMethodResource(context, instance, methods[i], methodInjection.getEncName(), clazz);
                            }
                        }
                    }
                }

                if (!disabledAnnotation) {
                    if (methods[i].isAnnotationPresent(Resource.class)) {
                        Resource annotation = methods[i].getAnnotation(Resource.class);
                        lookupMethodResource(context, instance, methods[i], annotation.name(), clazz);
                    }
                    if (methods[i].isAnnotationPresent(EJB.class)) {
                        EJB annotation = methods[i].getAnnotation(EJB.class);
                        lookupMethodResource(context, instance, methods[i], annotation.name(), clazz);
                    }
                    if (methods[i].isAnnotationPresent(WebServiceRef.class)) {
                        WebServiceRef annotation = methods[i].getAnnotation(WebServiceRef.class);
                        lookupMethodResource(context, instance, methods[i], annotation.name(), clazz);
                    }
                    if (methods[i].isAnnotationPresent(PersistenceContext.class)) {
                        PersistenceContext annotation = methods[i].getAnnotation(PersistenceContext.class);
                        lookupMethodResource(context, instance, methods[i], annotation.name(), clazz);
                    }
                    if (methods[i].isAnnotationPresent(PersistenceUnit.class)) {
                        PersistenceUnit annotation = methods[i].getAnnotation(PersistenceUnit.class);
                        lookupMethodResource(context, instance, methods[i], annotation.name(), clazz);
                    }
                    Annotation[] annotations = methods[i].getDeclaredAnnotations();
                    if (annotations != null) {
                        for (Annotation annotation : annotations) {
                            if ("org.ow2.easybeans.osgi.annotation.OSGiResource".equals(annotation.annotationType().getName())) {
                                lookupMethodResource(context, instance, methods[i], "BundleContext", clazz);
                            }
                        }
                    }
                }
            }

            clazz = clazz.getSuperclass();
        }
    }

    /**
     * Inject resources in specified field.
     * @param context
     * @param instance
     * @param field
     * @param name
     * @param clazz
     */
    protected static void lookupFieldResource(final javax.naming.Context context, final Object instance, final Field field,
            final String name, final Class<?> clazz) throws NamingException, IllegalAccessException {

        Object lookedupResource = null;
        boolean accessibility = false;

        if ((name != null) && (name.length() > 0)) {
            lookedupResource = context.lookup(name);
        } else {
            lookedupResource = context.lookup(clazz.getName() + "/" + field.getName());
        }

        accessibility = field.isAccessible();
        field.setAccessible(true);
        field.set(instance, lookedupResource);
        field.setAccessible(accessibility);
    }

    /**
     * Inject resources in specified method.
     * @param context
     * @param instance
     * @param method
     * @param name
     * @param clazz
     */
    protected static void lookupMethodResource(final javax.naming.Context context, final Object instance, final Method method,
            final String name, final Class<?> clazz) throws NamingException, IllegalAccessException, InvocationTargetException {

        if (!method.getName().startsWith("set") || method.getParameterTypes().length != 1
                || !method.getReturnType().getName().equals("void")) {
            throw new IllegalArgumentException("Invalid method resource injection annotation");
        }

        Object lookedupResource = null;
        boolean accessibility = false;

        if ((name != null) && (name.length() > 0)) {
            lookedupResource = context.lookup(name);
        } else {
            lookedupResource = context.lookup(clazz.getName() + "/" + method.getName().substring(3));
        }

        accessibility = method.isAccessible();
        method.setAccessible(true);
        method.invoke(instance, lookedupResource);
        method.setAccessible(accessibility);
    }

    /**
     * Sets the Enc Binding holder.
     * @param encBindingHolder
     */
    public void setEncBindingHolder(final IENCBindingHolder encBindingHolder) {
        this.encBindingHolder = encBindingHolder;
    }

    /**
     * @return ENC binding holder.
     */
    public IENCBindingHolder getEncBindingHolder() {
        return encBindingHolder;
    }

}
