/*
 * JBoss, Home of Professional Open Source.
 * See the COPYRIGHT.txt file distributed with this work for information
 * regarding copyright ownership.  Some portions may be licensed
 * to Red Hat, Inc. under one or more contributor license agreements.
 * 
 * This library 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 library 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 library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 * Modified by
 * @author  Data Virtuality GmbH
 * @date    $Date$
 *  
 */
// https://issues.jboss.org/browse/TEIID-4076
// https://issues.jboss.org/browse/TEIID-2148
// https://issues.jboss.org/browse/TEIID-2268
// https://issues.jboss.org/browse/TEIID-3016
package org.teiid.translator.ws;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.xml.ws.Service.Mode;
import javax.xml.ws.http.HTTPBinding;
import javax.xml.ws.soap.SOAPBinding;

import org.teiid.core.BundleUtil;
import org.teiid.language.Argument;
import org.teiid.language.Call;
import org.teiid.metadata.MetadataFactory;
import org.teiid.metadata.Procedure;
import org.teiid.metadata.ProcedureParameter;
import org.teiid.metadata.RuntimeMetadata;
import org.teiid.metadata.BaseColumn.NullType;
import org.teiid.metadata.ProcedureParameter.Type;
import org.teiid.translator.ExecutionContext;
import org.teiid.translator.ExecutionFactory;
import org.teiid.translator.ProcedureExecution;
import org.teiid.translator.Translator;
import org.teiid.translator.TranslatorException;
import org.teiid.translator.TranslatorProperty;
import org.teiid.translator.TypeFacility;
import org.teiid.translator.WSConnection;

@Translator(name="ws", description="A translator for making Web Service calls")
public class WSExecutionFactory<ConnectionFactory, T extends WSConnection> extends ExecutionFactory<ConnectionFactory, WSConnection> {

	static final String INVOKE_HTTP = "invokeHttp"; //$NON-NLS-1$

	public enum Binding {
		HTTP(HTTPBinding.HTTP_BINDING), 
		SOAP11(SOAPBinding.SOAP11HTTP_BINDING),
		SOAP12(SOAPBinding.SOAP12HTTP_BINDING);
		
		private String bindingId;
		
		private Binding(String bindingId) {
			this.bindingId = bindingId;
		}
		
		public String getBindingId() {
			return bindingId;
		}
	}
	
	public static BundleUtil UTIL = BundleUtil.getBundleUtil(WSExecutionFactory.class);
	public enum Event implements BundleUtil.Event{
		TEIID15006,
		DVCORE94028,
		DVCORE94128,
	}
	private Mode defaultServiceMode = Mode.PAYLOAD;
	private Binding defaultBinding = Binding.SOAP12;
	private String xmlParamName;
	
	public WSExecutionFactory() {
		setSourceRequiredForMetadata(false);
		setTransactionSupport(TransactionSupport.NONE);
	}

	@TranslatorProperty(description="Contols request/response message wrapping - set to MESSAGE for full control over SOAP messages.", display="Default Service Mode")
	public Mode getDefaultServiceMode() {
		return defaultServiceMode;
	}
	
	public void setDefaultServiceMode(Mode mode) {
		this.defaultServiceMode = mode;
	}

	@TranslatorProperty(description="Contols what SOAP or HTTP type of invocation will be used if none is specified.", display="Default Binding")
	public Binding getDefaultBinding() {
		return defaultBinding;
	}
	
	public void setDefaultBinding(Binding defaultInvocationType) {
		this.defaultBinding = defaultInvocationType;
	}
	
	@TranslatorProperty(description="Used with the HTTP binding (typically with the GET method) to indicate that the request document should be part of the query string.", display="XML Param Name")
	public String getXMLParamName() {
		return xmlParamName;
	}
	
	public void setXMLParamName(String xmlParamName) {
		this.xmlParamName = xmlParamName;
	}
	
    @Override
    public ProcedureExecution createProcedureExecution(Call command, ExecutionContext executionContext, RuntimeMetadata metadata, WSConnection connection)
    		throws TranslatorException {
    	if (command.getProcedureName().equalsIgnoreCase(INVOKE_HTTP)) {
    		return new BinaryWSProcedureExecution(command, metadata, executionContext, this, connection);
    	}
		return new WSProcedureExecution(command, metadata, executionContext, this, connection);
    }
    
	@Override
	public final List<String> getSupportedFunctions() {
		return Collections.emptyList();
    }
	
	@Override
	public void getMetadata(MetadataFactory metadataFactory,
			WSConnection conn) throws TranslatorException {
		Procedure p = metadataFactory.addProcedure("invoke"); //$NON-NLS-1$ 
		p.setRemark("Invokes a webservice that returns an XML result"); //$NON-NLS-1$

		metadataFactory.addProcedureParameter("result", TypeFacility.RUNTIME_NAMES.XML, Type.ReturnValue, p); //$NON-NLS-1$

		ProcedureParameter param = metadataFactory.addProcedureParameter("binding", TypeFacility.RUNTIME_NAMES.STRING, Type.In, p); //$NON-NLS-1$
		param.setRemark("The invocation binding (HTTP, SOAP11, SOAP12).  May be set or allowed to default to null to use the default binding."); //$NON-NLS-1$
		param.setNullType(NullType.Nullable);

		param = metadataFactory.addProcedureParameter("action", TypeFacility.RUNTIME_NAMES.STRING, Type.In, p); //$NON-NLS-1$
		param.setRemark("With a SOAP invocation, action sets the SOAPAction.  With HTTP it sets the HTTP Method (GET, POST - default, etc.)."); //$NON-NLS-1$
		param.setNullType(NullType.Nullable);

		//can be one of string, xml, clob
		param = metadataFactory.addProcedureParameter("request", TypeFacility.RUNTIME_NAMES.XML, Type.In, p); //$NON-NLS-1$
		param.setRemark("The XML document or root element that represents the request.  If the ExecutionFactory is configured in with a DefaultServiceMode of MESSAGE, then the SOAP request must contain the entire SOAP message."); //$NON-NLS-1$
		param.setNullType(NullType.Nullable);
		
		param = metadataFactory.addProcedureParameter("endpoint", TypeFacility.RUNTIME_NAMES.STRING, Type.In, p); //$NON-NLS-1$
		param.setRemark("The relative or abolute endpoint to use.  May be set or allowed to default to null to use the default endpoint address."); //$NON-NLS-1$
		param.setNullType(NullType.Nullable);

		param = metadataFactory.addProcedureParameter("failOnHTTPError", TypeFacility.RUNTIME_NAMES.BOOLEAN, Type.In, p); //$NON-NLS-1$
		param.setRemark("The optional fail on HTTP Error (True by default)."); //$NON-NLS-1$
		param.setNullType(NullType.Nullable);

		param = metadataFactory.addProcedureParameter("stream", TypeFacility.RUNTIME_NAMES.BOOLEAN, Type.In, p); //$NON-NLS-1$
		param.setRemark("If the result should be streamed."); //$NON-NLS-1$
		param.setNullType(NullType.Nullable);
		param.setDefaultValue("false"); //$NON-NLS-1$

		param = metadataFactory.addProcedureParameter("failed", TypeFacility.RUNTIME_NAMES.BOOLEAN, Type.Out, p); //$NON-NLS-1$
		param.setRemark("Returns whether invocation was failed or not."); //$NON-NLS-1$
		param.setNullType(NullType.Nullable);

		param = metadataFactory.addProcedureParameter("httpResponseCode", TypeFacility.RUNTIME_NAMES.INTEGER, Type.Out, p); //$NON-NLS-1$
		param.setRemark("Returns http response code."); //$NON-NLS-1$
		param.setNullType(NullType.Nullable);
		
		p = metadataFactory.addProcedure(INVOKE_HTTP);
		p.setRemark("Invokes a webservice that returns an binary result"); //$NON-NLS-1$

		metadataFactory.addProcedureParameter("result", TypeFacility.RUNTIME_NAMES.BLOB, Type.ReturnValue, p); //$NON-NLS-1$

		param = metadataFactory.addProcedureParameter("action", TypeFacility.RUNTIME_NAMES.STRING, Type.In, p); //$NON-NLS-1$
		param.setRemark("Sets the HTTP Method (GET, POST - default, etc.)."); //$NON-NLS-1$
		param.setNullType(NullType.Nullable);

		//can be one of string, xml, clob
		param = metadataFactory.addProcedureParameter("request", TypeFacility.RUNTIME_NAMES.OBJECT, Type.In, p); //$NON-NLS-1$
		param.setRemark("The String, XML, BLOB, or CLOB value containing a payload (only for POST)."); //$NON-NLS-1$
		param.setNullType(NullType.Nullable);
		
		param = metadataFactory.addProcedureParameter("endpoint", TypeFacility.RUNTIME_NAMES.STRING, Type.In, p); //$NON-NLS-1$
		param.setRemark("The relative or abolute endpoint to use.  May be set or allowed to default to null to use the default endpoint address."); //$NON-NLS-1$
		param.setNullType(NullType.Nullable);
		
		// EXTENSION
		// ISSUE: DVCORE-382
		// FIXED BY: Roman Pedchenko
		// DATE: 2013.04.23
		param = metadataFactory.addProcedureParameter("requestContentType", TypeFacility.RUNTIME_NAMES.STRING, Type.In, p); //$NON-NLS-1$	
		param.setRemark("The optional content-type to include to the request headers."); //$NON-NLS-1$
		param.setNullType(NullType.Nullable);
		// END EXTENSION

		// EXTENSION
		// ISSUE: DVCORE-545
		// FIXED BY: Salvatore Raunich
		// DATE: 2014.06.24
		param = metadataFactory.addProcedureParameter("requestHeaders", TypeFacility.RUNTIME_NAMES.CLOB, Type.In, p); //$NON-NLS-1$	
		param.setRemark("The optional request header to include to the request headers."); //$NON-NLS-1$
		param.setNullType(NullType.Nullable);
		// END EXTENSION

		// EXTENSION
		// ISSUE: DVCORE-2218
		// FIXED BY: Salvatore Raunich
		// DATE: 2014.07.21
		param = metadataFactory.addProcedureParameter("failOnHTTPError", TypeFacility.RUNTIME_NAMES.BOOLEAN, Type.In, p); //$NON-NLS-1$	
		param.setRemark("The optional fail on HTTP Error (True by default)."); //$NON-NLS-1$
		param.setNullType(NullType.Nullable);
		// END EXTENSION

		param = metadataFactory.addProcedureParameter("stream", TypeFacility.RUNTIME_NAMES.BOOLEAN, Type.In, p); //$NON-NLS-1$
		param.setRemark("If the result should be streamed."); //$NON-NLS-1$
		param.setNullType(NullType.Nullable);
		param.setDefaultValue("false"); //$NON-NLS-1$

		metadataFactory.addProcedureParameter("contentType", TypeFacility.RUNTIME_NAMES.STRING, Type.Out, p); //$NON-NLS-1$
		metadataFactory.addProcedureParameter("responseHeadersArray", TypeFacility.RUNTIME_NAMES.OBJECT, Type.Out, p); //$NON-NLS-1$
		metadataFactory.addProcedureParameter("responseHeadersXML", TypeFacility.RUNTIME_NAMES.CLOB, Type.Out, p); //$NON-NLS-1$

		param = metadataFactory.addProcedureParameter("failed", TypeFacility.RUNTIME_NAMES.BOOLEAN, Type.Out, p); //$NON-NLS-1$
		param.setRemark("Returns whether invocation was failed or not."); //$NON-NLS-1$
		param.setNullType(NullType.Nullable);

		param = metadataFactory.addProcedureParameter("httpResponseCode", TypeFacility.RUNTIME_NAMES.INTEGER, Type.Out, p); //$NON-NLS-1$
		param.setRemark("Returns http response code."); //$NON-NLS-1$
		param.setNullType(NullType.Nullable);
	}
	
	@Override
	public boolean areLobsUsableAfterClose() {
		return true;
	}

	protected static List<Argument> getInArguments(Call procedure) {
		List<Argument> result = new ArrayList<Argument>();
		for (Argument argument : procedure.getArguments()) {
			if (argument.getDirection() == Argument.Direction.IN)
				result.add(argument);
		}
		return result;
	}

}
