/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2008 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.cxf.easybeans.naming;

import org.ow2.easybeans.api.Factory;
import static org.ow2.easybeans.deployment.helper.JavaContextHelper.getJndiName;
import org.ow2.easybeans.deployment.metadata.ejbjar.EasyBeansEjbJarClassMetadata;
import org.ow2.easybeans.deployment.metadata.ejbjar.EasyBeansEjbJarFieldMetadata;
import org.ow2.easybeans.deployment.metadata.ejbjar.EasyBeansEjbJarMethodMetadata;
import static org.ow2.easybeans.deployment.util.BytecodeDescriptorHelper.getClassname;
import static org.ow2.easybeans.deployment.util.BytecodeDescriptorHelper.getMethodParamClassname;
import org.ow2.easybeans.event.naming.EnvNamingEvent;
import org.ow2.util.ee.builder.webserviceref.WebServiceRefBuilder;
import org.ow2.util.ee.metadata.common.api.struct.IJaxwsWebServiceRef;
import org.ow2.util.event.api.EventPriority;
import org.ow2.util.event.api.IEvent;
import org.ow2.util.event.api.IEventListener;
import org.ow2.util.log.Log;
import org.ow2.util.log.LogFactory;
import org.ow2.jonas.ws.cxf.client.CXFWebServiceRefBuilder;

import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.Reference;
import java.util.List;

/**
 * This extension is responsible of filling the EJB java:comp/env Context with @WebServiceRef.
 * @author Guillaume Sauthier
 */
public class WebServiceRefExtensionListener implements IEventListener {

    /**
     * Logger.
     */
    private static Log logger = LogFactory.getLog(WebServiceRefExtensionListener.class);

    /**
     * Only accepts the event if it's a EnvNamingEvent.
     * @param event tested event
     * @return <code>true</code> if the proposed event is of the expected type
     */
    public boolean accept(final IEvent event) {
        if (event instanceof EnvNamingEvent) {
            EnvNamingEvent namingEvent = (EnvNamingEvent) event;

            // source/event-provider-id attribute is used to filter the destination
            if ("comp/env".equals(namingEvent.getEventProviderId())) {
                return true;
            }
        }
        return false;
    }

    /**
     * @return Normal synchronous priority
     */
    public EventPriority getPriority() {
        return EventPriority.SYNC_NORM;
    }

    /**
     * Process the event.
     * @param event
     */
    public void handle(final IEvent event) {

        EnvNamingEvent ene = (EnvNamingEvent) event;
        EasyBeansEjbJarClassMetadata bean = ene.getBeanMetadata();
        Factory<?, ?> easyBeansFactory = ene.getFactory();
        Context envCtx = ene.getContext();

        logger.debug("Handling 'java:comp/env' building for EJB {0}", easyBeansFactory.getBeanInfo().getName());

        // Handle @WebServiceRefs
        // Class level -----------------------
        IJaxwsWebServiceRef webServiceRefOnClass = bean.getJaxwsWebServiceRef();
        if (webServiceRefOnClass != null) {
            // JNDI name must be set
            String name = webServiceRefOnClass.getName();
            createAndBindWebServiceReference(easyBeansFactory, envCtx, webServiceRefOnClass, name, ene);
        }
        List<IJaxwsWebServiceRef> refs = bean.getJaxwsWebServiceRefs();
        if (refs != null && refs.size() > 0) {
            for (IJaxwsWebServiceRef webServiceRef : refs) {
                String name = webServiceRef.getName();
                createAndBindWebServiceReference(easyBeansFactory, envCtx, webServiceRef, name, ene);
            }
        }

        // Field level -----------------------
        for (EasyBeansEjbJarFieldMetadata fieldMetadata : bean.getStandardFieldMetadataCollection()) {
            IJaxwsWebServiceRef webServiceRef = fieldMetadata.getJaxwsWebServiceRef();
            if (webServiceRef != null) {

                // Compute JNDI name from field info
                String refName = webServiceRef.getName();
                String name = getJndiName(refName, fieldMetadata);

                // Fix type
                String type = getClassname(fieldMetadata.getJField().getDescriptor());
                webServiceRef.setType(type);

                // Rebind
                createAndBindWebServiceReference(easyBeansFactory, envCtx, webServiceRef, name, ene);
            }
        }
        // Method level -----------------------
        for (EasyBeansEjbJarMethodMetadata methodMetadata : bean.getMethodMetadataCollection()) {
            IJaxwsWebServiceRef webServiceRef = methodMetadata.getJaxwsWebServiceRef();
            if (webServiceRef != null) {

                // Compute JNDI name from method info
                String refName = webServiceRef.getName();
                String name = getJndiName(refName, methodMetadata);

                // Fix type
                String type = getMethodParamClassname(methodMetadata.getJMethod().getDescriptor(), 0);
                webServiceRef.setType(type);

                // Rebind
                createAndBindWebServiceReference(easyBeansFactory, envCtx, webServiceRef, name, ene);
            }
        }
    }


    /**
     * Create a Reference from the metadata and then bind it in the 'env' Context.
     * @param easyBeansFactory the EJB Factory
     * @param envCtx the <code>java:comp/env</code> Context
     * @param ref the @WebServiceRef metadata
     * @param name JNDI name
     */
    private void createAndBindWebServiceReference(final Factory<?, ?> easyBeansFactory,
                                                  final Context envCtx,
                                                  final IJaxwsWebServiceRef ref,
                                                  final String name,
                                                  final EnvNamingEvent event) {
        try {
            // Create the Reference object
            Reference wsRef = getWebServiceReference(easyBeansFactory, ref);

            // Rebind
            if (wsRef != null) {
                logger.debug("Rebind ''java:comp/env/{0}''", name);
                envCtx.rebind(name, wsRef);
            }
        } catch (NamingException e) {
            throwException(event, new IllegalStateException("Cannot bind element '" + name + "'.", e));
        }
    }

    /**
     * Creates a JNDI Reference object from the given metadata.
     * @param easyBeansFactory the factory used to retrieve extensions
     * @param webServiceRef metadata
     * @return a newly created Reference or null.
     */
    private Reference getWebServiceReference(final Factory<?,?> easyBeansFactory,
                                             final IJaxwsWebServiceRef webServiceRef)
            throws NamingException {

        // TODO Should use the Factory and the Extensor model of the EZBContainer to get a builder instance
        // Without this, Axis2 will have to copy this whole class if it wants to provides it's own builder.
        WebServiceRefBuilder builder = new CXFWebServiceRefBuilder();

        Reference ref = null;
        if (builder != null) {
            ref = builder.build(webServiceRef);
        }

        return ref;
    }

    /**
     * Add the Exception in the event for a possible feedback for the caller.
     * @param event the event to be completed
     * @param throwable the exception to be added and then rethrown
     */
    private static void throwException(final EnvNamingEvent event,
                                       final Throwable throwable) {

        // Log a message
        logger.error("Errors during Listeners processing.", throwable);

        // Append the exception in the event
        event.addThrowable(throwable);

        // Rethrow the exception to break the execution flow
        if (throwable instanceof RuntimeException) {
            throw (RuntimeException) throwable;
        } else {
            throw new RuntimeException("Wrapping cause", throwable);
        }
    }

}
