/*
 * This source code is made available under the terms of the
 * Common Public License Version 1.0 distibuted along with the
 * source code package.
 */
package de.uka.cmtm.serviceregistry.query.uddi;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.uddi4j.UDDIException;
import org.uddi4j.client.UDDIProxy;
import org.uddi4j.datatype.binding.BindingTemplate;
import org.uddi4j.datatype.binding.TModelInstanceInfo;
import org.uddi4j.datatype.business.BusinessEntity;
import org.uddi4j.datatype.service.BusinessService;
import org.uddi4j.datatype.tmodel.TModel;
import org.uddi4j.response.BusinessInfo;
import org.uddi4j.response.ServiceInfo;
import org.uddi4j.transport.TransportException;
import org.uddi4j.util.CategoryBag;
import org.uddi4j.util.KeyedReference;

import de.uka.cmtm.serviceregistry.publish.uddi.UddiTModels;
import de.uka.cmtm.serviceregistry.publish.uddi.UddiUtils;
import de.uka.cmtm.serviceregistry.publish.uddi.wsdl.WsdlPublisherV01_08;
import de.uka.cmtm.serviceregistry.query.ServiceInstanceInfo;
import de.uka.cmtm.serviceregistry.query.ServiceInstanceLocator;
import de.uka.cmtm.serviceregistry.query.ServiceInstanceParameter;

/**
 * A ServiceInstanceLocator that uses UDDI to locate service instances and the
 * UDDI.org best practices 1.08 UDDI WSDL mapping.
 * 
 * @author tilmann
 */
public class UddiServiceInstanceLocatorV01_08 implements ServiceInstanceLocator {

	private UddiUtils uddiUtils;

	/**
	 * Creates a new UddiServiceInstanceLocatorV01_08
	 * 
	 * @param uddiProxy
	 *            the UDDIProxy to be used
	 */
	public UddiServiceInstanceLocatorV01_08(UDDIProxy uddiProxy) {
		this.uddiUtils = new UddiUtils(uddiProxy);
	}

	/**
	 * Cache used to prevent repeated UDDI lookups
	 */
	private Map<String, String> wsdlLocationCache = new HashMap<String, String>();

	/**
	 * Cache used to prevent repeated UDDI lookups
	 */
	private Map<String, String> businessNameCache = new HashMap<String, String>();

	/**
	 * Invoce this method to search for service instances that conform to the
	 * given search parameters.
	 * 
	 * @param searchParameter
	 *            the parameter used during search
	 * @return all instances found
	 */
	public List<ServiceInstanceInfo> findSeviceInstances(
			ServiceInstanceParameter searchParameter) throws IOException {

		try {
			List<ServiceInstanceInfo> results = new LinkedList<ServiceInstanceInfo>();

			// Retrieve service profile TModel
			TModel serviceProfileTModel = findServiceProfileTModel();

			if (serviceProfileTModel != null) {

				// Retrieve businesses
				String businessName = searchParameter.getBusinessName();
				Collection<BusinessInfo> businessInfos;

				if (businessName != null && !"".equals(businessName)) {

					businessInfos = uddiUtils.findBusinesses(businessName,
							Integer.MAX_VALUE);
				} else {
					businessInfos = new ArrayList<BusinessInfo>(1);
					businessInfos.add(new BusinessInfo(null, null, null));
				}

				// Prepare category bag with service profile reference
				CategoryBag categoryBag = new CategoryBag();
				KeyedReference kRef = new KeyedReference();
				kRef.setTModelKey(serviceProfileTModel.getTModelKey());
				kRef.setKeyValue(searchParameter.getProfileUri());
				categoryBag.add(kRef);

				// Traverse all businesses
				for (BusinessInfo businessInfo : businessInfos) {

					// Clear caches
					wsdlLocationCache.clear();
					businessNameCache.clear();

					// Retrieve services
					Collection<ServiceInfo> serviceInfos = uddiUtils
							.findServices(businessInfo.getBusinessKey(),
									categoryBag, Integer.MAX_VALUE);

					// Put business name in Cache
					if (businessInfo.getBusinessKey() != null
							&& businessInfo.getDefaultNameString() != null) {
						businessNameCache.put(businessInfo.getBusinessKey(),
								businessInfo.getDefaultNameString());
					}

					// Traverse all services
					for (ServiceInfo serviceInfo : serviceInfos) {

						// Create new result
						ServiceInstanceInfo sif = new ServiceInstanceInfo();
						sif.setBusinessName(findBusinessName(serviceInfo
								.getBusinessKey()));
						sif.setProfileUri(searchParameter.getProfileUri());
						sif.setRelevance(0);
						sif.setServiceName(serviceInfo.getDefaultNameString());
						sif.setWsdlLocation(findWsdlLocation(serviceInfo
								.getServiceKey()));
						results.add(sif);
					}
				}
			}

			return results;

		} catch (UDDIException e) {
			IOException ioe = new IOException(
					"Problem while communicating with UDDI.");
			ioe.initCause(e);
			throw ioe;
		} catch (TransportException e) {
			IOException ioe = new IOException(
					"Problem while communicating with UDDI.");
			ioe.initCause(e);
			throw ioe;
		}
	}

	/**
	 * Retrieve the name of the business with the given key and put it in the
	 * cache.
	 * 
	 * @param businessKey
	 *            the key
	 * @return the name
	 * @throws UDDIException
	 * @throws TransportException
	 */
	private String findBusinessName(String businessKey) throws UDDIException,
			TransportException {

		// Check the cache first
		String businessName = businessNameCache.get(businessKey);

		// Search if not present
		if (businessName == null) {
			BusinessEntity businessEntity = uddiUtils
					.getBusinessEntity(businessKey);
			businessName = businessEntity.getDefaultNameString();
			businessNameCache.put(businessKey, businessName);
		}
		return businessName;
	}

	/**
	 * Retrieve the WSDL URL of the service with the given key an put it in the
	 * cache.
	 * 
	 * @param serviceKey
	 *            the key
	 * @return the WSDL URL
	 * @throws UDDIException
	 * @throws TransportException
	 */
	private String findWsdlLocation(String serviceKey) throws UDDIException,
			TransportException {

		// Check the cache first
		String wsdlLocation = wsdlLocationCache.get(serviceKey);

		// Search if not present
		if (wsdlLocation == null) {
			BusinessService businessService = uddiUtils
					.getBusinessService(serviceKey);

			// For all BingingTemplates in the BusinessService
			Collection<BindingTemplate> bindingTemplates = businessService
					.getBindingTemplates().getBindingTemplateVector();
			for (BindingTemplate bindingTemplate : bindingTemplates) {

				// For all TModelInstanceInfos in the BindingTemplate
				Collection<TModelInstanceInfo> tModelInstanceInfos = bindingTemplate
						.getTModelInstanceDetails()
						.getTModelInstanceInfoVector();
				for (TModelInstanceInfo tModelInstanceInfo : tModelInstanceInfos) {

					// Retrieve TModel
					TModel tModel = uddiUtils.getTModel(tModelInstanceInfo
							.getTModelKey());

					// For all KeyedReferences in the TModels CategoryBag
					Collection<KeyedReference> keyedReferences = tModel
							.getCategoryBag().getKeyedReferenceVector();
					for (KeyedReference keyedReference : keyedReferences) {

						// Check if it is a WSDL reference
						if (UddiTModels.TYPES.getKey().equals(
								keyedReference.getTModelKey())
								&& WsdlPublisherV01_08.WSDL_SPEC_TMODEL_KEY_VALUE
										.equals(keyedReference.getKeyValue())) {

							wsdlLocation = tModel.getOverviewDoc()
									.getOverviewURLString();
							wsdlLocationCache.put(serviceKey, wsdlLocation);

							// Abort iteration if found
							return wsdlLocation;
						}
					}
				}
			}
		}
		return wsdlLocation;
	}

	/**
	 * Search the TModel used for the service profile reference.
	 * 
	 * @return the TModel
	 * @throws UDDIException
	 * @throws TransportException
	 */
	private TModel findServiceProfileTModel() throws UDDIException,
			TransportException {

		CategoryBag cBag = new CategoryBag();
		KeyedReference kRef = new KeyedReference();
		kRef.setTModelKey(UddiTModels.TYPES.getKey());
		kRef.setKeyName(UddiTModels.TYPES.getName());
		kRef.setKeyValue("categorization");
		cBag.add(kRef);
		return uddiUtils
				.findTModel(UddiTModels.SERVICE_PROFILE.getName(), cBag);
	}

}
