/******************************************************************************
 * 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.blueprint.container;

import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.eclipse.gemini.blueprint.blueprint.container.support.BlueprintEditorRegistrar;
import org.eclipse.gemini.blueprint.context.support.internal.security.SecurityUtils;
import org.osgi.service.blueprint.container.Converter;
import org.osgi.service.blueprint.container.ReifiedType;
import org.springframework.beans.SimpleTypeConverter;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionException;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;

/**
 * OSGi 4.2 Blueprint converter adapter as an Spring 3.0 ConverterService.
 * 
 * @author Costin Leau
 */
public class SpringBlueprintConverterService implements ConversionService {

	private static final class BlueprintConverterException extends ConversionException {

		public BlueprintConverterException(String message, Throwable cause) {
			super(message, cause);
		}

		public BlueprintConverterException(String message) {
			super(message);
		}
	}

	/** fallback delegate */
	private final ConversionService delegate;
	private final List<Converter> converters = new ArrayList<Converter>();
	private final SimpleTypeConverter typeConverter;
	// used for grabbing the security context
	private final ConfigurableBeanFactory cbf;
	private volatile boolean converterInitialized = false;

	public SpringBlueprintConverterService(ConversionService delegate, ConfigurableBeanFactory cbf) {
		this.delegate = delegate;
		this.cbf = cbf;
		this.typeConverter = new SimpleTypeConverter();
	}

	public void add(Converter blueprintConverter) {
		synchronized (converters) {
			converters.add(blueprintConverter);
		}
	}

	public void add(Collection<Converter> blueprintConverters) {
		synchronized (converters) {
			converters.addAll(blueprintConverters);
		}
	}

	public boolean canConvert(Class<?> sourceType, Class<?> targetType) {
		return true;
	}

	public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) {
		return true;
	}

	@SuppressWarnings("unchecked")
	public <T> T convert(Object source, Class<T> targetType) {
		return (T) convert(source, TypeDescriptor.forObject(source), TypeDescriptor.valueOf(targetType));
	}

	public Object convert(final Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
		if (targetType == TypeDescriptor.NULL)
			return source;
		
		final ReifiedType type = TypeFactory.getType(targetType);
		boolean hasSecurity = (System.getSecurityManager() != null);
		AccessControlContext acc = (hasSecurity ? SecurityUtils.getAccFrom(cbf) : null);
		Object result = null;

		if (hasSecurity) {
			result = AccessController.doPrivileged(new PrivilegedAction<Object>() {
				public Object run() {
					return doConvert(source, type);
				}
			}, acc);
		} else {
			result = doConvert(source, type);
		}

		if (result != null) {
			return result;
		}

		MethodParameter mp = targetType.getMethodParameter();
		Class<?> tType = (mp != null && mp.getNestingLevel() > 1 ? null : targetType.getType());

		if (!targetType.isCollection() && !targetType.isArray() && !targetType.isMap()) {
			if (type.size() > 0) {
				for (int i = 0; i < type.size(); i++) {
					ReifiedType arg = type.getActualTypeArgument(i);
					if (!Object.class.equals(arg.getRawClass())) {
						throw new BlueprintConverterException(
								"No conversion found for generic argument(s) for reified type " + arg.getRawClass()
										+ "source type " + sourceType + "| targetType =" + tType, null);
					}
				}
			}
		}

		if (delegate != null) {
			delegate.convert(source, sourceType, targetType);
		}

		lazyInitConverter();
		return typeConverter.convertIfNecessary(source, tType, targetType.getMethodParameter());
	}

	private void lazyInitConverter() {
		if (!converterInitialized) {
			synchronized (typeConverter) {
				if (!converterInitialized) {
					converterInitialized = true;
					if (cbf != null) {
						cbf.copyRegisteredEditorsTo(typeConverter);
						new BlueprintEditorRegistrar().registerCustomEditors(typeConverter);
					}
				}
			}
		}
	}

	private Object doConvert(Object source, ReifiedType type) {
		synchronized (converters) {
			for (Converter converter : converters) {
				try {
					if (converter.canConvert(source, type)) {
						return converter.convert(source, type);
					}
				} catch (Exception ex) {
					throw new BlueprintConverterException("Conversion between source " + source + " and reified type "
							+ type + " failed", ex);
				}
			}
		}
		return null;
	}
}