package eu.dnetlib.soap.cxf;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.ws.wsaddressing.W3CEndpointReference;
import javax.xml.ws.wsaddressing.W3CEndpointReferenceBuilder;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.oro.text.perl.Perl5Util;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import eu.dnetlib.soap.AbstractEndpointReferenceBuilder;

/**
 * The cxf endpoint is an internal cross-toolkit implementation of the messaging endpoint.
 *
 * <p>
 * End users will normally access jaxws endpoints, since jaxws is the mostly used cxf frontend. However, generic code,
 * like interceptors, will handle cxf endpoints, so sometimes you may need to construct endpoint references from them.
 * </p>
 *
 * <pre>
 * &lt;ns3:Address&gt;http://localhost:8090/app/services/isStore&lt;/ns3:Address&gt;
 * &lt;ns3:ReferenceParameters/&gt;
 * &lt;ns3:Metadata&gt;
 *   &lt;wsaw:InterfaceName&gt;ns1:ISStoreService&lt;/wsaw:InterfaceName&gt;
 *   &lt;wsaw:ServiceName EndpointName=&quot;ISStoreServicePort&quot;&gt;ns2:ISStoreServiceService&lt;/wsaw:ServiceName&gt;
 *   &lt;infrastructure:infrastructure&gt;development&lt;/infrastructure:infrastructure&gt;
 * &lt;/ns3:Metadata&gt;
 * </pre>
 *
 * Users can easily define default system or service wide custom metadata to endpoint references by setting a
 * defaultMetadata property:
 *
 * <pre>
 * &lt;bean name=&quot;cxfEndpointReferenceBuilder&quot; class=&quot;eu.dnetlib.soap.cxf.CxfEndpointReferenceBuilder&quot;&gt;
 *  &lt;property name=&quot;defaultMetadata&quot;&gt;
 *   &lt;map&gt;
 *    &lt;entry key=&quot;{http://dnetlib.eu/endpointReference}infrastructure&quot; value=&quot;${infrastructure.name}&quot; /&gt;
 *   &lt;/map&gt;
 *  &lt;/property&gt;
 * &lt;/bean&gt;
 * </pre>
 *
 *
 * @author marko
 *
 */
public class CxfEndpointReferenceBuilder extends AbstractEndpointReferenceBuilder<Endpoint> {
	/**
	 * logger.
	 */
	private static final Log log = LogFactory.getLog(CxfEndpointReferenceBuilder.class); // NOPMD by marko on 11/24/08 4:55

	/**
	 * users can put some default metadata elements into all EPRs.
	 */
	private Map<String, Object> defaultMetadata;

	/**
	 * regexp utility.
	 */
	private final transient Perl5Util matcher = new Perl5Util();

	/**
	 * namespace of the ResourceIdentifier reference parameter.
	 */
	private String riNamespace = "http://www.driver.org";

	/**
	 * element name of the ResourceIdentifier reference parameter.
	 */
	private String riElementName = "ResourceIdentifier";

	/**
	 * {@inheritDoc}
	 *
	 * TODO: refactor.
	 *
	 * @see eu.dnetlib.soap.EndpointReferenceBuilder#getEndpointReference(java.lang.Object, java.util.Map)
	 */

	@Override
	public W3CEndpointReference getEndpointReference(final Endpoint endpoint, final String referenceParam, final Map<QName, Object> attributes) {
		final String address = getAddress(endpoint);
		return getEndpointReference(address, endpoint.getService().getName(), endpoint.getEndpointInfo().getName(), null, referenceParam,
				attributes);
	}

	/**
	 * low level method which allows the construction of a endpoint reference knowing all basic data as the address, service name etc.
	 *
	 * @param address
	 * @param serviceName
	 * @param endpointName
	 * @param wsdl
	 * @param referenceParam
	 * @param attributes
	 * @return
	 */
	public W3CEndpointReference getEndpointReference(
			final String address,
			final QName serviceName,
			final QName endpointName,
			final String wsdl,
			final String referenceParam,
			final Map<QName, Object> attributes) {
		Map<QName, Object> attrs = attributes;

		final W3CEndpointReferenceBuilder builder = new W3CEndpointReferenceBuilder();
		builder.address(address);
		if(serviceName != null)
			builder.serviceName(serviceName);
		if(endpointName != null)
			builder.endpointName(endpointName);
		builder.wsdlDocumentLocation(wsdl);

		if (defaultMetadata != null) {
			if (attrs == null)
				attrs = new HashMap<QName, Object>();
			for (Entry<String, Object> entry : defaultMetadata.entrySet())
				attrs.put(splitQNameString(entry.getKey()), entry.getValue());
		}

		try {
			final Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); // NOPMD
			if (referenceParam != null) {
				final Element referenceElement = doc.createElementNS(getRiNamespace(), getRiElementName());
				referenceElement.setTextContent(referenceParam);
				builder.referenceParameter(referenceElement);
			}

			if (attrs != null && !attrs.isEmpty()) {
				for (Entry<QName, Object> entry : attrs.entrySet()) {
					final QName qname = entry.getKey();
					final Element element = doc.createElementNS(qname.getNamespaceURI(), qname.getLocalPart());
					element.setTextContent((String) entry.getValue());
					builder.metadata(element);
				}
			}
		} catch (ParserConfigurationException e) {
			log.fatal("cannot extend EPR", e);
			throw new IllegalStateException("cannot extend EPR", e);
		}

		return builder.build();
	}

	/**
	 * compute an endpoint address.
	 *
	 * @param endpoint
	 *            endpoint
	 * @return url as string
	 */
	@Override
	public String getAddress(final Endpoint endpoint) {
		return endpoint.getEndpointInfo().getAddress();
	}

	/**
	 * helper method for converting "{namespace}elementName" strings to QNames.
	 *
	 * @param key
	 *            "{namespace}elementName" string
	 * @return qname
	 */
	private QName splitQNameString(final String key) {
		matcher.match("m/{(.*)}(.*)/", key);

		return new QName(matcher.group(1), matcher.group(2));
	}

	public Map<String, Object> getDefaultMetadata() {
		return defaultMetadata;
	}

	public void setDefaultMetadata(final Map<String, Object> defaultMetadata) {
		this.defaultMetadata = defaultMetadata;
	}

	public String getRiNamespace() {
		return riNamespace;
	}

	public void setRiNamespace(final String riNamespace) {
		this.riNamespace = riNamespace;
	}

	public String getRiElementName() {
		return riElementName;
	}

	public void setRiElementName(final String riElementName) {
		this.riElementName = riElementName;
	}

}
