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

import static org.ow2.jonas.lib.util.Log.setComponentLogLevel;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import org.apache.cxf.Bus;
import org.apache.cxf.BusException;
import org.apache.cxf.BusFactory;
import org.apache.cxf.binding.soap.SoapTransportFactory;
import org.apache.cxf.bus.managers.DestinationFactoryManagerImpl;
import org.apache.cxf.transport.DestinationFactory;
import org.apache.cxf.transport.DestinationFactoryManager;
import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Level;
import org.ow2.jonas.ws.cxf.http.JOnASHTTPTransportFactory;
import org.ow2.jonas.ws.jaxws.IJAXWSService;
import org.ow2.util.execution.ExecutionResult;
import org.ow2.util.execution.IExecution;
import org.ow2.util.execution.helper.RunnableHelper;
import org.ow2.util.log.Log;
import org.ow2.util.log.LogFactory;

/**
 * Manage all the code required to start a new CXF Bus.
 * @author Guillaume Sauthier
 */
public class BusCreator extends BusFactory {

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

    /**
     * JAX-WS Service (used to retrieve handlers).
     */
    private IJAXWSService service;


    /**
     * Initialize Internal CXF Bus.
     * @return a new fully initialized CXF Bus
     */
    @Override
    public Bus createBus() {

        // Create a new Bus
        // Creation mechanism use the TCCL to access default Bus configuration
        IExecution<Bus> createBusJob = new IExecution<Bus>() {

            public Bus execute() throws Exception {

                // Bus start/stop operations are extra verbose
                Level old = setComponentLogLevel("org.apache.cxf", BasicLevel.LEVEL_ERROR);
                Level old2 = setComponentLogLevel("org.springframework", BasicLevel.LEVEL_ERROR);

                Bus newBus;

                // TODO: Remove these lines when issue will be closed
                // Avoid to get e.printStackTrace from CatalogManager class by catching error stream
                PrintStream previousErrorStream = System.err;
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                PrintStream tempStream = new PrintStream(baos);
                System.setErr(tempStream);

                // Get the current associated Bus (if any, may be null)
                Bus oldThreadBus = BusFactory.getThreadDefaultBus(false);

                try {
                    newBus =  BusFactory.newInstance().createBus();

                    // Store the Endpoint Audit Handler
                    newBus.setProperty(BusCreator.class.getName() + ".ENDPOINT", service.getEndpointAuditHandler());
                } finally {

                    // Dissociate the Bus from the Thread
                    BusFactory.setThreadDefaultBus(oldThreadBus);

                    System.setErr(previousErrorStream);

                    // Check if there was an UnknownHostException
                    String errorMessage = baos.toString();
                    if (errorMessage != null && errorMessage.contains("java.net.UnknownHostException")) {
                        // debug level
                        logger.debug("Error while initializing the Bus : ''{0}'' ", errorMessage);
                    } else if (!"".equals(errorMessage)) {
                        // error level
                        logger.error("Error while initializing the Bus : ''{0}'' ", errorMessage);
                    }

                    // Reset log levels
                    setComponentLogLevel("org.apache.cxf", old);
                    setComponentLogLevel("org.springframework", old2);
                }

                return newBus;
            }

        };

        ExecutionResult<Bus> res = new RunnableHelper<Bus>().execute(Bus.class.getClassLoader(),
                                                                     createBusJob);

        if (res.hasException()) {
            logger.error("Cannot create Bus", res.getException());
        }

        Bus bus = res.getResult();

        try {
            createDestinationFactory(bus);
        } catch (BusException e) {
            logger.error("Cannot create destination factory", e);
        }

        return bus;

    }

    /**
     * Create the EasyBeans destination factory and register the transports.
     * @throws BusException if the factory is not created.
     * @param bus Bus to be configured
     */
    private void createDestinationFactory(final Bus bus) throws BusException {

        JOnASHTTPTransportFactory factory = new JOnASHTTPTransportFactory(bus);

        // transportIds done by Spring ?
        List<String> namespaces = new ArrayList<String>();
        // SOAP11 over HTTP
        namespaces.add("http://schemas.xmlsoap.org/wsdl/soap/http");
        namespaces.add("http://schemas.xmlsoap.org/soap/http");
        namespaces.add("http://schemas.xmlsoap.org/wsdl/http/");


        // SOAP12 over HTTP
        namespaces.add("http://www.w3.org/2003/05/soap/bindings/HTTP/");

        namespaces.add("http://cxf.apache.org/transports/http");
        namespaces.add("http://cxf.apache.org/transports/http/configuration");
        namespaces.add("http://cxf.apache.org/bindings/xformat");
        factory.setTransportIds(namespaces);

        for (String ns : namespaces) {
            registerTransport(bus, factory, ns);
        }

        // Adds Soap factory
        SoapTransportFactory soapTransportFactory = new SoapTransportFactory();
        soapTransportFactory.setBus(bus);

        // Needs to tell that some NS have no factory (and then no need to create jetty http transport factory for it)
        Object destinationFactoryManager = bus.getExtension(DestinationFactoryManager.class);
        if (destinationFactoryManager instanceof DestinationFactoryManagerImpl) {
            DestinationFactoryManagerImpl destinationFactoryManagerImpl = (DestinationFactoryManagerImpl) destinationFactoryManager;
            Field failedField = null;
            try {
                failedField = DestinationFactoryManagerImpl.class.getDeclaredField("failed");
                failedField.setAccessible(true);
                Set<String> failed = (Set<String>) failedField.get(destinationFactoryManagerImpl);
                failed.add("http://java.sun.com/xml/ns/jaxws");
            } catch (SecurityException e) {
                throw new IllegalStateException("Cannot get failed field", e);
            } catch (NoSuchFieldException e) {
                throw new IllegalStateException("Cannot get failed field", e);
            } catch (IllegalArgumentException e) {
                throw new IllegalStateException("Cannot get failed field", e);
            } catch (IllegalAccessException e) {
                throw new IllegalStateException("Cannot get failed field", e);
            }
        }




    }

    /**
     * Utility method to register {@link org.apache.cxf.transport.DestinationFactory} under a namespace.
     * @param bus Bus to be configured
     * @param factory the {@link org.apache.cxf.transport.DestinationFactory} instance
     * @param namespace the key
     * @throws BusException if registration fails.
     */
    private void registerTransport(final Bus bus,
                                   final DestinationFactory factory,
                                   final String namespace) throws BusException {

        DestinationFactoryManager manager = bus.getExtension(DestinationFactoryManager.class);
        manager.registerDestinationFactory(namespace, factory);
    }

    /**
     * Inject the service reference.
     * @param service Jaxws service
     */
    public void bindJaxwsService(final IJAXWSService service) {
        this.service = service;
    }
}
