/******************************************************************************
 * 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.service.importer.support;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.gemini.blueprint.service.importer.support.internal.aop.ServiceProxyCreator;
import org.eclipse.gemini.blueprint.service.importer.support.internal.collection.CollectionProxy;
import org.eclipse.gemini.blueprint.service.importer.support.internal.collection.OsgiServiceCollection;
import org.eclipse.gemini.blueprint.service.importer.support.internal.collection.OsgiServiceList;
import org.eclipse.gemini.blueprint.service.importer.support.internal.collection.OsgiServiceSet;
import org.eclipse.gemini.blueprint.service.importer.support.internal.collection.OsgiServiceSortedList;
import org.eclipse.gemini.blueprint.service.importer.support.internal.collection.OsgiServiceSortedSet;
import org.eclipse.gemini.blueprint.service.importer.support.internal.controller.ImporterController;
import org.eclipse.gemini.blueprint.service.importer.support.internal.controller.ImporterInternalActions;
import org.eclipse.gemini.blueprint.service.importer.support.internal.dependency.ImporterStateListener;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Filter;
import org.springframework.util.Assert;

/**
 * OSGi service (collection) importer. This implementation creates a managed (read-only) collection of OSGi services.
 * The returned collection automatically handles the OSGi services dynamics. If a new service that matches the
 * configuration criteria appears, it will be automatically added to the collection. If a service that matches the
 * criteria disappears (is unregistered), it will be automatically removed from the collection.
 * 
 * <p/> Due to the dynamic nature of OSGi services, the collection content can change at runtime, even during iteration.
 * This implementation will correctly update all the collection <code>Iterator</code>s so they reflect the collection
 * content. This approach (as opposed to the 'snapshot' strategy) prevents dealing with <em>dead</em> services which can
 * appear when imported services go down while iterating. This means that iterating while the collection is being
 * changed is safe.
 * 
 * <p/> Note that the <code>Iterator</code> still has to be fulfilled meaning the <code>next()</code> method always obey
 * the result of the previous <code>hasNext()</code> invocation:
 * 
 * <p/> <table border="1"> <tr> <th><code>hasNext()</code> returned value</th> <th><code>next()</code> behaviour</th>
 * </th> <tr> <td> <code>true</code></td> <td><em>Always</em> return a non-null value, even when the collection has
 * shrunk as services when away. This means returning a proxy that will throw an exception on an invocation that
 * requires the backing service to be present.</td> </tr> <tr> <td><code>false</code></td> <td>per <code>Iterator</code>
 * contract, <code>NoSuchElementException</code> is thrown. This applies even if other services are added to the
 * collection.</td> </tr> </table>
 * 
 * <p/> Due to the dynamic nature of OSGi, <code>hasNext()</code> invocation made on the same <code>Iterator</code> can
 * return different values based on the services availability. However, as explained above, <code>next()</code> will
 * always obey the result of the last <code>hasNext()</code> method.
 * 
 * <p/> <strong>Note:</strong> Even though the collection and its iterators communicate in a thread-safe manner,
 * iterators themselves are not thread-safe. Concurrent access on the iterators should be properly synchronized. Due to
 * the light nature of the iterators, consider creating a new one rather then reusing or sharing.
 * 
 * 
 * @see java.util.Iterator
 * @see java.util.Collection
 * @see java.util.List
 * @see java.util.Set
 * @see java.util.SortedSet
 * 
 * @author Costin Leau
 */
public final class OsgiServiceCollectionProxyFactoryBean extends AbstractServiceImporterProxyFactoryBean {

	/**
	 * Wrapper around internal commands.
	 * 
	 * @author Costin Leau
	 * 
	 */
	private class Executor implements ImporterInternalActions {

		public void addStateListener(ImporterStateListener stateListener) {
			stateListeners.add(stateListener);
		}

		public void removeStateListener(ImporterStateListener stateListener) {
			stateListeners.remove(stateListener);
		}

		public boolean isSatisfied() {
			return (exposedProxy == null ? true : exposedProxy.isSatisfied());
		}
	};

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

	/** proxy casted to a specific interface to allow specific method calls */
	private CollectionProxy exposedProxy;

	/** proxy infrastructure hook exposed to allow clean up */
	private Runnable proxyDestructionCallback;
	private Runnable initializationCallback;

	/** proxy creator */
	private ServiceProxyCreator proxyCreator;

	private Comparator comparator;

	private CollectionType collectionType = CollectionType.LIST;
	/** greedy-proxying */
	private boolean greedyProxying = false;

	private MemberType memberType = MemberType.SERVICE_OBJECT;

	/** internal listeners */
	private final List<ImporterStateListener> stateListeners =
			Collections.synchronizedList(new ArrayList<ImporterStateListener>(4));

	private final ImporterInternalActions controller;

	public OsgiServiceCollectionProxyFactoryBean() {
		controller = new ImporterController(new Executor());
	}

	public void afterPropertiesSet() {
		super.afterPropertiesSet();

		proxyCreator =
				new StaticServiceProxyCreator(getInterfaces(), getAopClassLoader(), getBeanClassLoader(),
						getBundleContext(), getImportContextClassLoader(), greedyProxying, isUseBlueprintExceptions());
	}

	/**
	 * Create the managed-collection given the existing settings. This method creates the osgi managed collection and
	 * wraps it with an unmodifiable map to prevent exposing infrastructure methods and write access.
	 * 
	 * @return importer proxy
	 */
	@SuppressWarnings("unchecked")
	@Override
	Object createProxy(boolean lazyProxy) {
		if (log.isDebugEnabled())
			log.debug("Creating a multi-value/collection proxy");

		OsgiServiceCollection collection;
		Collection delegate;

		BundleContext bundleContext = getBundleContext();
		ClassLoader classLoader = getAopClassLoader();
		Filter filter = getUnifiedFilter();
		boolean useServiceReferences = MemberType.SERVICE_REFERENCE.equals(memberType);

		if (CollectionType.LIST.equals(collectionType)) {
			collection =
					(comparator == null ? new OsgiServiceList(filter, bundleContext, classLoader, proxyCreator,
							useServiceReferences) : new OsgiServiceSortedList(filter, bundleContext, classLoader,
							comparator, proxyCreator, useServiceReferences));
			delegate = Collections.unmodifiableList((List) collection);
		} else if (CollectionType.SET.equals(collectionType)) {
			collection =
					(comparator == null ? new OsgiServiceSet(filter, bundleContext, classLoader, proxyCreator,
							useServiceReferences) : new OsgiServiceSortedSet(filter, bundleContext, classLoader,
							comparator, proxyCreator, useServiceReferences));

			delegate = Collections.unmodifiableSet((Set) collection);
		} else if (CollectionType.SORTED_LIST.equals(collectionType)) {
			collection =
					new OsgiServiceSortedList(filter, bundleContext, classLoader, comparator, proxyCreator,
							useServiceReferences);

			delegate = Collections.unmodifiableList((List) collection);
		}

		else if (CollectionType.SORTED_SET.equals(collectionType)) {
			collection =
					new OsgiServiceSortedSet(filter, bundleContext, classLoader, comparator, proxyCreator,
							useServiceReferences);
			delegate = Collections.unmodifiableSortedSet((SortedSet) collection);
		}

		else {
			throw new IllegalArgumentException("Unknown collection type:" + collectionType);
		}

		// assign the proxy early to avoid multiple collection creation
		// when calling the listeners
		proxy = delegate;

		collection.setRequiredAtStartup(Availability.MANDATORY.equals(getAvailability()));
		collection.setListeners(getListeners());
		collection.setStateListeners(stateListeners);
		collection.setServiceImporter(this);
		collection.setServiceImporterName(getBeanName());
		collection.setUseBlueprintExceptions(isUseBlueprintExceptions());

		// start the lookup only after the proxy has been assembled
		if (!lazyProxy) {
			collection.afterPropertiesSet();
		} else {
			final OsgiServiceCollection col = collection;
			initializationCallback = new Runnable() {
				public void run() {
					col.afterPropertiesSet();
				}
			};
		}

		exposedProxy = collection;
		proxyDestructionCallback = new DisposableBeanRunnableAdapter(collection);

		return delegate;
	}

	@Override
	Runnable getProxyInitializer() {
		return initializationCallback;
	}

	@Override
	Runnable getProxyDestructionCallback() {
		return proxyDestructionCallback;
	}

	/**
	 * Sets the (optional) comparator for ordering the resulting collection. The presence of a comparator will force the
	 * FactoryBean to use a <em>sorted</em> collection even though, the specified collection type does not imply
	 * ordering.
	 * 
	 * <p/> Thus, instead of list a sorted list will be created and instead of a set, a sorted set.
	 * 
	 * @see #setCollectionType(CollectionType)
	 * 
	 * @param comparator Comparator (can be null) used for ordering the resulting collection.
	 */
	public void setComparator(Comparator comparator) {
		this.comparator = comparator;
	}

	/**
	 * Sets the collection type this FactoryBean will produce. Note that if a comparator is set, a sorted collection
	 * will be created even if the specified type is does not imply ordering. If no comparator is set but the collection
	 * type implies ordering, the natural order of the elements will be used.
	 * 
	 * @see #setComparator(Comparator)
	 * @see java.lang.Comparable
	 * @see java.util.Comparator
	 * @see CollectionType
	 * 
	 * @param collectionType the collection type as string using one of the values above.
	 */
	public void setCollectionType(CollectionType collectionType) {
		Assert.notNull(collectionType);
		this.collectionType = collectionType;
	}

	/**
	 * Dictates whether <em>greedy</em> proxies are created or not (default).
	 * 
	 * <p> Greedy proxies will proxy <b>all</b> the (visible) classes published by the imported OSGi services. This
	 * means that the individual service proxy, might implement/extend additional classes. </p> By default, greedy
	 * proxies are disabled (false) meaning that only the specified classes are used for generating the the imported
	 * OSGi service proxies.
	 * 
	 * <p/> <b>Note:</b>Greedy proxying will use the proxy mechanism dictated by this factory configuration. This means
	 * that if JDK proxies are used, greedy proxing will consider only additional interfaces exposed by the OSGi service
	 * and none of the extra classes. When CGLIB is used, all extra published classes (whether interfaces or
	 * <em>non-final</em> concrete classes) will be considered.
	 * 
	 * @param greedyProxying true if greedy proxying should be enabled, false otherwise.
	 */
	public void setGreedyProxying(boolean greedyProxying) {
		this.greedyProxying = greedyProxying;
	}

	/**
	 * Sets the member type of this service collection.
	 * 
	 * @return the collection member type
	 */
	public MemberType getMemberType() {
		return memberType;
	}

	/**
	 * Sets the member type of this service collection. The collection can hold either service proxies (the default)
	 * indicated by {@link MemberType#SERVICE_OBJECT} or service references {@link MemberType#SERVICE_REFERENCE}.
	 * 
	 * @param type the collection member type
	 */
	public void setMemberType(MemberType type) {
		Assert.notNull(type);
		this.memberType = type;
	}

	@Override
	Cardinality getInternalCardinality() {
		return (Availability.OPTIONAL.equals(getAvailability()) ? Cardinality.C_0__N : Cardinality.C_1__N);
	}
}