/*
 * 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.publish.uddi.wsdl;

import java.io.IOException;
import java.util.Collection;

import javax.wsdl.Definition;
import javax.wsdl.Port;
import javax.wsdl.Service;
import javax.wsdl.extensions.ExtensibilityElement;
import javax.wsdl.extensions.soap.SOAPAddress;
import javax.wsdl.factory.WSDLFactory;
import javax.wsdl.xml.WSDLReader;

import org.uddi4j.UDDIException;
import org.uddi4j.client.UDDIProxy;
import org.uddi4j.datatype.OverviewDoc;
import org.uddi4j.datatype.binding.AccessPoint;
import org.uddi4j.datatype.binding.InstanceDetails;
import org.uddi4j.datatype.binding.TModelInstanceDetails;
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.AuthToken;
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.WsdlPublisher;

/**
 * This WsdlPublisher can publish WSDL files in UDDI following the UDDI.org best
 * practices 1.08 mapping
 * 
 * @author tilmann
 */
public class WsdlPublisherV01_08 implements WsdlPublisher {

	/**
	 * Value name vor keyd references specifying WSDL documents
	 */
	public static final String WSDL_SPEC_TMODEL_KEY_VALUE = "wsdlSpec";

	private UddiUtils uddiUtils;

	/**
	 * Create a new WsdlPublisherV01_08
	 * 
	 * @param uddiProxy
	 *            the UDDIProxy to use
	 * @param authToken
	 *            the UDDI credentials to use
	 */
	public WsdlPublisherV01_08(UDDIProxy uddiProxy, AuthToken authToken) {
		this.uddiUtils = new UddiUtils(uddiProxy, authToken);
	}

	/**
	 * Publish all services defined in the WSDL identified by the given URL.
	 * Services will be published belonging to the business with the given key.
	 * 
	 * @param wsdlUrl
	 *            the URL of the WSDL file
	 * @param businessKey
	 *            the key of the business
	 * @throws IOException
	 */
	public void publishWsdl(String wsdlUrl, String businessKey)
			throws IOException {

		try {

			// Read WSDL file and retrieve Services
			WSDLFactory wsdlFactory = WSDLFactory.newInstance();
			WSDLReader reader = wsdlFactory.newWSDLReader();
			Definition wsdl = reader.readWSDL(wsdlUrl);
			Collection<Service> services = wsdl.getServices().values();

			// Publish WSDL reference
			TModel wsdlRefTModel = publishWsdlReference(wsdl
					.getTargetNamespace(), wsdl.getDocumentBaseURI());

			// Publish all services
			for (Service service : services) {

				// Construct CategoryBag
				CategoryBag categoryBag = new CategoryBag();
				addServiceCategoryBagKeyedReferences(categoryBag, service);
				if (categoryBag.size() == 0)
					categoryBag = null;

				// Publish Service
				BusinessService businessService = uddiUtils.publishService(
						service.getQName().getLocalPart(), null, businessKey,
						categoryBag);

				// BingingTemplate for each Port
				Collection<Port> ports = service.getPorts().values();
				for (Port port : ports) {

					// Find SOAP-Address
					Collection<ExtensibilityElement> exElements = port
							.getExtensibilityElements();
					String httpAddress = null;

					for (ExtensibilityElement element : exElements) {
						if (element instanceof SOAPAddress) {
							String uri = ((SOAPAddress) element)
									.getLocationURI();
							if (uri.startsWith("http"))
								httpAddress = uri;
						}
					}
					if (httpAddress == null)
						throw new IllegalArgumentException(
								"The WSDL does not contain a SOAP http access point.");

					
					// Create TModelInstanceInfos
					TModelInstanceDetails tModelInstanceDetails = new TModelInstanceDetails();
					TModelInstanceInfo tModelInstanceInfo = new TModelInstanceInfo(
							wsdlRefTModel.getTModelKey());
					InstanceDetails instanceDetails = new InstanceDetails();

					OverviewDoc oDoc = new OverviewDoc();
					oDoc.setOverviewURL(wsdl.getDocumentBaseURI() + "#"
							+ port.getName());
					instanceDetails.setOverviewDoc(oDoc);

					tModelInstanceInfo.setInstanceDetails(instanceDetails);
					tModelInstanceDetails.add(tModelInstanceInfo);

					addBindingTemplateTModelInstaceInfos(tModelInstanceDetails,
							port);

					AccessPoint ap = new AccessPoint(httpAddress, "http");
					
					// Publish BindingTemplate
					uddiUtils.publishBindingTemplate(ap, businessService
							.getServiceKey(), tModelInstanceDetails);
				}
			}
		} catch (Exception e) {
			IOException ioe = new IOException("Communication or WSDL problem.");
			ioe.initCause(e);
			throw ioe;
		}
	}

	/**
	 * This is a hook method for subclasses to add their own TModelInstanceInfos
	 * to the published BindingTemplate.
	 * 
	 * @param tModelInstanceDetails
	 *            here can the TModelInstanceInfos be added
	 * @param port
	 *            the Port for which the BindingTemplate is published
	 * @throws UDDIException
	 * @throws TransportException
	 */
	protected void addBindingTemplateTModelInstaceInfos(
			TModelInstanceDetails tModelInstanceDetails, Port port)
			throws UDDIException, TransportException {

	}

	/**
	 * This is a hook method for subclasses to add their own KeyedReferences to
	 * a published Services CategoryBag.
	 * 
	 * @param categoryBag
	 *            here can KeyedReferences be added
	 * @param service
	 *            the Service that is published
	 * @throws UDDIException
	 * @throws TransportException
	 */
	protected void addServiceCategoryBagKeyedReferences(
			CategoryBag categoryBag, Service service) throws UDDIException,
			TransportException {

	}

	/**
	 * Publish a WSDL reference tModel according to UDDI.org best practices 1.07
	 * 
	 * @param tModelName
	 *            the name of the TModel
	 * @param wsdlUri
	 *            the URI of the WSDL file
	 * @return the newly published TModel
	 * @throws UDDIException
	 * @throws TransportException
	 */
	private TModel publishWsdlReference(String tModelName, String wsdlUri)
			throws UDDIException, TransportException {
		CategoryBag cBag = new CategoryBag();
		KeyedReference kRef = new KeyedReference();
		kRef.setTModelKey(UddiTModels.TYPES.getKey());
		kRef.setKeyName(UddiTModels.TYPES.getName());
		kRef.setKeyValue(WSDL_SPEC_TMODEL_KEY_VALUE);
		cBag.add(kRef);
		return uddiUtils.publishTModel(tModelName, "WSDL Reference", wsdlUri,
				cBag);
	}

	/**
	 * Publish all services defined in the WSDL identified by the given URL.
	 * Search for a business with the given name to publish the services for. If
	 * none can be found publish a new business with given name and description.
	 * 
	 * @param wsdlUrl
	 *            the URL of the WSDL file
	 * @param businessName
	 *            the name of the business to search for
	 * @param businessDescription
	 *            the description of the business to be published
	 * @throws IOException
	 */
	public void publishWsdl(String wsdlUrl, String businessName,
			String businessDescription) throws IOException {

		BusinessEntity businessEntity = null;

		try {
			businessEntity = uddiUtils.publishBusiness(businessName,
					businessDescription);
		} catch (Exception e) {
			IOException ioe = new IOException("Communication or WSDL problem.");
			ioe.initCause(e);
			throw ioe;
		}
		if (businessEntity != null)
			publishWsdl(wsdlUrl, businessEntity.getBusinessKey());

	}

}
