/******************************************************************************
 * Copyright (c) 2006, 2010 VMware Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Apache License v2.0 which accompanies this distribution. 
 * The Eclipse Public License is available at 
 * http://www.eclipse.org/legal/epl-v10.html and the Apache License v2.0
 * is available at http://www.opensource.org/licenses/apache2.0.php.
 * You may elect to redistribute this code under either of these licenses. 
 * 
 * Contributors:
 *   VMware Inc.
 *****************************************************************************/

package org.eclipse.gemini.blueprint.util;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Filter;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;

/**
 * Utility class for dealing with various OSGi listeners, mainly
 * {@link ServiceListener}s. This class contains common functionality such as
 * broadcasting events or safely registering an OSGi listener.
 * 
 * @author Costin Leau
 */
public abstract class OsgiListenerUtils {

	private static final Log log = LogFactory.getLog(OsgiListenerUtils.class);


	/**
	 * Adds a service listener to the given bundle context under the specified
	 * filter. This method will deliver <em>synthetic</em> events of type
	 * <code>REGISTERED</code> for <em>all</em> existing services (that
	 * match the given filter) as if the services were registered after the
	 * listener registration.
	 * 
	 * <p/> This might cause problems if a service is registered between the
	 * listener registration and the retrieval of existing services since the
	 * listener will receive two events for the same service. For most listeners
	 * implementations however, this should not be a problem
	 * 
	 * @param context bundle context to register the listener with
	 * @param listener service listener to be registered
	 * @param filter OSGi filter (given as a Filter) for registering the
	 * listener (can be <code>null</code>)
	 * @see #addServiceListener(BundleContext, ServiceListener, String)
	 */
	public static void addServiceListener(BundleContext context, ServiceListener listener, Filter filter) {
		String toStringFilter = (filter == null ? null : filter.toString());
		addServiceListener(context, listener, toStringFilter);
	}

	/**
	 * Adds a service listener to the given bundle context under the specified
	 * filter given as a String. The method will also retrieve <em>all</em>
	 * the services registered before the listener registration (that match the
	 * given filter) and will inform the listener through service events of type
	 * <code>REGISTERED</code>.
	 * 
	 * <p/> This might cause problems if a service is registered between the
	 * listener registration and the retrieval of existing services since the
	 * listener will receive two events for the same service. For most listeners
	 * implementations however, this should not be a problem
	 * 
	 * 
	 * @param context bundle context to register the listener with
	 * @param listener service listener to be registered
	 * @param filter OSGi filter (given as a String) for registering the
	 * listener (can be <code>null</code>)
	 * @see BundleContext#getServiceReference(String)
	 * @see BundleContext#getServiceReferences(String, String)
	 */
	public static void addServiceListener(BundleContext context, ServiceListener listener, String filter) {
		registerListener(context, listener, filter);

		// now get the already registered services and call the listener
		// (the listener should be able to handle duplicates)
		dispatchServiceRegistrationEvents(OsgiServiceReferenceUtils.getServiceReferences(context, filter), listener);
	}

	private static void registerListener(BundleContext context, ServiceListener listener, String filter) {
		Assert.notNull(context);
		Assert.notNull(listener);

		try {
			// add listener
			context.addServiceListener(listener, filter);
		}
		catch (InvalidSyntaxException isex) {
			throw (RuntimeException) new IllegalArgumentException("Invalid filter").initCause(isex);
		}
	}

	private static void dispatchServiceRegistrationEvents(ServiceReference[] alreadyRegistered, ServiceListener listener) {
		if (log.isTraceEnabled())
			log.trace("Calling listener for already registered services: "
					+ ObjectUtils.nullSafeToString(alreadyRegistered));

		if (alreadyRegistered != null) {
			for (int i = 0; i < alreadyRegistered.length; i++) {
				listener.serviceChanged(new ServiceEvent(ServiceEvent.REGISTERED, alreadyRegistered[i]));
			}
		}
	}

	/**
	 * Adds a service listener to the given bundle context, under the specified
	 * filter. This method will deliver at most one <em>synthetic</em> event
	 * of type <code>REGISTERED</code> for the <em>best matching</em>
	 * existing service as if the services were registered after the listener
	 * registration.
	 * 
	 * 
	 * <p/> This might cause problems if a service is registered between the
	 * listener registration and the retrieval of existing services since the
	 * listener will receive two events for the same service. For most listeners
	 * implementations however, this should not be a problem
	 * 
	 * 
	 * @param context bundle context to register the listener with
	 * @param listener service listener to be registered
	 * @param filter OSGi filter (given as a Filter) for registering the
	 * listener (can be <code>null</code>)
	 * @see #addSingleServiceListener(BundleContext, ServiceListener, String)
	 */
	public static void addSingleServiceListener(BundleContext context, ServiceListener listener, Filter filter) {
		String toStringFilter = (filter == null ? null : filter.toString());
		addSingleServiceListener(context, listener, toStringFilter);
	}

	/**
	 * Adds a service listener to the given application context, under the
	 * specified filter given as a String. The method will also retrieve the
	 * <em>best matching</em> service registered before the listener
	 * registration and will inform the listener through a service event of type
	 * <code>REGISTERED</code>. This is the only difference from
	 * {@link #addServiceListener(BundleContext, ServiceListener, Filter)} which
	 * considers all services, not just the best match.
	 * 
	 * <p/> This might cause problems if a service is registered between the
	 * listener registration and the retrieval of existing services since the
	 * listener will receive two events for the same service. For most listeners
	 * implementations however, this should not be a problem
	 * 
	 * 
	 * @param context bundle context to register the listener with
	 * @param listener service listener to be registered
	 * @param filter OSGi filter (given as a String) for registering the
	 * listener (can be <code>null</code>)
	 * @see BundleContext#getServiceReference(String)
	 * @see BundleContext#getServiceReferences(String, String)
	 */
	public static void addSingleServiceListener(BundleContext context, ServiceListener listener, String filter) {
		registerListener(context, listener, filter);

		// now get the already registered services and call the listener
		// (the listener should be able to handle duplicates)
		ServiceReference ref = OsgiServiceReferenceUtils.getServiceReference(context, filter);
		ServiceReference[] refs = (ref == null ? null : new ServiceReference[] { ref });
		dispatchServiceRegistrationEvents(refs, listener);
	}

	/**
	 * Removes a service listener from the given bundle context. This method
	 * simply takes care of any exceptions that might be thrown (in case the
	 * context is invalid).
	 * 
	 * @param context bundle context to unregister the listener from
	 * @param listener service listener to unregister
	 * @return true if the listener unregistration has succeeded, false
	 * otherwise (for example if the bundle context is invalid)
	 */
	public static boolean removeServiceListener(BundleContext context, ServiceListener listener) {
		if (context == null || listener == null)
			return false;

		try {
			context.removeServiceListener(listener);
			return true;
		}
		catch (IllegalStateException e) {
			// Bundle context is no longer valid
		}

		return false;
	}

}