package org.apache.james.protocols.lib.mock;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;

import org.apache.commons.configuration.Configuration;
import org.apache.james.protocols.api.handler.ProtocolHandler;
import org.apache.james.protocols.lib.handler.ProtocolHandlerLoader;

public class MockProtocolHandlerLoader implements ProtocolHandlerLoader{

    @Override
    public ProtocolHandler load(String name, Configuration config) throws LoadingException {
        try {
            ProtocolHandler obj = create(name);
            injectResources(obj);
            postConstruct(obj);
            synchronized (this) {
                loaderRegistry.add(obj);
            }
            return obj;
        } catch (Exception e) {
            throw new LoadingException("Unable to load protocolhandler", e);
        }
    }

    private final Map<String, Object> servicesByName = new HashMap<String, Object>();
    public Object get(String name) {
        Object service = servicesByName.get(name);
        return service;
    }

    public void put(String role, Object service) {
        servicesByName.put(role, service);
    }

    private List<Object> loaderRegistry = new ArrayList<Object>();

    protected ProtocolHandler create(String className) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        return (ProtocolHandler) Thread.currentThread().getContextClassLoader().loadClass(className).newInstance();
    }

    /**
     * Dispose all loaded instances by calling the method of the instances which
     * is annotated with @PreDestroy
     */
    public synchronized void dispose() {
        for (int i = 0; i < loaderRegistry.size(); i++) {
            try {
                preDestroy(loaderRegistry.get(i));
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
        loaderRegistry.clear();
    }

    private void postConstruct(Object resource) throws IllegalAccessException, InvocationTargetException {
        Method[] methods = resource.getClass().getMethods();
        for (Method method : methods) {
            PostConstruct postConstructAnnotation = method.getAnnotation(PostConstruct.class);
            if (postConstructAnnotation != null) {
                Object[] args = {};
                method.invoke(resource, args);

            }
        }
    }

    private void preDestroy(Object resource) throws IllegalAccessException, InvocationTargetException {
        Method[] methods = resource.getClass().getMethods();
        for (Method method : methods) {
            PreDestroy preDestroyAnnotation = method.getAnnotation(PreDestroy.class);
            if (preDestroyAnnotation != null) {
                Object[] args = {};
                method.invoke(resource, args);

            }
        }
    }

    private void injectResources(Object resource) {
        final Method[] methods = resource.getClass().getMethods();
        for (Method method : methods) {
            final Resource resourceAnnotation = method.getAnnotation(Resource.class);
            if (resourceAnnotation != null) {
                final String name = resourceAnnotation.name();
                if (name == null) {
                    throw new UnsupportedOperationException("Resource annotation without name specified is not supported by this implementation");
                } else {
                    // Name indicates a service
                    final Object service = getObjectForName(name);

                    if (service == null) {
                        throw new RuntimeException("Injection failed for object " + resource + " on method " + method + " with resource name " + name + ", because no mapping was found");
                    } else {
                        try {
                            Object[] args = { service };
                            method.invoke(resource, args);
                        } catch (IllegalAccessException e) {
                            throw new RuntimeException("Injection failed for object " + resource + " on method " + method + " with resource " + service, e);
                        } catch (IllegalArgumentException e) {
                            throw new RuntimeException("Injection failed for object " + resource + " on method " + method + " with resource " + service, e);
                        } catch (InvocationTargetException e) {
                            throw new RuntimeException("Injection failed for object " + resource + " on method " + method + " with resource " + service, e);
                        }
                    }
                }
            }
        }
    }

    public Object getObjectForName(String name) {
        return get(name);
    }

}

@@ -39,16 +39,56 @@
 .mock;%0A%0A
+import java.lang.annotation.Annotation;%0A
 import j
@@ -356,27 +356,48 @@
 vax.
-annotation.Resource
+inject.Inject;%0Aimport javax.inject.Named
 ;%0A%0Ai
@@ -3407,25 +3407,21 @@
 nal 
-Resource resource
+Inject inject
 Anno
@@ -3450,24 +3450,22 @@
 otation(
-Resource
+Inject
 .class);
@@ -3481,24 +3481,22 @@
     if (
-resource
+inject
 Annotati
@@ -3529,54 +3529,455 @@
     
-final String name = resourceAnnotation.name();
+String name = null;%0A                Annotation%5B%5D%5B%5D paramAnnotations = method.getParameterAnnotations();%0A                if (paramAnnotations.length == 1) %7B%0A                    if (paramAnnotations%5B0%5D.length ==1) %7B%0A                        if (paramAnnotations%5B0%5D%5B0%5D.annotationType().equals(Named.class)) %7B%0A                            name = ((Named) paramAnnotations%5B0%5D%5B0%5D).value();%0A                        %7D%0A                    %7D%0A                %7D
 %0A   
@@ -4070,24 +4070,23 @@
 eption(%22
-Resource
+@Inject
  annotat
@@ -4097,20 +4097,22 @@
 without 
-n
+@N
 ame
+d
  specifi
@@ -4297,17 +4297,16 @@
 (name);%0A
-%0A
         

Fix POP3 failing unit tests due to @Inject usage instead of @Resource (JAMES-1477)