/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2009 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 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */



package org.ow2.jonas.ws.jaxws.handler.builder;


import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;

import javax.xml.ws.WebServiceException;
import javax.xml.ws.handler.Handler;

import org.ow2.jonas.ws.jaxws.handler.HandlerResourceProcessor;
import org.ow2.util.annotation.processor.DefaultAnnotationProcessor;
import org.ow2.util.annotation.processor.IAnnotationProcessor;
import org.ow2.util.annotation.processor.ProcessorException;
import org.ow2.util.ee.metadata.common.api.xml.struct.IHandler;

/**
 * The HandlerBuilder is responsible of building Handler instances from
 * handler's XML descriptions.
 *
 * @author Guillaume Sauthier
 */
public class HandlerBuilder {

    /**
     * Handler description to be transformed into a real Handler instance.
     */
    private IHandler handlerDesc;

    /**
     * Classloader to be used for Handler class loading.
     */
    private ClassLoader classloader;

    /**
     * Constructs a new HandlerBuilder.
     * @param pcHandler the handler desc
     * @param loader classloader to be used for loading
     */
    public HandlerBuilder(final IHandler pcHandler, final ClassLoader loader) {
        this.handlerDesc = pcHandler;
        this.classloader = loader;
    }

    /**
     * Build a handler and init it.
     * @return an inited Handler
     */
    public Handler buildHandler() {

        String classname = handlerDesc.getHandlerClass();

        Handler handler;
        try {
            Class<? extends Handler> clazz = Class.forName(classname, true, classloader).asSubclass(Handler.class);
            handler = clazz.newInstance();
        } catch (ClassNotFoundException e) {
            throw new WebServiceException("Cannot load class '" + classname + "' from loader '" + classloader + "'", e);
        } catch (IllegalAccessException e) {
            throw new WebServiceException("Cannot call default constructor of class '" + classname + "'", e);
        } catch (InstantiationException e) {
            throw new WebServiceException("Cannot instanciate class '" + classname + "'", e);
        }

        // Init the handler
        initHandler(handler, handlerDesc.getInitParams());

        return handler;
    }

    /**
     * Initialize the newly created handler.
     * @param handler to be inited instance
     * @param initParams init params from the descriptor
     */
    private void initHandler(final Handler handler, final Map<String, String> initParams) {

        // Initialize Handler
        Method initMethod = findInitMethod(handler.getClass());
        if (initMethod != null) {
            initHandlerUsingMethod(handler, initMethod, initParams);
        } else {
            initHandlerUsingResource(handler, initParams);
        }

    }

    /**
     * init the Handler using @Resource injection resolved by the init params.
     * @param handler inited instance
     * @param initParams Map of init parameters
     */
    private void initHandlerUsingResource(final Handler handler, final Map<String, String> initParams) {

        // Create an annotation processor
        IAnnotationProcessor processor = createAnnotationProcessor(initParams);

        try {
            // perform injection
            processor.process(handler);
        } catch (ProcessorException e) {
            throw new WebServiceException("Cannot initialise the handler '" + handler + "'", e);
        }
    }

    /**
     * Creates a new configured AnnotationProcessor for init param injection.
     * @param initParams parameters
     * @return a configured AnnotationProcessor
     */
    private IAnnotationProcessor createAnnotationProcessor(final Map<String, String> initParams) {
        IAnnotationProcessor processor = new DefaultAnnotationProcessor();
        processor.addAnnotationHandler(new HandlerResourceProcessor(initParams));
        // TODO inject other types (WebServiceContext, ...)
        return processor;
    }

    /**
     * Init the handler using the init(Map) method.
     * @param handler inited instance
     * @param initMethod method to be called
     * @param initParams init parameters
     */
    private void initHandlerUsingMethod(final Handler handler, final Method initMethod, final Map<String, String> initParams) {

        boolean access = initMethod.isAccessible();
        try {
            initMethod.setAccessible(true);
            initMethod.invoke(handler, initParams);
        } catch (IllegalAccessException e) {
            throw new WebServiceException("Cannot use method '" + initMethod + "'", e);
        } catch (InvocationTargetException e) {
            throw new WebServiceException("Exception during '" + initMethod + "' invocation", e);
        } finally {
            initMethod.setAccessible(access);
        }
    }

    /**
     * Find the init(Map) method (if any).
     * @param clazz inspected class
     * @return the Method of the init(Map) or null if none was found
     */
    private Method findInitMethod(final Class<? extends Handler> clazz) {

        try {
            return clazz.getMethod("init", Map.class);
        } catch (NoSuchMethodException e) {
            // No init method: that's OK, continue
            return null;
        }
    }
}
