/*
 * JBoss, Home of Professional Open Source
 * Copyright 2007, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags.
 * See the copyright.txt in the distribution for a full listing
 * of individual contributors.
 * This copyrighted material is made available to anyone wishing to use,
 * modify, copy, or redistribute it subject to the terms and conditions
 * of the GNU General Public License, v. 2.0.
 * This program is distributed in the hope that it will be useful, but WITHOUT A
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE.  See the GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License,
 * v. 2.0 along with this distribution; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA  02110-1301, USA.
 *
 * (C) 2007,
 * @author JBoss Inc.
 */
package org.jboss.txbridge.ba.execute;

import org.apache.log4j.Logger;

import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.naming.Context;
import javax.rmi.PortableRemoteObject;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Properties;

/**
 * The Service Locator component is responsible for locating EJBs. It uses a caching mechanism
 * so that if an EJB has been previously searched for a cached reference can be returned.
 *
 * Based on: http://java.sun.com/blueprints/patterns/ServiceLocator.html
 *
 * @author Maciej P. Machulak (mmachulak@redhat.com)
 * @version 0.1
 */
public class ServiceLocator
{
    // Logger
    private static Logger log = Logger.getLogger(ServiceLocator.class);

    // Initial context
    private InitialContext initialContext;

    // Cache
    private ConcurrentMap<String,Object> beanCache;

    // Local service locator instance
    private static ServiceLocator localServiceLocator;

    // Service locator instances
    private static ConcurrentMap<String,ServiceLocator> serviceLocators = new ConcurrentHashMap<String,ServiceLocator>();

    /**
     * Returns the instance of the Service Locator object.
     *
     * @param providerURL is the Provider URL
     * @return the instance of the Service Locator object.
     * @throws ServiceLocatorException if it was not possible to create the service locator.
     */
    public static ServiceLocator getInstance(String providerURL)
            throws ServiceLocatorException
    {
        log.info("getSingletonInstance()");
        if (providerURL == null || providerURL.equals(""))
        {
            if (localServiceLocator == null)
            {
                synchronized (ServiceLocator.class)
                {
                    if (localServiceLocator == null)
                    {
                        log.info("Creating new instance...");
                        try
                        {
                            localServiceLocator = new ServiceLocator();
                        }
                        catch (Exception e)
                        {
                            log.info("Could not create service locator instance");
                            throw new ServiceLocatorException(e);
                        }
                    }
                }
            }
            return localServiceLocator;
        }
        else
        {
            ServiceLocator serviceLocator = serviceLocators.get(providerURL);
            if (serviceLocator == null)
            {
                synchronized (ServiceLocator.class)
                {
                    if (serviceLocator == null)
                    {
                        log.info("Creating new instance...");
                        try
                        {
                            serviceLocator = new ServiceLocator(providerURL);
                            serviceLocators.put(providerURL,serviceLocator);
                        }
                        catch (Exception e)
                        {
                            log.info("Could not create service locator instance");
                            throw new ServiceLocatorException(e);
                        }
                    }
                }
            }
            return serviceLocator;
        }
    }

    /**
     * Constructor
     *
     * @throws NamingException if there was a naming error.
     */
    private ServiceLocator() throws NamingException
    {
        log.info("constructor()");
        beanCache = new ConcurrentHashMap<String,Object>();
        Properties p = new Properties();
        p.put(Context.INITIAL_CONTEXT_FACTORY,"org.jnp.interfaces.NamingContextFactory");
        p.put(Context.URL_PKG_PREFIXES,"org.jboss.naming:org.jnp.interfaces");
        initialContext = new InitialContext(p);

    }

    /**
     * Constructor
     *
     * @param providerURL is the URL of the provider.
     * @throws NamingException if there was a naming error.
     */
    private ServiceLocator(String providerURL) throws NamingException
    {
        log.info("constructor()");
        log.info("Provider URL: " + providerURL);
        beanCache = new ConcurrentHashMap<String,Object>();
        Properties p = new Properties();
        p.put(Context.INITIAL_CONTEXT_FACTORY,"org.jnp.interfaces.NamingContextFactory");
        p.put(Context.URL_PKG_PREFIXES,"org.jboss.naming:org.jnp.interfaces");
        p.put(Context.PROVIDER_URL,providerURL);
        initialContext = new InitialContext(p);

    }

    /**
     * This method returns a reference to the interface of an EJB.
     *
     * @param jndiName is the name of the bean.
     * @param interfaceName is the class of the remote interface.
     * @return an object representing an interface of a bean.
     * @throws ServiceLocatorException if there was an error with the lookup.
     */
    public Object getInterface(String jndiName, Class interfaceName)
            throws ServiceLocatorException
    {
        log.info("getInterface()");

        // Check if the bean is cached
        log.info("Checking if the bean is cached");
        if (beanCache.containsKey(jndiName))
        {
            log.info("Bean cached - returning");
            return beanCache.get(jndiName);
        }

        // If not - perform the lookup, remember the reference
        log.info("Bean is not cached - performing the lookup");
        try
        {
            Object ejbInterface = initialContext.lookup(jndiName);
            Object theBean = PortableRemoteObject.narrow(ejbInterface,interfaceName);
            log.info("Lookup successful - caching reference");
            beanCache.put(jndiName,theBean);
            return theBean;
        }
        catch (Exception e)
        {
            throw new ServiceLocatorException(e);
        }
    }

}

