/**
 * 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.jaxws.client.naming.factory;

import org.ow2.jonas.lib.util.JNDIUtils;
import org.ow2.jonas.ws.jaxws.client.naming.Constants;

import javax.naming.Context;
import javax.naming.Name;
import javax.naming.Reference;
import javax.naming.spi.ObjectFactory;
import javax.xml.namespace.QName;
import javax.xml.ws.Service;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.net.MalformedURLException;
import java.util.Hashtable;

/**
 * @author Guillaume Sauthier
 */
public abstract class ServiceObjectFactory implements ObjectFactory {
    /**
     * Creates an object using the location or reference information
     * specified.
     *
     * @param obj         The possibly null object containing location or reference
     *                    information that can be used in creating an object.
     * @param name        The name of this object relative to <code>nameCtx</code>,
     *                    or null if no name is specified.
     * @param nameCtx     The context relative to which the <code>name</code>
     *                    parameter is specified, or null if <code>name</code> is
     *                    relative to the default initial context.
     * @param environment The possibly null environment that is used in
     *                    creating the object.
     * @return The object created; null if an object cannot be created.
     * @throws Exception if this object factory encountered an exception
     *                   while attempting to create an object, and no other object factories are
     *                   to be tried.
     * @see javax.naming.spi.NamingManager#getObjectInstance
     * @see javax.naming.spi.NamingManager#getURLContext
     */
        public Object getObjectInstance(final Object obj,
                                        final Name name,
                                        final Context nameCtx,
                                        final Hashtable<?, ?> environment) throws Exception {

        if (!(obj instanceof Reference)) {
            // Do not support something that is not a Reference
            return null;
        }
        Reference ref = (Reference) obj;

        // Assume that the TCCL is a ClassLoader that can load everything we need
        ClassLoader loader = Thread.currentThread().getContextClassLoader();

        // Extract what we need from the Reference
        String serviceClassname = JNDIUtils.extractStringFrom(ref, Constants.SERVICE_CLASS);
        String seiClassname = JNDIUtils.extractStringFrom(ref, Constants.SERVICE_ENDPOINT_INTERFACE);
        String wsdlLocation = JNDIUtils.extractStringFrom(ref, Constants.WSDL_LOCATION);
        QName serviceName = JNDIUtils.extractObjectFrom(ref, Constants.SERVICE_QNAME, QName.class);

        // Load the class
        Class<? extends Service> serviceClass = null;
        serviceClass = Class.forName(serviceClassname, true, loader).asSubclass(Service.class);

        // Transform the WSDL location into an URL
        boolean haveWSDL = true;
        URL location = null;
        try {
            // Try if this is an absolute URL
            location = new URL(wsdlLocation);
        } catch (MalformedURLException mue) {
            // Was not absolute
            // Use the service class's classloader to look up the WSDL URL
            ClassLoader serviceLoader = serviceClass.getClassLoader();
            location = serviceLoader.getResource(wsdlLocation);

            if (location == null) {
                // OK last chance, try with the TCCL
                location = loader.getResource(wsdlLocation);
            }

            if (location == null) {
                // If it's still null at this point, remember it ...
                haveWSDL = false;
                // ... and let the JAX-WS Provider load it (JAX-WS 7.2.5)
            }

        }

        // Get the constructor and create the service instance
        Service service = null;
        if (haveWSDL) {
            // Use the constructor that can override the WSDL URL value
            Constructor<? extends Service> constructor = serviceClass.getConstructor(URL.class);
            service = constructor.newInstance(location);
        } else {
            // Use the constructor that use default values
            Constructor<? extends Service> constructor = serviceClass.getConstructor();
            service = constructor.newInstance();
        }

        // Allow specific ws providers to customize the
        // Service instance before returning it to the client
        prepareService(service, ref);

        // By default, return the service instance
        Object returnedObject = service;

        // Maybe the client requires the SEI, not the Service
        if (seiClassname != null) {
            ClassLoader serviceClassLoader = service.getClass().getClassLoader();
            Class<?> sei = serviceClassLoader.loadClass(seiClassname);
            returnedObject = service.getPort(sei);
        }

        // return to the client
        return returnedObject;
    }

    /**
     * Prepare the service before returning it to the client.
     * This is basically a hook for WS provider specific tasks to do.
     * Like setup HandlerChains, ...
     * Warning, Exceptions handling is like what is done for JNDI
     * ObjectFactory: if an Exception is thrown, no other ObjectFactories
     * are tried and the object will never been build, even if other registered
     * factories may have been able to build an Object from the Reference.
     * So throw Exception with care.
     * @param service Service to be customized
     * @param ref the Reference used for object creation
     * @throws Exception if preparation cannot be performed.
     */
    protected abstract void prepareService(final Service service,
                                           final Reference ref) throws Exception;

}
