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

import java.util.Dictionary;
import java.util.Hashtable;
import java.util.Map;
import java.util.Properties;

import junit.framework.TestCase;

import org.easymock.MockControl;
import org.eclipse.gemini.blueprint.context.support.BundleContextAwareProcessor;
import org.eclipse.gemini.blueprint.service.exporter.support.ServicePropertiesChangeEvent;
import org.eclipse.gemini.blueprint.service.exporter.support.ServicePropertiesChangeListener;
import org.eclipse.gemini.blueprint.service.exporter.support.ServicePropertiesListenerManager;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.cm.ManagedService;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.eclipse.gemini.blueprint.mock.MockBundleContext;

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

	private GenericApplicationContext appContext;

	private BundleContext bundleContext;

	private MockControl adminControl;

	private ConfigurationAdmin admin;

	private Dictionary<String, String> config;

	private String persistentId = "foo.bar";
	private Configuration cfg;
	private ManagedService msCallback;


	protected void setUp() throws Exception {

		adminControl = MockControl.createControl(ConfigurationAdmin.class);
		admin = (ConfigurationAdmin) adminControl.getMock();
		MockControl configMock = MockControl.createControl(Configuration.class);
		cfg = (Configuration) configMock.getMock();

		config = new Hashtable<String, String>();

		adminControl.expectAndReturn(admin.getConfiguration(persistentId), cfg, MockControl.ONE_OR_MORE);
		configMock.expectAndReturn(cfg.getProperties(), config, MockControl.ONE_OR_MORE);

		adminControl.replay();
		configMock.replay();

		bundleContext = new MockBundleContext() {

			// add Configuration admin support
			@Override
			public Object getService(ServiceReference reference) {
				return admin;
			}

			// ManagedService registration
			@Override
			public ServiceRegistration registerService(String clazz, Object service, Dictionary properties) {
				// save the callback
				if (ManagedService.class.getName().equals(clazz)) {
					msCallback = (ManagedService) service;
				}
				return super.registerService(clazz, service, properties);
			}
		};

		appContext = new GenericApplicationContext();
		appContext.getBeanFactory().addBeanPostProcessor(new BundleContextAwareProcessor(bundleContext));

		XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(appContext);
		// reader.setEventListener(this.listener);
		reader.loadBeanDefinitions(new ClassPathResource("configProperties.xml", getClass()));
		appContext.refresh();
	}

	protected void tearDown() throws Exception {
		adminControl.verify();
	}

	public void testPropertiesLazyInit() throws Exception {
		adminControl.reset();
		adminControl.replay();
	}

	public void testBlankConfigProperties() throws Exception {
		config.put("Spring", "Source");
		Object bean = appContext.getBean("named");
		assertTrue(bean instanceof Properties);
		assertEquals(config, bean);
	}

	public void testPropertiesWithDefaultsAndNoOverride() throws Exception {
		persistentId = "noLocalOverride";

		adminControl.reset();
		adminControl.expectAndReturn(admin.getConfiguration(persistentId), cfg, MockControl.ONE_OR_MORE);
		adminControl.replay();

		config.put("foo", "foo");
		config.put("Spring", "Source");
		Object bean = appContext.getBean(persistentId);
		assertTrue(bean instanceof Properties);
		Properties props = (Properties) bean;
		assertFalse(config.equals(bean));
		// the local property has been replaced
		assertEquals("foo", props.getProperty("foo"));
		// but the one not present on the CM are still present
		assertTrue(props.containsKey("kry"));
		assertTrue(props.containsKey("Spring"));
		assertEquals(3, props.entrySet().size());
	}

	public void testPropertiesWithDefaultsAndOverride() throws Exception {
		persistentId = "localOverride";

		adminControl.reset();
		adminControl.expectAndReturn(admin.getConfiguration(persistentId), cfg, MockControl.ONE_OR_MORE);
		adminControl.replay();

		config.put("foo", "foo");
		config.put("Spring", "Source");
		Object bean = appContext.getBean(persistentId);
		assertTrue(bean instanceof Properties);
		Properties props = (Properties) bean;
		assertFalse(config.equals(bean));
		// the local property is still present
		assertEquals("bar", props.getProperty("foo"));
		// the CM props are still there
		assertTrue(props.containsKey("kry"));
		// and so are the local props
		assertTrue(props.containsKey("Spring"));
		assertEquals(3, props.entrySet().size());
	}

	// disabled until custom attributes are enabled again
	public void tstPropertiesWithPropRef() throws Exception {
		persistentId = "custom-attributes";

		adminControl.reset();
		adminControl.expectAndReturn(admin.getConfiguration(persistentId), cfg, MockControl.ONE_OR_MORE);
		adminControl.replay();

		config.put("foo", "foo");
		config.put("Spring", "Source");
		Object bean = appContext.getBean(persistentId);
		BeanDefinition bd = appContext.getBeanDefinition(persistentId);
		System.out.println(bd.getScope());
		assertTrue(bean instanceof Properties);
		Properties props = (Properties) bean;
		assertFalse(config.equals(bean));
		// the local property is still present
		assertEquals("bar", props.getProperty("foo"));
		// the CM props are still there
		assertTrue(props.containsKey("kry"));
		// and so are the local props
		assertTrue(props.containsKey("Spring"));
		assertEquals(3, props.entrySet().size());
	}

	public void testDynamicNoOverride() throws Exception {
		persistentId = "noLocalOverride";
		String beanId = "dynamic-noOverride";

		adminControl.reset();
		adminControl.expectAndReturn(admin.getConfiguration(persistentId), cfg, MockControl.ONE_OR_MORE);
		adminControl.replay();

		// initial config
		config.put("bo", "bozo");
		config.put("Spring", "Source");
		Object bean = appContext.getBean(beanId);
		assertTrue(bean instanceof Properties);
		Properties props = (Properties) bean;
		assertFalse(config.equals(bean));
		// the local property has been replaced
		assertEquals("bozo", props.getProperty("bo"));
		// but the one not present on the CM are still present
		assertTrue(props.containsKey("kry"));
		assertEquals("pton", props.getProperty("kry"));
		assertEquals("Source", props.getProperty("Spring"));
		assertEquals(3, props.entrySet().size());

		// CM updates
		assertNotNull(msCallback);

		Dictionary<String, String> newProps = new Hashtable<String, String>();
		newProps.put("bo", "b0z0");
		newProps.put("new", "prop");
		// trigger update
		msCallback.updated(newProps);

		// verify properties
		assertSame(props, appContext.getBean(beanId));
		assertEquals("b0z0", props.getProperty("bo"));
		assertEquals("prop", props.getProperty("new"));
		assertNull(props.getProperty("Spring"));
		// verify local properties
		assertEquals("pton", props.getProperty("kry"));
	}

	public void testDynamicOverride() throws Exception {
		persistentId = "localOverride";
		String beanId = "dynamic-override";

		adminControl.reset();
		adminControl.expectAndReturn(admin.getConfiguration(persistentId), cfg, MockControl.ONE_OR_MORE);
		adminControl.replay();

		// initial config
		config.put("bo", "bozo");
		config.put("Spring", "Source");
		Object bean = appContext.getBean(beanId);
		assertTrue(bean instanceof Properties);
		Properties props = (Properties) bean;
		assertFalse(config.equals(bean));
		// the local property has been replaced
		assertEquals("zo", props.getProperty("bo"));
		// but the one not present on the CM are still present
		assertEquals("pton", props.getProperty("kry"));
		assertEquals("Source", props.getProperty("Spring"));
		assertEquals(3, props.entrySet().size());

		// CM updates
		assertNotNull(msCallback);

		Dictionary<String, String> newProps = new Hashtable<String, String>();
		newProps.put("bo", "b0z0");
		newProps.put("new", "prop");
		// trigger update
		msCallback.updated(newProps);

		// verify properties
		assertSame(props, appContext.getBean(beanId));
		assertEquals("zo", props.getProperty("bo"));
		assertEquals("prop", props.getProperty("new"));
		assertNull(props.getProperty("Spring"));
		// verify local properties
		assertEquals("pton", props.getProperty("kry"));
	}

	public void testExtendedProperties() throws Exception {
		persistentId = "noLocalOverride";
		String beanId = "dynamic-noOverride";

		adminControl.reset();
		adminControl.expectAndReturn(admin.getConfiguration(persistentId), cfg, MockControl.ONE_OR_MORE);
		adminControl.replay();

		// initial config
		config.put("bo", "bozo");
		config.put("Spring", "Source");
		Object bean = appContext.getBean(beanId);
		assertTrue(bean instanceof Properties);
		assertTrue(bean instanceof ServicePropertiesListenerManager);
		Properties props = (Properties) bean;
		ServicePropertiesListenerManager manager = (ServicePropertiesListenerManager) bean;

		final Map<?, ?>[] updatedProps = new Map<?, ?>[1];
		manager.addListener(new ServicePropertiesChangeListener() {

			public void propertiesChange(ServicePropertiesChangeEvent event) {
				updatedProps[0] = event.getServiceProperties();
			}
		});

		// CM updates
		assertNotNull(msCallback);

		Dictionary<String, String> newProps = new Hashtable<String, String>();
		newProps.put("bo", "b0z0");
		newProps.put("new", "prop");
		// trigger update
		msCallback.updated(newProps);

		// verify listener properties
		assertNotNull(updatedProps[0]);
		// the properties contains both the CM props
		assertEquals("b0z0", updatedProps[0].get("bo"));
		assertEquals("prop", updatedProps[0].get("new"));
		// and the local defined ones
		assertEquals("pton", updatedProps[0].get("kry"));
		assertEquals(3, updatedProps[0].size());
	}
}