/******************************************************************************
 * 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.config.internal;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import junit.framework.TestCase;

import org.easymock.MockControl;
import org.eclipse.gemini.blueprint.config.internal.adapter.OsgiServiceLifecycleListenerAdapter;
import org.eclipse.gemini.blueprint.service.importer.ImportedOsgiServiceProxy;
import org.eclipse.gemini.blueprint.service.importer.OsgiServiceLifecycleListener;
import org.eclipse.gemini.blueprint.service.importer.ServiceReferenceProxy;
import org.eclipse.gemini.blueprint.service.importer.support.internal.aop.StaticServiceReferenceProxy;
import org.eclipse.gemini.blueprint.util.internal.MapBasedDictionary;
import org.osgi.framework.ServiceReference;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.eclipse.gemini.blueprint.mock.MockServiceReference;

/**
 * @author Costin Leau
 */
public class OsgiServiceLifecycleListenerAdapterTest extends TestCase {

	protected static class JustListener implements OsgiServiceLifecycleListener {

		public static int BIND_CALLS = 0;

		public static int UNBIND_CALLS = 0;

		public void bind(Object service, Map properties) throws Exception {
			BIND_CALLS++;
		}

		public void unbind(Object service, Map properties) throws Exception {
			UNBIND_CALLS++;
		}
	}

	protected static class CustomListener {

		public static int BIND_CALLS = 0;

		public static int UNBIND_CALLS = 0;

		public static List BIND_SERVICES = new ArrayList();

		public static List UNBIND_SERVICES = new ArrayList();

		public void myBind(Object service, Map properties) {
			BIND_CALLS++;
			BIND_SERVICES.add(service);

		}

		public void myUnbind(Object service, Map properties) {
			UNBIND_CALLS++;
			UNBIND_SERVICES.add(service);

		}

		public void wrongBind() {
			BIND_CALLS++;
		}

		public void wrongUnbind() {
			UNBIND_CALLS--;
		}
	}

	protected static class CustomAndListener extends JustListener {

		public Integer aBind(Object service, Map props) throws Exception {
			super.bind(service, props);
			return null;
		}

		public void aUnbind(Object service, Map props) throws Exception {
			super.unbind(service, props);
		}
	}

	protected static class OverloadedCustomMethods extends CustomListener {

		public void myBind(Date service, Map properties) {
			super.myBind(service, properties);
		}

		public void myUnbind(String service, Map properties) {
			super.myUnbind(service, properties);
		}
	}

	/**
	 * Override standard methods with ones that throw exceptions.
	 * 
	 * @author Costin Leau
	 */
	protected static class ExceptionListener extends CustomAndListener {

		public void bind(Object service, Map properties) throws Exception {
			throw new Exception("expected!") {

				public synchronized Throwable fillInStackTrace() {
					return null;
				}
			};
		}

		public void unbind(Object service, Map properties) throws Exception {
			throw new IOException("expected!") {

				public synchronized Throwable fillInStackTrace() {
					return null;
				}
			};
		}
	}

	protected static class ExceptionCustomListener extends CustomListener {

		public void myBind(Date service, Map properties) {
			throw (RuntimeException) new RuntimeException("expected!") {

				public synchronized Throwable fillInStackTrace() {
					return null;
				}
			};
		}

		public void myUnbind(String service, Map properties) throws IOException {
			throw new IOException("expected!") {

				public synchronized Throwable fillInStackTrace() {
					return null;
				}
			};
		}
	}

	// piggy backs on JustListener static fields
	protected static class DictionaryAndMapCustomListener {

		public void bind(Object service, Dictionary properties) {
			JustListener.BIND_CALLS++;
		}

		public void unbind(Object service, Map props) {
			JustListener.UNBIND_CALLS++;
		}

		public void unbind(Object service, Dictionary props) throws Exception {
			JustListener.UNBIND_CALLS++;
		}
	}

	protected static class OverridingMethodListener extends CustomListener {

		public void myBind(Object service, Map properties) {
			BIND_CALLS++;
		}

		public void myUnbind(Object service, Map properties) {
			UNBIND_CALLS++;
		}
	}

	protected static class JustBind {

		public void myBind(Object service, Map properties) {
			JustListener.BIND_CALLS++;
		}
	}

	protected static class JustUnbind {

		public void myUnbind(Object service, Map properties) {
			JustListener.UNBIND_CALLS++;
		}
	}

	protected static class CustomServiceRefListener {

		private void myUnbind(ServiceReference ref) {
			JustListener.UNBIND_CALLS++;
		}

		private void myBind(ServiceReference ref) {
			JustListener.BIND_CALLS++;
		}
	}

	protected static class CustomServiceRefAndObjectWOMapListener {

		private void myUnbind(ServiceReference ref) {
			JustListener.UNBIND_CALLS++;
		}

		private void myBind(ServiceReference ref) {
			JustListener.BIND_CALLS++;
		}

		private void myBind(Object obj) {
			JustListener.BIND_CALLS++;
		}

	}

	private OsgiServiceLifecycleListenerAdapter listener;

	private static final String BEAN_NAME = "bla";

	protected void setUp() throws Exception {
		JustListener.BIND_CALLS = 0;
		JustListener.UNBIND_CALLS = 0;
		OverloadedCustomMethods.BIND_SERVICES = new ArrayList();
		OverloadedCustomMethods.UNBIND_SERVICES = new ArrayList();

		CustomListener.BIND_CALLS = 0;
		CustomListener.UNBIND_CALLS = 0;
	}

	protected void tearDown() throws Exception {
		listener = null;
		OverloadedCustomMethods.BIND_SERVICES = null;
		OverloadedCustomMethods.UNBIND_SERVICES = null;
	}

	public void testWrapperOverListener() throws Exception {
		listener = new OsgiServiceLifecycleListenerAdapter();
		listener.setBeanFactory(createMockBF(new JustListener()));
		listener.setTargetBeanName(BEAN_NAME);
		listener.afterPropertiesSet();

		assertEquals(0, JustListener.BIND_CALLS);
		listener.bind(null, null);
		assertEquals(1, JustListener.BIND_CALLS);
		assertEquals(0, JustListener.UNBIND_CALLS);
		listener.bind(null, null);
		assertEquals(2, JustListener.BIND_CALLS);
		assertEquals(0, JustListener.UNBIND_CALLS);

		listener.unbind(null, null);
		assertEquals(1, JustListener.UNBIND_CALLS);
		assertEquals(2, JustListener.BIND_CALLS);
		listener.unbind(null, null);
		assertEquals(2, JustListener.UNBIND_CALLS);
		assertEquals(2, JustListener.BIND_CALLS);
	}

	public void testWrapperOverNoInvalidClass() throws Exception {
		listener = new OsgiServiceLifecycleListenerAdapter();
		listener.setTargetBeanName(BEAN_NAME);
		listener.setTarget(new Object());
		listener.setBeanFactory(createMockBF(new Object()));

		try {
			listener.afterPropertiesSet();
			fail("should have thrown exception");
		} catch (IllegalArgumentException ex) {
			// expected
		}
	}

	public void testWrapperWithIncorrectCustomMethodNames() throws Exception {
		listener = new OsgiServiceLifecycleListenerAdapter();
		listener.setTargetBeanName(BEAN_NAME);
		listener.setTarget(new Object());
		listener.setBindMethod("pop");
		listener.setUnbindMethod("corn");

		try {
			listener.afterPropertiesSet();
			fail("should have thrown exception");
		} catch (IllegalArgumentException ex) {
			// expected
		}
	}

	public void testWrapperWithCorrectCustomMethodNamesButIncorrectArgumentTypes() throws Exception {
		listener = new OsgiServiceLifecycleListenerAdapter();
		listener.setTarget(new CustomListener());
		listener.setBindMethod("wrongBind");
		listener.setUnbindMethod("wrongUnbind");

		assertEquals(0, CustomListener.BIND_CALLS);
		assertEquals(0, CustomListener.UNBIND_CALLS);
		try {
			listener.afterPropertiesSet();
			fail("should have thrown exception");
		} catch (IllegalArgumentException ex) {
			// expected
		}

		assertEquals(0, CustomListener.BIND_CALLS);
		assertEquals(0, CustomListener.UNBIND_CALLS);

	}

	public void testWrapperWithCustomMethods() throws Exception {
		listener = new OsgiServiceLifecycleListenerAdapter();
		listener.setBeanFactory(createMockBF(new CustomListener()));
		listener.setTargetBeanName(BEAN_NAME);
		listener.setBindMethod("myBind");
		listener.setUnbindMethod("myUnbind");
		listener.afterPropertiesSet();

		Object service = new Object();
		assertEquals(0, CustomListener.BIND_CALLS);
		assertEquals(0, CustomListener.UNBIND_CALLS);
		listener.bind(new Object(), null);
		assertEquals(1, CustomListener.BIND_CALLS);
		assertEquals(0, CustomListener.UNBIND_CALLS);

		listener.bind(service, null);
		assertEquals(2, CustomListener.BIND_CALLS);
		assertEquals(0, CustomListener.UNBIND_CALLS);

		listener.unbind(service, null);
		assertEquals(2, CustomListener.BIND_CALLS);
		assertEquals(1, CustomListener.UNBIND_CALLS);

		listener.unbind(service, null);
		assertEquals(2, CustomListener.BIND_CALLS);
		assertEquals(2, CustomListener.UNBIND_CALLS);
	}

	public void testWrapperWithCustomMethodsAndNullParameters() throws Exception {
		listener = new OsgiServiceLifecycleListenerAdapter();
		listener.setBeanFactory(createMockBF(new CustomListener()));
		listener.setTargetBeanName(BEAN_NAME);
		listener.setBindMethod("myBind");
		listener.setUnbindMethod("myUnbind");
		listener.afterPropertiesSet();

		assertEquals(0, CustomListener.BIND_CALLS);
		assertEquals(0, CustomListener.UNBIND_CALLS);
		listener.bind(null, null);
		assertEquals("null services allowed", 1, CustomListener.BIND_CALLS);

		listener.unbind(null, null);

		assertEquals("null services allowed", 1, CustomListener.UNBIND_CALLS);
	}

	public void testWrapperWithBothCustomAndInterfaceMethods() throws Exception {
		listener = new OsgiServiceLifecycleListenerAdapter();
		listener.setBeanFactory(createMockBF(new CustomAndListener()));
		listener.setTargetBeanName(BEAN_NAME);
		listener.setBindMethod("aBind");
		listener.setUnbindMethod("aUnbind");
		listener.afterPropertiesSet();

		Object service = new Object();

		assertEquals(0, CustomAndListener.BIND_CALLS);
		assertEquals(0, CustomAndListener.UNBIND_CALLS);
		listener.bind(service, null);
		assertEquals(2, CustomAndListener.BIND_CALLS);
		assertEquals(0, CustomAndListener.UNBIND_CALLS);

		listener.unbind(service, null);
		assertEquals(2, CustomAndListener.BIND_CALLS);
		assertEquals(2, CustomAndListener.UNBIND_CALLS);

	}

	public void testWrapperWithCustomOverloadedMethodsAndDifferentServiceTypes() throws Exception {
		listener = new OsgiServiceLifecycleListenerAdapter();
		listener.setBeanFactory(createMockBF(new OverloadedCustomMethods()));
		listener.setTargetBeanName(BEAN_NAME);
		listener.setBindMethod("myBind");
		listener.setUnbindMethod("myUnbind");
		listener.afterPropertiesSet();

		Object objService = new Object();
		Date dateService = new Date();
		String stringService = "token";

		assertEquals(0, OverloadedCustomMethods.BIND_CALLS);
		assertEquals(0, OverloadedCustomMethods.UNBIND_CALLS);
		listener.bind(objService, null);
		assertEquals("only one method accepts Object(s)", 1, OverloadedCustomMethods.BIND_CALLS);
		assertEquals(0, OverloadedCustomMethods.UNBIND_CALLS);

		listener.bind(dateService, null);
		assertEquals("two method accept Date(s)", 3, OverloadedCustomMethods.BIND_CALLS);
		assertEquals(0, OverloadedCustomMethods.UNBIND_CALLS);

		listener.unbind(stringService, null);
		assertEquals("two method accept Date(s)", 3, OverloadedCustomMethods.BIND_CALLS);
		assertEquals("two method accept String(s)", 2, OverloadedCustomMethods.UNBIND_CALLS);

		listener.unbind(objService, null);
		assertEquals("two method accept Date(s)", 3, OverloadedCustomMethods.BIND_CALLS);
		assertEquals("only one method accepts Object(s)", 3, OverloadedCustomMethods.UNBIND_CALLS);

		assertEquals(3, OverloadedCustomMethods.BIND_SERVICES.size());
		assertSame("incorrect call order", objService, OverloadedCustomMethods.BIND_SERVICES.get(0));
		assertSame("incorrect call order", dateService, OverloadedCustomMethods.BIND_SERVICES.get(1));
		assertSame("incorrect call order", dateService, OverloadedCustomMethods.BIND_SERVICES.get(2));

		assertEquals(3, OverloadedCustomMethods.UNBIND_SERVICES.size());
		assertSame("incorrect call order", stringService, OverloadedCustomMethods.UNBIND_SERVICES.get(0));
		assertSame("incorrect call order", stringService, OverloadedCustomMethods.UNBIND_SERVICES.get(1));
		assertSame("incorrect call order", objService, OverloadedCustomMethods.UNBIND_SERVICES.get(2));
	}

	public void testExceptionOnListenerMethod() throws Exception {
		listener = new OsgiServiceLifecycleListenerAdapter();
		listener.setBeanFactory(createMockBF(new ExceptionListener()));
		listener.setTargetBeanName(BEAN_NAME);
		listener.setBindMethod("aBind");
		listener.setUnbindMethod("aUnbind");
		listener.afterPropertiesSet();

		Object service = new Object();
		assertEquals(0, JustListener.BIND_CALLS);
		assertEquals(0, JustListener.UNBIND_CALLS);
		listener.bind(service, null);
		assertEquals(1, JustListener.BIND_CALLS);
		assertEquals(0, JustListener.UNBIND_CALLS);

		listener.unbind(service, null);
		assertEquals(1, JustListener.BIND_CALLS);
		assertEquals(1, JustListener.UNBIND_CALLS);
	}

	public void testExceptionOnCustomMethods() throws Exception {
		listener = new OsgiServiceLifecycleListenerAdapter();
		listener.setBeanFactory(createMockBF(new ExceptionCustomListener()));
		listener.setTargetBeanName(BEAN_NAME);
		listener.setBindMethod("myBind");
		listener.setUnbindMethod("myUnbind");
		listener.afterPropertiesSet();

		Date service = new Date();
		assertEquals(0, ExceptionCustomListener.BIND_CALLS);
		assertEquals(0, ExceptionCustomListener.UNBIND_CALLS);
		listener.bind(service, null);
		assertEquals("should have called overloaded method with type Object", 1, ExceptionCustomListener.BIND_CALLS);
		assertEquals(0, ExceptionCustomListener.UNBIND_CALLS);

		listener.unbind(service, null);
		assertEquals(1, ExceptionCustomListener.BIND_CALLS);
		assertEquals("should have called overloaded method with type Object", 1, ExceptionCustomListener.UNBIND_CALLS);
	}

	public void testStandardListenerWithListeningMethodsSpecifiedAsCustomOnes() throws Exception {
		listener = new OsgiServiceLifecycleListenerAdapter();
		listener.setBeanFactory(createMockBF(new JustListener()));
		listener.setTargetBeanName(BEAN_NAME);
		listener.setBindMethod("bind");
		listener.setUnbindMethod("unbind");
		listener.afterPropertiesSet();

		Object service = null;
		assertEquals(0, JustListener.BIND_CALLS);
		listener.bind(service, null);
		// only the interface is being called since the service is null
		assertEquals(2, JustListener.BIND_CALLS);

		service = new Object();

		listener.bind(service, null);
		assertEquals(4, JustListener.BIND_CALLS);
	}

	public void testListenerWithOverloadedTypesAndMultipleParameterTypes() throws Exception {
		listener = new OsgiServiceLifecycleListenerAdapter();
		listener.setBeanFactory(createMockBF(new DictionaryAndMapCustomListener()));
		listener.setTargetBeanName(BEAN_NAME);
		listener.setBindMethod("bind");
		listener.setUnbindMethod("unbind");
		listener.afterPropertiesSet();

		Object service = new Date();
		MapBasedDictionary props = new MapBasedDictionary();

		assertEquals(0, JustListener.BIND_CALLS);
		assertEquals(0, JustListener.UNBIND_CALLS);
		listener.bind(service, props);

		assertEquals(1, JustListener.BIND_CALLS);
		assertEquals(0, JustListener.UNBIND_CALLS);

		listener.unbind(service, props);
		assertEquals(1, JustListener.BIND_CALLS);
		assertEquals("only one unbind method should be called", 1, JustListener.UNBIND_CALLS);
	}

	public void testOverridingMethodsDiscovery() throws Exception {
		listener = new OsgiServiceLifecycleListenerAdapter();
		listener.setBeanFactory(createMockBF(new OverridingMethodListener()));
		listener.setTargetBeanName(BEAN_NAME);
		listener.setBindMethod("myBind");
		listener.setUnbindMethod("myUnbind");
		listener.afterPropertiesSet();

		assertEquals(0, CustomListener.BIND_CALLS);
		assertEquals(0, CustomListener.UNBIND_CALLS);

		listener.bind(new Object(), null);
		assertEquals(1, CustomListener.BIND_CALLS);
		assertEquals(0, CustomListener.UNBIND_CALLS);

		listener.unbind(new Object(), null);
		assertEquals(1, CustomListener.BIND_CALLS);
		assertEquals(1, CustomListener.UNBIND_CALLS);

	}

	public void testJustCustomBindMethod() throws Exception {
		listener = new OsgiServiceLifecycleListenerAdapter();
		listener.setBeanFactory(createMockBF(new JustBind()));
		listener.setTargetBeanName(BEAN_NAME);
		listener.setBindMethod("myBind");
		listener.afterPropertiesSet();

		assertEquals(0, JustListener.BIND_CALLS);
		assertEquals(0, JustListener.UNBIND_CALLS);

		listener.bind(new Object(), null);

		assertEquals(1, JustListener.BIND_CALLS);
		assertEquals(0, JustListener.UNBIND_CALLS);
	}

	public void testJustCustomUnbindMethod() throws Exception {
		listener = new OsgiServiceLifecycleListenerAdapter();
		listener.setBeanFactory(createMockBF(new JustUnbind()));
		listener.setTargetBeanName(BEAN_NAME);
		listener.setUnbindMethod("myUnbind");
		listener.afterPropertiesSet();

		assertEquals(0, JustListener.BIND_CALLS);
		assertEquals(0, JustListener.UNBIND_CALLS);

		listener.unbind(new Object(), null);

		assertEquals(0, JustListener.BIND_CALLS);
		assertEquals(1, JustListener.UNBIND_CALLS);
	}

	public void testCustomServiceRefBind() throws Exception {
		listener = new OsgiServiceLifecycleListenerAdapter();
		listener.setBeanFactory(createMockBF(new CustomServiceRefListener()));
		listener.setTargetBeanName(BEAN_NAME);
		listener.setBindMethod("myBind");
		listener.afterPropertiesSet();

		assertEquals(0, JustListener.BIND_CALLS);
		assertEquals(0, JustListener.UNBIND_CALLS);

		listener.bind(new ImportedOsgiServiceProxyMock(), null);

		assertEquals(1, JustListener.BIND_CALLS);
		assertEquals(0, JustListener.UNBIND_CALLS);

	}

	public void testCustomServiceRefUnbind() throws Exception {
		listener = new OsgiServiceLifecycleListenerAdapter();
		listener.setBeanFactory(createMockBF(new CustomServiceRefListener()));
		listener.setTargetBeanName(BEAN_NAME);
		listener.setUnbindMethod("myUnbind");
		listener.afterPropertiesSet();

		assertEquals(0, JustListener.BIND_CALLS);
		assertEquals(0, JustListener.UNBIND_CALLS);

		listener.unbind(new ImportedOsgiServiceProxyMock(), null);

		assertEquals(0, JustListener.BIND_CALLS);
		assertEquals(1, JustListener.UNBIND_CALLS);
	}
	
	
	public void testCustomServiceRefAndObjectWOMapBind() throws Exception {
		listener = new OsgiServiceLifecycleListenerAdapter();
		listener.setBeanFactory(createMockBF(new CustomServiceRefAndObjectWOMapListener()));
		listener.setTargetBeanName(BEAN_NAME);
		listener.setBindMethod("myBind");
		listener.afterPropertiesSet();

		assertEquals(0, JustListener.BIND_CALLS);
		assertEquals(0, JustListener.UNBIND_CALLS);

		listener.bind(new ImportedOsgiServiceProxyMock(), null);

		assertEquals(2, JustListener.BIND_CALLS);
		assertEquals(0, JustListener.UNBIND_CALLS);
	}
	

	private class ImportedOsgiServiceProxyMock implements ImportedOsgiServiceProxy {

		public Map getServiceProperties() {
			return new HashMap();
		}

		public ServiceReferenceProxy getServiceReference() {
			return new StaticServiceReferenceProxy(new MockServiceReference());
		}
	}

	private ConfigurableBeanFactory createMockBF(Object target) {
		MockControl ctrl = MockControl.createNiceControl(ConfigurableBeanFactory.class);
		ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) ctrl.getMock();

		ctrl.expectAndReturn(cbf.getBean(BEAN_NAME), target);
		ctrl.expectAndReturn(cbf.getType(BEAN_NAME), target.getClass());

		ctrl.replay();
		return cbf;
	}
}
