/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2006, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.soa.bpel.uddi;

import java.net.URL;
import java.rmi.RemoteException;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.wsdl.Definition;
import javax.wsdl.Port;
import javax.wsdl.factory.WSDLFactory;
import javax.wsdl.xml.WSDLReader;
import javax.xml.namespace.QName;

import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.juddi.api_v3.AccessPointType;
import org.apache.juddi.v3.annotations.AnnotationProcessor;
import org.apache.juddi.v3.client.config.TokenResolver;
import org.apache.juddi.v3.client.config.UDDIClerk;
import org.apache.juddi.v3.client.config.UDDIClerkManager;
import org.apache.juddi.v3.client.config.UDDIClientContainer;
import org.apache.juddi.v3.client.transport.TransportException;
import org.jboss.soa.bpel.runtime.engine.ode.UDDIRegistration;
import org.jboss.soa.bpel.runtime.ws.WSDLReference;
import org.jboss.soa.bpel.runtime.ws.WebServiceClient;
import org.uddi.api_v3.AccessPoint;
import org.uddi.api_v3.BindingTemplate;
import org.uddi.api_v3.BindingTemplates;
import org.uddi.api_v3.BusinessService;
import org.uddi.api_v3.CategoryBag;
import org.uddi.api_v3.Description;
import org.uddi.api_v3.FindQualifiers;
import org.uddi.api_v3.FindService;
import org.uddi.api_v3.GetAuthToken;
import org.uddi.api_v3.Name;
import org.uddi.api_v3.ServiceInfo;
import org.uddi.api_v3.ServiceList;
import org.uddi.api_v3.TModelInstanceDetails;
import org.uddi.api_v3.TModelInstanceInfo;
import org.uddi.v3_service.DispositionReportFaultMessage;

/**
 * 
 * * @author Kurt T Stam <kurt.stam@jboss.com>
 *
 */
public class UDDIRegistrationImpl extends AnnotationProcessor implements UDDIRegistration {

	protected static final Log log = LogFactory.getLog(UDDIRegistrationImpl.class);
	private final static String BPEL_UDDI_CLERK_MANAGER = "bpel.uddi.clerk.manager";
	private final static String BPEL_UDDI_CLERK = "bpel.uddi.clerk";
	private final static String BPEL_UDDI_CONFIG = "bpel.uddi.config";
	private final static String DEFAULT_BPEL_UDDI_CLERK_MANAGER = "esb.manager";
	private final static String DEFAULT_BPEL_UDDI_CLERK = "BPELClerk";
	private Properties properties = new Properties();
	UDDIClerk bpelClerk = null;
	
	public static final String DEFAULT_SERVICE_KEY_FORMAT   = "uddi:${keyDomain}:bpel-services-";
	public static final String DEFAULT_BINDING_KEY_FORMAT   = "uddi:${keyDomain}:bindings-";
	//Can be set in the uddi.xml property section
	public static final String LANG                         = "lang";
	public static final String SERVICE_DESCRIPTION          = "serviceDescription";
	public static final String SERVICE_CATEGORY_BAG         = "serviceCategoryBag";
	public static final String BINDING_DESCRIPTION          = "bindingDescription";
	public static final String BINDING_CATEGORY_BAG         = "bindingCategoryBag";
	public static final String BINDING_TMODEL_KEYS          = "bindingTModelKeys";
	public static final String SERVICE_KEY_FORMAT           = "serviceKeyFormat";
	public static final String BINDING_KEY_FORMAT           = "bindingKeyFormat";
	
	private String clerkManagerName = null;
	
	
	public UDDIRegistrationImpl(Properties properties) {
		super();
		this.properties = properties;
		String bpelUDDIConfig = getBPELUDDIConfig();
		if (bpelUDDIConfig!=null) {
			try {
				UDDIClerkManager clerkManager = new UDDIClerkManager(bpelUDDIConfig);
		        clerkManager.start();
		        clerkManagerName = clerkManager.getName();
			} catch (Exception e) {
				log.error(e.getMessage(),e);
			}
		} else {
			log.debug("Using jbossesb.sar/esb.uddi.client.xml");
		}
	}

	private String getBPELUDDIConfig() {
		return (properties.getProperty(BPEL_UDDI_CONFIG));
	}
	
	private String getClerkManagerName() {
		if (clerkManagerName!=null) {
			log.debug("ClerkManagerName is " + clerkManagerName);
			return clerkManagerName;
		} else {
			String clerkManager = (properties.getProperty(BPEL_UDDI_CLERK_MANAGER, DEFAULT_BPEL_UDDI_CLERK_MANAGER));
			log.debug("ClerkManagerName is " + clerkManager);
			return clerkManager;
		}
	}
	
	private String getClerkName() {
	      return (properties.getProperty(BPEL_UDDI_CLERK, DEFAULT_BPEL_UDDI_CLERK));
	}
	
	private UDDIClerk getBPELClerk() throws ConfigurationException {
		if (bpelClerk==null) {
			UDDIClerkManager clerkManager = UDDIClientContainer.getUDDIClerkManager(getClerkManagerName());
			Map<String,UDDIClerk> clerks = clerkManager.getClientConfig().getUDDIClerks();
			UDDIClerk bpelClerk = clerks.get(getClerkName());
			if (bpelClerk==null) {
				throw new ConfigurationException("Could not find UDDI Clerk named "+ getClerkName());
			}
		}
		return bpelClerk;
	}
	 
	/**
	 * Registers a BPEL ServiceEndpointReference (EPR) into a UDDI registry using the jUDDI client code.
	 * If the serviceKey does not already exist we register the service along with the EPR.
	 * 
	 * @param EPR
	 * @param metaData
	 * @param wsdlRef
	 */
	public void registerEPR(String serviceName, String portName, URL accessUrl) {
		boolean selfRegister = false;
		try {
			UDDIClerkManager clerkManager = UDDIClientContainer.getUDDIClerkManager(getClerkManagerName());
			selfRegister = clerkManager.getClientConfig().isRegisterOnStartup();
			if (selfRegister) {
				Map<String,UDDIClerk> clerks = clerkManager.getClientConfig().getUDDIClerks();
				UDDIClerk bpelClerk = clerks.get(getClerkName());
				if (bpelClerk!=null) {
					try {
						BusinessService service = createBusinessService(bpelClerk, serviceName, portName, accessUrl);
						bpelClerk.register(service, bpelClerk.getUDDINode().getApiNode());
					} catch (Exception e) {
						log.error("Unable to register service " + serviceName
								+ " ." + e.getMessage(),e);
					} catch (Throwable t) {
						log.error("Unable to register service " + serviceName
								+ " ." + t.getMessage(),t);
					}
				} else {
					throw new ConfigurationException("Could not find UDDI Clerk named "+ getClerkName());
				}
			}
		} catch (ConfigurationException ce) {
			if (selfRegister) {
				log.error(ce.getMessage(),ce);
			} else {
				log.info(ce.getMessage());
			}
		}
	}
	/**
	 * UnRegisters the binding from the UDDI Registry. 
	 * @param EPR
	 * @param metaData
	 * @param wsdlRef
	 */
	public void unRegisterEPR(QName service, String port) {
		boolean selfRegister = false;
		try {
			UDDIClerkManager clerkManager = UDDIClientContainer.getUDDIClerkManager("esb-registry");
			selfRegister = clerkManager.getClientConfig().isRegisterOnStartup();
			if (selfRegister) {
				Map<String,UDDIClerk> clerks = clerkManager.getClientConfig().getUDDIClerks();
				UDDIClerk bpelClerk = clerks.get(getClerkName());
				String bindingName = service.getLocalPart() + "-" + port;
				if (bpelClerk!=null) {
					try {
						Properties properties = bpelClerk.getUDDINode().getProperties();
						//Constructing the bindingKey
						String bindingKey = TokenResolver.replaceTokens(DEFAULT_BINDING_KEY_FORMAT + bindingName, properties);
						if (properties.containsKey(BINDING_KEY_FORMAT)) {
							bindingKey = TokenResolver.replaceTokens((String)properties.get(BINDING_KEY_FORMAT) + bindingName, properties);
						}
						bindingKey = bindingKey.toLowerCase();
						//Lookup the binding
						BindingTemplate binding = null;
						try {
							binding = bpelClerk.findServiceBinding(bindingKey, bpelClerk.getUDDINode().getApiNode());
							//TODO Kurt: Just delete this binding -not all bindings- upgrade the jUDDI client to support this.
							BusinessService businessService = bpelClerk.findService(binding.getServiceKey(), bpelClerk.getUDDINode().getApiNode());
							bpelClerk.unRegister(businessService, bpelClerk.getUDDINode().getApiNode());
						} catch (Exception e) {
							log.warn("Could not find BindingTemplate with key " + bindingKey + " for unRegistration.");
						}
					} catch (Exception e) {
						log.error("Unable to unRegister EPR " + bindingName
								+ " ." + e.getMessage(),e);
					} catch (Throwable t) {
						log.error("Unable to unRegister EPR " + bindingName
								+ " ." + t.getMessage(),t);
					}
				} else {
					throw new ConfigurationException("Could not find UDDI Clerk named "+ getClerkName());
				}
			}
		} catch (ConfigurationException ce) {
			if (selfRegister) {
				log.error(ce.getMessage(),ce);
			} else {
				log.info(ce.getMessage());
			}
		}
	}
	
	private BusinessService createBusinessService(UDDIClerk clerk,
			String serviceName, String portName, URL accessUrl) 
		throws DispositionReportFaultMessage, RemoteException, ConfigurationException, TransportException {
		Properties properties = clerk.getUDDINode().getProperties();
		//Constructing the serviceKey
		String serviceKey = TokenResolver.replaceTokens(DEFAULT_SERVICE_KEY_FORMAT + serviceName, properties);
		if (properties.containsKey(SERVICE_KEY_FORMAT)) {
			serviceKey = TokenResolver.replaceTokens((String)properties.get(SERVICE_KEY_FORMAT) + serviceName, properties);
		}
		BusinessService service = null;
		try {
			//Checking if this serviceKey already exist
			service = clerk.findService(serviceKey, clerk.getUDDINode().getApiNode());
			log.debug("Service " + serviceName + " already present in the UDDI Registry");
		} catch (Exception e) {
			//If it does not exist construct service information
			log.debug("Constructing Service UDDI Information for " + serviceName);
			service = new BusinessService();
			service.setBusinessKey(TokenResolver.replaceTokens("uddi:${keyDomain}:${businessKey}", properties));
			service.setServiceKey(serviceKey);
			if (properties.containsKey(SERVICE_DESCRIPTION)) {
				Description description = new Description();
				String lang = "en";
				if (properties.containsKey(LANG)) lang = properties.getProperty(LANG);
				description.setLang(lang);
				description.setValue(properties.getProperty(SERVICE_DESCRIPTION));
				Name sName = new Name();
				sName.setLang(lang);
				sName.setValue(serviceName);
				service.getName().add(sName);
				service.getDescription().add(description);
			}
			//default categoryBag on the binding
			if (properties.containsKey(SERVICE_CATEGORY_BAG)) {
				String defaultCategoryBag = properties.getProperty(SERVICE_CATEGORY_BAG);
				log.info("Adding categoryBag: " + defaultCategoryBag);
				CategoryBag categoryBag = parseCategoryBag(defaultCategoryBag);
		        service.setCategoryBag(categoryBag);
			}
		}
		//Construct bindingTemplate
		BindingTemplate binding = createBindingTemplate(clerk, serviceName, portName, accessUrl);
		//Add the bindingTemplate on the service
		if (service.getBindingTemplates()==null) {
			BindingTemplates bindingTemplates = new BindingTemplates();
			service.setBindingTemplates(bindingTemplates);
		}
		service.getBindingTemplates().getBindingTemplate().add(binding);
		return service;
	}
	
	private BindingTemplate createBindingTemplate(UDDIClerk clerk,
			String serviceName, String portName, URL accessURL) {
		
		Properties properties = clerk.getUDDINode().getProperties();
		BindingTemplate bindingTemplate = new BindingTemplate();
		//Constructing the bindingKey
		String bindingKey = TokenResolver.replaceTokens(DEFAULT_BINDING_KEY_FORMAT + serviceName + "-"
				+ portName, properties);
		if (properties.containsKey(BINDING_KEY_FORMAT)) {
			bindingKey = TokenResolver.replaceTokens((String)properties.get(BINDING_KEY_FORMAT) + serviceName + "-"
					+ portName, properties);
		}
		
		bindingTemplate.setBindingKey(bindingKey);
		
		String lang = "en";
		if (properties.containsKey(LANG)) lang = properties.getProperty(LANG);
		if (properties.containsKey(BINDING_DESCRIPTION)) {
			Description bindingDescription = new Description();
			bindingDescription.setLang(lang);
			bindingDescription.setValue(properties.getProperty(BINDING_DESCRIPTION));
			bindingTemplate.getDescription().add(bindingDescription);
		}
		
		AccessPoint accessPoint = new AccessPoint();
		accessPoint.setUseType(AccessPointType.WSDL_DEPLOYMENT.toString());
		accessURL = rewriteWSDLURL(accessURL);
		accessPoint.setValue(accessURL.toExternalForm());
		bindingTemplate.setAccessPoint(accessPoint);
		
		//default tModelKeys on the binding
		String defaultTModelKeys = properties.getProperty("tModelKeys");
		if (defaultTModelKeys!=null &&  !"".equals(defaultTModelKeys)) {
			String[] tModelKeys= defaultTModelKeys.split(",");
			for (String tModelKey : tModelKeys) {
				TModelInstanceInfo instanceInfo = new TModelInstanceInfo();
				instanceInfo.setTModelKey(tModelKey);
				if (bindingTemplate.getTModelInstanceDetails()==null) {
					bindingTemplate.setTModelInstanceDetails(new TModelInstanceDetails());
				}
				bindingTemplate.getTModelInstanceDetails().getTModelInstanceInfo().add(instanceInfo);
			}
		}
		//default categoryBag on the binding
		String defaultCategoryBag = properties.getProperty("bindingCategoryBag");
		if (defaultCategoryBag!=null &&  !"".equals(defaultCategoryBag)) {
			log.info("Adding categoryBag: " + defaultCategoryBag);
			CategoryBag categoryBag = parseCategoryBag(defaultCategoryBag);
	        bindingTemplate.setCategoryBag(categoryBag);
		}
		
		return bindingTemplate;
	}
	
	/**
	 * Looks up the WSDL for the requested service and portName. The BPELClerk
	 * is used to lookup the service in the UDDI Registry by ServiceName. For
	 * each Service, it will loop over the BindingTemplates. If a BindingTemplate
	 * is found containing an AccessPointType of "wsdlDeployment" then this
	 * URL is used to obtain the WSDL. If successful the WSDLReference is returned,
	 * if not successful the next bindingTemplate is tried. A null result will be
	 * returned if none of the attempts are successful.
	 */
    public WSDLReference lookupWSDL(QName serviceQName, String portName) {
		
		try {
			UDDIClerk bpelClerk = getBPELClerk();
			FindService findService = new FindService();
			findService.setAuthInfo(getAuthToken(bpelClerk));
			Name serviceName = new Name();
			
			String lang = "en";
			if (properties.containsKey(LANG)) lang = properties.getProperty(LANG);
			serviceName.setLang(lang);
			serviceName.setValue(serviceQName.getLocalPart());
			findService.getName().add(serviceName);
			
			FindQualifiers findQualifiers = new FindQualifiers();
			findQualifiers.getFindQualifier().add("exactMatch");
			findService.setFindQualifiers(findQualifiers);
			
			ServiceList serviceList = bpelClerk.getUDDINode().getTransport().getUDDIInquiryService().findService(findService);
			//Loop over all services found.
			if (serviceList.getServiceInfos()!=null) {
			List<ServiceInfo> serviceInfos = serviceList.getServiceInfos().getServiceInfo();
			    for (ServiceInfo serviceInfo : serviceInfos) {
					String serviceKey = serviceInfo.getServiceKey();
					
					BusinessService service = bpelClerk.findService(serviceKey, bpelClerk.getUDDINode().getApiNode());
					BindingTemplates bindingTemplates = service.getBindingTemplates();
					if (bindingTemplates==null) {
						log.warn("Found service " + serviceQName.getLocalPart()
								  + " with serviceKey '" + serviceInfo.getServiceKey() + "'" 
								  + " but no EPRs");
					} else {
						log.info("Found service " + serviceQName.getLocalPart()
								  + " with serviceKey '" + serviceInfo.getServiceKey() + "'" 
								  + " and " + bindingTemplates.getBindingTemplate().size() + " EPRs");
						//Loop over all bindingTemplates found
						for (BindingTemplate bindingTemplate : bindingTemplates.getBindingTemplate()) {
							AccessPoint accessPoint = bindingTemplate.getAccessPoint();
							if (AccessPointType.WSDL_DEPLOYMENT.toString().equals(accessPoint.getUseType())) {
								URL url = null;
								try {
								    url = new URL(accessPoint.getValue());
								    log.info("wsdlUrl for service " + serviceQName.getLocalPart() + " is " + url);
								    WSDLReader wsdlReader = WSDLFactory.newInstance().newWSDLReader();
							        Definition def = wsdlReader.readWSDL(url.toExternalForm());
//								    if (log.isDebugEnabled()) {
//								    	log.debug(getWSDL(url));
//								    }
								    //Checking if this WSDL contains the right port name.
							        Port port = def.getService(serviceQName).getPort(portName);
							        if (port!=null) {
							        	//Current policy: "FirstSuccess" :)
							        	return new WSDLReference(def, url.toURI());
							        } else {
							        	log.info("PortName " + portName + " could not be found in WSDL");
							        }
								} catch (Exception e) {
									log.warn("Unable to obtain WSDL from " + url + ". " + e.getMessage(),e);
								}
						    } else {
						    	log.debug("This accessPoint is of type " + accessPoint.getUseType() + " only " 
						    			+ AccessPointType.WSDL_DEPLOYMENT + " is supported at the moment.");
						    }
						}
					}
				}
			} else {
				log.info("No Service by the name " + serviceQName.getLocalPart() + " was found in the registry.");
			}
		    
		} catch (Exception e) {
			log.error(e.getMessage(),e);
		}
		log.info("No WSDL could be obtained using the UDDI Registry Lookup.");
		return null;
	}
	/**
	 * Obtains an authToken for this clerk. 
	 * 
	 * @param clerk
	 * @return
	 * @throws TransportException
	 * @throws DispositionReportFaultMessage
	 * @throws RemoteException
	 */
	private String getAuthToken(UDDIClerk clerk) throws TransportException, DispositionReportFaultMessage, RemoteException {
		
		String endpointURL = clerk.getUDDINode().getApiNode().getSecurityUrl();
		GetAuthToken getAuthToken = new GetAuthToken();
		getAuthToken.setUserID(clerk.getPublisher());
		getAuthToken.setCred(clerk.getPassword());
		String authToken = clerk.getUDDINode().getTransport().getUDDISecurityService(endpointURL).getAuthToken(getAuthToken).getAuthInfo();
		
		return authToken;
	}
//	/**
//	 * Reads the WSDL from an endpoint URL and returns it in a formatted String.
//	 * 
//	 * @param url - url of a WSDL Endpoint.
//	 * @return - Formatted WSDL
//	 * @throws IOException when there are IO issues reading the WSDL content from the url.
//	 */
//	private String getWSDL(URL url) throws IOException {
//		
//		BufferedInputStream inputStream = (BufferedInputStream) url.getContent();
//		BufferedReader in = new BufferedReader(new InputStreamReader(inputStream));
//		StringBuffer buffer = new StringBuffer();
//		String inputLine;
//        while ((inputLine = in.readLine()) != null) {
//        	buffer.append(inputLine).append("\r\n");
//        }
//        in.close();
//	    return buffer.toString();
//	}
	/**
	 * 
	 * @param urlIn
	 * @return
	 */
	public URL rewriteWSDLURL(URL urlIn) {
		URL outUrl = urlIn;
		try {
			String urlString = getWebserviceBaseUrl() + urlIn.getFile() + "?wsdl";
			outUrl = new URL(urlString);
		} catch (Exception e) {
			log.error(e.getMessage(),e);
		}
		return outUrl;
	}
	/**
	 * 
	 * @return
	 */
	private String getWebserviceBaseUrl() {
	      return (properties.getProperty(WebServiceClient.BPEL_WEBSERVICE_BASEURL, "http://localhost:8080"));
	}
}
