/******************************************************************************
 * 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.extensions.annotation;

import junit.framework.TestCase;
import org.easymock.EasyMock;
import org.easymock.internal.MocksControl;
import org.eclipse.gemini.blueprint.mock.MockBundleContext;
import org.eclipse.gemini.blueprint.service.importer.support.Availability;
import org.eclipse.gemini.blueprint.service.importer.support.ImportContextClassLoaderEnum;
import org.eclipse.gemini.blueprint.service.importer.support.OsgiServiceCollectionProxyFactoryBean;
import org.eclipse.gemini.blueprint.service.importer.support.OsgiServiceProxyFactoryBean;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.FieldCallback;
import org.springframework.util.ReflectionUtils.FieldFilter;

import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import java.util.SortedSet;

/**
 * @author Andy Piper
 */
public class OsgiServiceAnnotationTest extends TestCase {

	private ServiceReferenceInjectionBeanPostProcessor processor;

	private BundleContext context;


	protected void setUp() throws Exception {
		super.setUp();
		processor = new ServiceReferenceInjectionBeanPostProcessor();
		context = new MockBundleContext();
		processor.setBundleContext(context);
		processor.setBeanClassLoader(getClass().getClassLoader());
		BeanFactory factory = EasyMock.createMock(BeanFactory.class);
		processor.setBeanFactory(factory);
	}

	/**
	 * Disabled since it doesn't work as we can't proxy final classes.
	 */
	public void tstGetServicePropertySetters() throws Exception {
		OsgiServiceProxyFactoryBean pfb = new OsgiServiceProxyFactoryBean();
		Method setter = AnnotatedBean.class.getMethod("setStringType", new Class<?>[] { String.class });
		ServiceReference ref = AnnotationUtils.getAnnotation(setter, ServiceReference.class);

		processor.getServiceProperty(pfb, ref, setter.getParameterTypes()[0], null);
		Class<?>[] intfs = (Class[]) getPrivateProperty(pfb, "serviceTypes");
		assertEquals(intfs[0], String.class);

		setter = AnnotatedBean.class.getMethod("setIntType", new Class<?>[] { Integer.TYPE });
		ref = AnnotationUtils.getAnnotation(setter, ServiceReference.class);

		pfb = new OsgiServiceProxyFactoryBean();
		processor.getServiceProperty(pfb, ref, setter.getParameterTypes()[0], null);
		intfs = (Class[]) getPrivateProperty(pfb, "serviceTypes");
		assertEquals(intfs[0], Integer.TYPE);

	}

	public void testGetServicePropertyCardinality() throws Exception {
		OsgiServiceProxyFactoryBean pfb = new OsgiServiceProxyFactoryBean();
		Method setter = AnnotatedBean.class.getMethod("setAnnotatedBeanTypeWithCardinality1_1",
			new Class<?>[] { AnnotatedBean.class });
		ServiceReference ref = AnnotationUtils.getAnnotation(setter, ServiceReference.class);
		processor.getServiceProperty(pfb, ref, setter.getParameterTypes()[0], null);
		assertTrue(pfb.getAvailability() == Availability.MANDATORY);

		setter = AnnotatedBean.class.getMethod("setAnnotatedBeanTypeWithCardinality0_1",
			new Class<?>[] { AnnotatedBean.class });
		ref = AnnotationUtils.getAnnotation(setter, ServiceReference.class);
		pfb = new OsgiServiceProxyFactoryBean();
		processor.getServiceProperty(pfb, ref, setter.getParameterTypes()[0], null);
		assertFalse(pfb.getAvailability() == Availability.MANDATORY);
	}

	public void testProperMultiCardinality() throws Exception {
		OsgiServiceCollectionProxyFactoryBean pfb = new OsgiServiceCollectionProxyFactoryBean();

		Method setter = AnnotatedBean.class.getMethod("setAnnotatedBeanTypeWithCardinality0_N",
			new Class<?>[] { List.class });
		ServiceReference ref = AnnotationUtils.getAnnotation(setter, ServiceReference.class);
		processor.getServiceProperty(pfb, ref, setter.getParameterTypes()[0], null);
		assertFalse(pfb.getAvailability() == Availability.MANDATORY);

		setter = AnnotatedBean.class.getMethod("setAnnotatedBeanTypeWithCardinality1_N",
			new Class<?>[] { SortedSet.class });
		ref = AnnotationUtils.getAnnotation(setter, ServiceReference.class);
		pfb = new OsgiServiceCollectionProxyFactoryBean();
		processor.getServiceProperty(pfb, ref, setter.getParameterTypes()[0], null);
		assertTrue(pfb.getAvailability() == Availability.MANDATORY);
	}

	public void testErrorMultiCardinality() throws Exception {
		OsgiServiceCollectionProxyFactoryBean pfb = new OsgiServiceCollectionProxyFactoryBean();

		Method setter = AnnotatedBean.class.getMethod("setAnnotatedBeanErrorTypeWithCardinality1_N",
			new Class<?>[] { SortedSet.class });
		ServiceReference ref = AnnotationUtils.getAnnotation(setter, ServiceReference.class);
		pfb = new OsgiServiceCollectionProxyFactoryBean();
		try {
			processor.getServiceProperty(pfb, ref, setter.getParameterTypes()[0], null);
			fail("IllegalArgumentException should have been thrown");
		}
		catch (Exception e) {
		}
	}

	public void testGetServicePropertyClassloader() throws Exception {
		OsgiServiceProxyFactoryBean pfb = new OsgiServiceProxyFactoryBean();
		Method setter = AnnotatedBean.class.getMethod("setAnnotatedBeanTypeWithClassLoaderClient",
			new Class<?>[] { AnnotatedBean.class });
		ServiceReference ref = AnnotationUtils.getAnnotation(setter, ServiceReference.class);
		processor.getServiceProperty(pfb, ref, setter.getParameterTypes()[0], null);
		assertEquals(pfb.getImportContextClassLoader(), ImportContextClassLoaderEnum.CLIENT);

		pfb = new OsgiServiceProxyFactoryBean();
		setter = AnnotatedBean.class.getMethod("setAnnotatedBeanTypeWithClassLoaderUmanaged",
			new Class<?>[] { AnnotatedBean.class });
		ref = AnnotationUtils.getAnnotation(setter, ServiceReference.class);
		processor.getServiceProperty(pfb, ref, setter.getParameterTypes()[0], null);

		assertEquals(pfb.getImportContextClassLoader(), ImportContextClassLoaderEnum.UNMANAGED);

		pfb = new OsgiServiceProxyFactoryBean();
		setter = AnnotatedBean.class.getMethod("setAnnotatedBeanTypeWithClassLoaderServiceProvider",
			new Class<?>[] { AnnotatedBean.class });
		ref = AnnotationUtils.getAnnotation(setter, ServiceReference.class);
		processor.getServiceProperty(pfb, ref, setter.getParameterTypes()[0], null);
		assertEquals(pfb.getImportContextClassLoader(), ImportContextClassLoaderEnum.SERVICE_PROVIDER);
	}

	public void testGetServicePropertyBeanName() throws Exception {
		OsgiServiceProxyFactoryBean pfb = new OsgiServiceProxyFactoryBean();
		Method setter = AnnotatedBean.class.getMethod("setAnnotatedBeanTypeWithBeanName",
			new Class<?>[] { AnnotatedBean.class });
		ServiceReference ref = AnnotationUtils.getAnnotation(setter, ServiceReference.class);
		processor.getServiceProperty(pfb, ref, setter.getParameterTypes()[0], null);
		String beanName = (String) getPrivateProperty(pfb, "serviceBeanName");
		;
		assertEquals(beanName, "myBean");
	}

	public void testGetServicePropertyFilter() throws Exception {
		OsgiServiceProxyFactoryBean pfb = new OsgiServiceProxyFactoryBean();
		Method setter = AnnotatedBean.class.getMethod("setAnnotatedBeanTypeWithFilter",
			new Class<?>[] { AnnotatedBean.class });
		ServiceReference ref = AnnotationUtils.getAnnotation(setter, ServiceReference.class);
		processor.getServiceProperty(pfb, ref, setter.getParameterTypes()[0], null);
		String filter = (String) getPrivateProperty(pfb, "filter");
		;
		assertEquals(filter, "(wooey=fooo)");
	}

	public void testGetServicePropertyServiceClass() throws Exception {
		OsgiServiceProxyFactoryBean pfb = new OsgiServiceProxyFactoryBean();
		Method setter = AnnotatedBean.class.getMethod("setAnnotatedBeanTypeWithServiceType",
			new Class<?>[] { AnnotatedBean.class });
		ServiceReference ref = AnnotationUtils.getAnnotation(setter, ServiceReference.class);
		processor.getServiceProperty(pfb, ref, setter.getParameterTypes()[0], null);
		Class<?>[] intfs = (Class[]) getPrivateProperty(pfb, "interfaces");
		assertEquals(intfs[0], Object.class);
	}

	public void testGetServicePropertyComplex() throws Exception {
		OsgiServiceProxyFactoryBean pfb = new OsgiServiceProxyFactoryBean();
		Method setter = AnnotatedBean.class.getMethod("setAnnotatedBeanTypeComplex",
			new Class<?>[] { AnnotatedBean.class });
		ServiceReference ref = AnnotationUtils.getAnnotation(setter, ServiceReference.class);
		processor.getServiceProperty(pfb, ref, setter.getParameterTypes()[0], null);
		Class<?>[] intfs = (Class[]) getPrivateProperty(pfb, "interfaces");
		String filter = (String) getPrivateProperty(pfb, "filter");
		String beanName = (String) getPrivateProperty(pfb, "serviceBeanName");
		assertEquals(intfs[0], AnnotatedBean.class);
		assertFalse(pfb.getAvailability() == Availability.MANDATORY);
		assertEquals(ImportContextClassLoaderEnum.SERVICE_PROVIDER, pfb.getImportContextClassLoader());
		assertEquals(filter, "(id=fooey)");
		assertEquals(beanName, "myBean");
	}

	public void testServiceBeanInjection() throws Exception {
		ServiceBean bean = new ServiceBean();
		final MyService bean1 = new MyService() {

			public Object getId() {
				return this;
			}
		};
		final Serializable bean2 = new Serializable() {

			public String toString() {
				return "bean2";
			}
		};

		BundleContext context = new MockBundleContext() {

			public Object getService(org.osgi.framework.ServiceReference reference) {
				String clazz = ((String[]) reference.getProperty(Constants.OBJECTCLASS))[0];
				if (clazz == null)
					return null;
				else if (clazz.equals(MyService.class.getName())) {
					return bean1;
				}
				else if (clazz.equals(Serializable.class.getName())) {
					return bean2;
				}
				return null;
			}

		};

		ServiceReferenceInjectionBeanPostProcessor p = new ServiceReferenceInjectionBeanPostProcessor();
		p.setBundleContext(context);
		p.setBeanClassLoader(getClass().getClassLoader());
		BeanFactory factory = EasyMock.createMock(BeanFactory.class);
		MocksControl factoryControl = MocksControl.getControl(factory);
		EasyMock.expect(factory.containsBean("&myBean")).andReturn(true);
		EasyMock.replay(factory);
    	p.setBeanFactory(factory);

		p.postProcessAfterInitialization(bean, "myBean");
		assertSame(bean1.getId(), bean.getServiceBean().getId());
		assertSame(bean2.toString(), bean.getSerializableBean().toString());

		factoryControl.verify();
	}

	public void testServiceBeanWithAnnotatedFieldsInjection() throws Exception {
		ServiceBeanWithAnnotatedFields bean = new ServiceBeanWithAnnotatedFields();
		final MyService bean1 = new MyService() {

			public Object getId() {
				return this;
			}
		};
		final Serializable bean2 = new Serializable() {

			public String toString() {
				return "bean2";
			}
		};

		BundleContext context = new MockBundleContext() {

			public Object getService(org.osgi.framework.ServiceReference reference) {
				String clazz = ((String[]) reference.getProperty(Constants.OBJECTCLASS))[0];
				if (clazz == null)
					return null;
				else if (clazz.equals(MyService.class.getName())) {
					return bean1;
				}
				else if (clazz.equals(Serializable.class.getName())) {
					return bean2;
				}
				return null;
			}

		};

		ServiceReferenceInjectionBeanPostProcessor p = new ServiceReferenceInjectionBeanPostProcessor();
		p.setBundleContext(context);
		p.setBeanClassLoader(getClass().getClassLoader());
		BeanFactory factory = EasyMock.createMock(BeanFactory.class);
		EasyMock.replay(factory);
		p.setBeanFactory(factory);

		p.postProcessBeforeInitialization(bean, "myBean");
		assertSame(bean1.getId(), bean.getServiceBean().getId());
		assertSame(bean2.toString(), bean.getSerializableBean().toString());
	}

	public void testServiceFactoryBeanNotInjected() throws Exception {
		ServiceFactoryBean bean = new ServiceFactoryBean();
		final MyService bean1 = new MyService() {

			public Object getId() {
				return this;
			}
		};
		final Serializable bean2 = new Serializable() {

			public String toString() {
				return "bean2";
			}
		};

		BundleContext context = new MockBundleContext() {

			public Object getService(org.osgi.framework.ServiceReference reference) {
				String clazz = ((String[]) reference.getProperty(Constants.OBJECTCLASS))[0];
				if (clazz == null)
					return null;
				else if (clazz.equals(MyService.class.getName())) {
					return bean1;
				}
				else if (clazz.equals(Serializable.class.getName())) {
					return bean2;
				}
				return null;
			}

		};

		ServiceReferenceInjectionBeanPostProcessor p = new ServiceReferenceInjectionBeanPostProcessor();
		p.setBundleContext(context);
		p.postProcessAfterInitialization(bean, "myBean");
		assertNull(bean.getServiceBean());
		assertNull(bean.getSerializableBean());
	}

	public void testServiceFactoryBeanInjected() throws Exception {
		ServiceFactoryBean bean = new ServiceFactoryBean();
		final MyService bean1 = new MyService() {

			public Object getId() {
				return this;
			}
		};
		final Serializable bean2 = new Serializable() {

			public String toString() {
				return "bean2";
			}
		};

		BundleContext context = new MockBundleContext() {

			public Object getService(org.osgi.framework.ServiceReference reference) {
				String clazz = ((String[]) reference.getProperty(Constants.OBJECTCLASS))[0];
				if (clazz == null)
					return null;
				else if (clazz.equals(MyService.class.getName())) {
					return bean1;
				}
				else if (clazz.equals(Serializable.class.getName())) {
					return bean2;
				}
				return null;
			}

		};

		ServiceReferenceInjectionBeanPostProcessor p = new ServiceReferenceInjectionBeanPostProcessor();
		p.setBundleContext(context);
		p.setBeanClassLoader(getClass().getClassLoader());
		PropertyValues pvs = p.postProcessPropertyValues(new MutablePropertyValues(), new PropertyDescriptor[] {
			new PropertyDescriptor("serviceBean", ServiceFactoryBean.class),
			new PropertyDescriptor("serializableBean", ServiceFactoryBean.class) }, bean, "myBean");

		MyService msb = (MyService) pvs.getPropertyValue("serviceBean").getValue();
		Serializable ssb = (Serializable) pvs.getPropertyValue("serializableBean").getValue();

		assertNotNull(msb);
		assertNotNull(ssb);

		assertSame(bean1.getId(), msb.getId());
		assertSame(bean2.toString(), ssb.toString());
	}

	public void testServiceBeanInjectedValues() throws Exception {
		ServiceBean bean = new ServiceBean();
		final MyService bean1 = new MyService() {

			public Object getId() {
				return this;
			}
		};
		final Serializable bean2 = new Serializable() {

			public String toString() {
				return "bean2";
			}
		};

		BundleContext context = new MockBundleContext() {

			public Object getService(org.osgi.framework.ServiceReference reference) {
				String clazz = ((String[]) reference.getProperty(Constants.OBJECTCLASS))[0];
				if (clazz == null)
					return null;
				else if (clazz.equals(MyService.class.getName())) {
					return bean1;
				}
				else if (clazz.equals(Serializable.class.getName())) {
					return bean2;
				}
				return null;
			}

		};

		ServiceReferenceInjectionBeanPostProcessor p = new ServiceReferenceInjectionBeanPostProcessor();
		p.setBundleContext(context);
		p.setBeanClassLoader(getClass().getClassLoader());
		PropertyValues pvs = p.postProcessPropertyValues(new MutablePropertyValues(), new PropertyDescriptor[] {
			new PropertyDescriptor("serviceBean", ServiceBean.class),
			new PropertyDescriptor("serializableBean", ServiceBean.class) }, bean, "myBean");

		MyService msb = (MyService) pvs.getPropertyValue("serviceBean").getValue();
		Serializable ssb = (Serializable) pvs.getPropertyValue("serializableBean").getValue();

		assertNotNull(msb);
		assertNotNull(ssb);

		assertSame(bean1.getId(), msb.getId());
		assertSame(bean2.toString(), ssb.toString());
	}

	protected Object getPrivateProperty(final Object target, final String fieldName) {
		final Field foundField[] = new Field[1];

		ReflectionUtils.doWithFields(target.getClass(), new FieldCallback() {

			public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
				field.setAccessible(true);
				foundField[0] = field;
			}

		}, new FieldFilter() {

			public boolean matches(Field field) {
				return fieldName.equals(field.getName());
			}

		});

		try {
			return foundField[0].get(target);
		}
		catch (Exception ex) {
			// translate
			throw new RuntimeException(ex);
		}
	}
}