/**
 * 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: JOnASInjectionDecorator.java 21566 2011-08-08 12:28:12Z cazauxj $
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.web.jetty8;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.EventListener;
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.servlet.ServletException;
import javax.xml.ws.WebServiceRef;

import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlet.ServletContextHandler.Decorator;
import org.ow2.util.ee.metadata.common.api.enc.IENCBindingHolder;
import org.ow2.util.ee.metadata.common.api.enc.IInjectionTargetEnc;

/**
 * This class provides injection of all Java EE stuff into the servlet, filter,
 * listener instances.
 * @author Florent Benoit
 */

public class JOnASInjectionDecorator implements Decorator {

    /**
     * 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 the given ENC
     */
    public JOnASInjectionDecorator(final javax.naming.Context context) {
        this.context = context;
    }

    /**
     * @param filter the given filter
     * @throws ServletException
     * @see org.eclipse.jetty.servlet.ServletContextHandler.Decorator#decorateFilterHolder(org.eclipse.jetty.servlet.FilterHolder)
     */
    public void decorateFilterHolder(final FilterHolder filter) throws ServletException {
    }

    /**
     * @param <T>
     * @param filter
     * @return the decorated filter
     * @throws ServletException
     * @see org.eclipse.jetty.servlet.ServletContextHandler.Decorator#decorateFilterInstance(javax.servlet.Filter)
     */
    public <T extends Filter> T decorateFilterInstance(final T filter) throws ServletException {
        processAnnotations(filter);
        return filter;
    }

    /**
     * @param <T>
     * @param listener
     * @return the decorated event listener instance
     * @throws ServletException
     * @see org.eclipse.jetty.servlet.ServletContextHandler.Decorator#decorateListenerInstance(java.util.EventListener)
     */
    public <T extends EventListener> T decorateListenerInstance(final T listener) throws ServletException {
        processAnnotations(listener);
        return listener;
    }

    /* ------------------------------------------------------------ */
    /**
     * @param servlet
     * @throws ServletException
     * @see org.eclipse.jetty.servlet.ServletContextHandler.Decorator#decorateServletHolder(org.eclipse.jetty.servlet.ServletHolder)
     */
    public void decorateServletHolder(final ServletHolder servlet) throws ServletException {
    }

    /* ------------------------------------------------------------ */
    /**
     * @param <T>
     * @param servlet
     * @return the decorated servlet instance
     * @throws ServletException
     * @see org.eclipse.jetty.servlet.ServletContextHandler.Decorator#decorateServletInstance(javax.servlet.Servlet)
     */
    public <T extends Servlet> T decorateServletInstance(final T servlet) throws ServletException {
        processAnnotations(servlet);
        return servlet;
    }

    /* ------------------------------------------------------------ */
    /**
     * @param f
     * @see org.eclipse.jetty.servlet.ServletContextHandler.Decorator#destroyFilterInstance(javax.servlet.Filter)
     */
    public void destroyFilterInstance(final Filter f) {
    }

    /* ------------------------------------------------------------ */
    /**
     * @param s
     * @see org.eclipse.jetty.servlet.ServletContextHandler.Decorator#destroyServletInstance(javax.servlet.Servlet)
     */
    public void destroyServletInstance(final Servlet s) {
    }

    /* ------------------------------------------------------------ */
    /**
     * @param f
     * @see org.eclipse.jetty.servlet.ServletContextHandler.Decorator#destroyListenerInstance(java.util.EventListener)
     */
    public void destroyListenerInstance(final EventListener f) {
    }

    /**
     * Inject resources in specified instance.
     */
    public void processAnnotations(final Object instance) throws ServletException {

        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 ServletException {

        Object lookedupResource = null;
        boolean accessibility = false;

        if ((name != null) && (name.length() > 0)) {
            try {
                lookedupResource = context.lookup(name);
            } catch (NamingException e) {
                throw new ServletException("Unable to find the given name '" + name + "'.", e);
            }
        } else {
            try {
                lookedupResource = context.lookup(clazz.getName() + "/" + field.getName());
            } catch (NamingException e) {
                throw new ServletException("Unable to find the given name '" + clazz.getName() + "/" + field.getName() + "'.",
                        e);

            }
        }

        accessibility = field.isAccessible();
        field.setAccessible(true);
        try {
            field.set(instance, lookedupResource);
        } catch (IllegalArgumentException e) {
            throw new ServletException("Unable to set the field instance.", e);
        } catch (IllegalAccessException e) {
            throw new ServletException("Unable to set the field instance.", e);
        }
        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 ServletException {

        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)) {
            try {
                lookedupResource = context.lookup(name);
            } catch (NamingException e) {
                throw new ServletException("Unable to find the given name '" + name + "'.", e);
            }
        } else {
            try {
                lookedupResource = context.lookup(clazz.getName() + "/" + method.getName().substring(3));
            } catch (NamingException e) {
                throw new ServletException("Unable to find the given name '" + clazz.getName() + "/"
                        + method.getName().substring(3) + "'.", e);
            }
        }

        accessibility = method.isAccessible();
        method.setAccessible(true);
        try {
            method.invoke(instance, lookedupResource);
        } catch (IllegalArgumentException e) {
            throw new ServletException("Unable to call the method instance.", e);
        } catch (IllegalAccessException e) {
            throw new ServletException("Unable to call the method instance.", e);
        } catch (InvocationTargetException e) {
            throw new ServletException("Unable to call the method instance.", e);
        }
        method.setAccessible(accessibility);
    }

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

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

}
