/*
 * Copyright 2010 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.kie.api.internal.utils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;

/**
 * Maintains a mapping of service names to an ordered set of service
 * providers when running in an OSGi container.
 * <br>
 * It is expected that a bundle using Drools will populate this map
 * with properties from its own ClassLoader.
 * <br>
 * This is an adaptation of the technique described by Guillaume Nodet
 * in his article<i>
 * <a href='http://gnodet.blogspot.com/2008/05/jee-specs-in-osgi.html'>
 * Java EE specs in OSGi</a></i>. The main changes were to add comments.
 *
 * see http://gnodet.blogspot.com/2008/05/jee-specs-in-osgi.html
 */
public final class OSGiLocator {

    /**
     * Maps a service name (the fully qualified name of the interface)
     * to an ordered set of factories. Each factory instantiating
     * a specific service provider (implementation).
     */
    private static Map<String, List<Callable<Class< ? >>>> factories;

    /**
     * Private constructor used to prevent instantiation of this
     * utility class (i.e., Utility Pattern).
     */
    private OSGiLocator() {
        super();
    }

    /**
     * Removes the given service provider factory from the set of
     * providers for the service.
     *
     * @param serviceName
     *          The fully qualified name of the service interface.
     * @param factory
     *          A factory for creating a specific type of service
     *          provider. May be <tt>null</tt> in which case this
     *          method does nothing.
     * @throws IllegalArgumentException if serviceName is <tt>null</tt>
     */
    public static synchronized void unregister(final String serviceName,
                                               final Callable<Class< ? >> factory) {
        if ( serviceName == null ) {
            throw new IllegalArgumentException( "serviceName cannot be null" );
        }
        if ( factories != null ) {
            List<Callable<Class< ? >>> l = factories.get( serviceName );
            if ( l != null ) {
                l.remove( factory );
            }
        }
    }

    /**
     * Adds the given service provider factory to the set of
     * providers for the service.
     *
     * @param serviceName
     *          The fully qualified name of the service interface.
     * @param factory
     *          A factory for creating a specific type of service
     *          provider. May be <tt>null</tt> in which case this
     *          method does nothing.
     * @throws IllegalArgumentException if serviceName is <tt>null</tt>
     */
    public static synchronized void register(final String serviceName,
                                             final Callable<Class< ? >> factory) {
        if ( serviceName == null ) {
            throw new IllegalArgumentException( "serviceName cannot be null" );
        }
        if ( factory != null ) {
            if ( factories == null ) {
                factories = new HashMap<String, List<Callable<Class< ? >>>>();
            }
            List<Callable<Class< ? >>> l = factories.get( serviceName );
            if ( l == null ) {
                l = new ArrayList<Callable<Class< ? >>>();
                factories.put( serviceName,
                               l );
            }
            l.add( factory );
        }
    }

    /**
     * Finds the preferred provider for the given service. The preferred
     * provider is the last one added to the set of providers.
     *
     * @param serviceName
     *          The fully qualified name of the service interface.
     * @return
     *          The last provider added for the service if any exists.
     *          Otherwise, it returns <tt>null</tt>.
     * @throws IllegalArgumentException if serviceName is <tt>null</tt>
     */
    public static synchronized Class< ? > locate(final String serviceName) {
        if ( serviceName == null ) {
            throw new IllegalArgumentException( "serviceName cannot be null" );
        }
        if ( factories != null ) {
            List<Callable<Class< ? >>> l = factories.get( serviceName );
            if ( l != null && !l.isEmpty() ) {
                Callable<Class< ? >> c = l.get( l.size() - 1 );
                try {
                    return c.call();
                } catch ( Exception e ) {
                }
            }
        }
        return null;
    }

    /**
     * Finds all providers for the given service.
     *
     * @param serviceName
     *          The fully qualified name of the service interface.
     * @return
     *          The ordered set of providers for the service if any exists.
     *          Otherwise, it returns an empty list.
     * @throws IllegalArgumentException if serviceName is <tt>null</tt>
     */
    public static synchronized List<Class< ? >> locateAll(final String serviceName) {
        if ( serviceName == null ) {
            throw new IllegalArgumentException( "serviceName cannot be null" );
        }
        List<Class< ? >> classes = new ArrayList<Class< ? >>();
        if ( factories != null ) {
            List<Callable<Class< ? >>> l = factories.get( serviceName );
            if ( l != null ) {
                for ( Callable<Class< ? >> c : l ) {
                    try {
                        classes.add( c.call() );
                    } catch ( Exception e ) {
                    }
                }
            }
        }
        return classes;
    }

}
