/*
 * 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.runtime.ws;


import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.soa.bpel.runtime.engine.BPELEngine;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.wsdl.Definition;
import javax.wsdl.Operation;
import javax.wsdl.WSDLException;
import javax.wsdl.factory.WSDLFactory;
import javax.wsdl.xml.WSDLReader;
import javax.xml.namespace.QName;
import javax.xml.soap.*;
import javax.xml.ws.Provider;

/**
 * Base class for BPEL endpoints that are created through javassist.
 * Represents a JAX-WS {@link javax.xml.ws.Provider} implementation.
 *
 * @see org.jboss.soa.bpel.runtime.ws.WebServiceProviderFactory
 *
 * @author Heiko.Braun <heiko.braun@jboss.com>
 */
public class BaseWebServiceEndpoint implements Provider<SOAPMessage>
{
  protected final Log log = LogFactory.getLog(getClass());

  private SOAPMessageAdapter soapAdapter;
  private QName serviceQName;
  private Definition wsdlDefinition;

  private boolean isInitialized;

  private void init()
  {
    if(!isInitialized)
    {
      try
      {
        WSDLReader wsdlReader = WSDLFactory.newInstance().newWSDLReader();
        this.wsdlDefinition = wsdlReader.readWSDL(getWsdlLocation());
        this.serviceQName = QName.valueOf(getServiceName());
        this.soapAdapter = new SOAPMessageAdapter(wsdlDefinition, serviceQName, getPortName());
      }
      catch (WSDLException e)
      {
        throw new RuntimeException("Failed to parse WSDL", e);
      }
      isInitialized = true;
    }
  }

  public SOAPMessage invoke(SOAPMessage soapMessage)
  {
    log.debug("Invoking endpoint "+getEndpointId());
    init();

    try
    {
      SOAPPart soapPart = soapMessage.getSOAPPart();
      SOAPEnvelope soapEnvelope = soapPart.getEnvelope();
      Element messageElement = getMessagePayload(soapEnvelope);

      if(log.isDebugEnabled())
        log.debug( "ODE inbound message: \n" +DOMWriter.printNode(soapEnvelope, true) );

      // Create invocation context
      final String operationName = resolveOperationName(messageElement);
      
      WSInvocationAdapter invocationContext = new WSInvocationAdapter(
          operationName,
          serviceQName,
          getPortName(),
          soapAdapter
      );
      invocationContext.setSOAPMessage(soapMessage);

      // Invoke ODE
      getEngine().invoke(invocationContext);

      // Handle response
      SOAPMessage responseMessage = null;
      
      if (isResponseExpected(messageElement)) {
    	  responseMessage = invocationContext.getInvocationResult();

    	  if(log.isDebugEnabled())
              log.debug( "ODE outbound message: \n" +
                  DOMWriter.printNode(responseMessage.getSOAPPart().getEnvelope(), true)
              );
      } else if (log.isDebugEnabled()) {
    	  log.debug( "ODE no outbound message");
      }

      if (responseMessage == null) {
    	  log.debug("No response, probably due to oneway request");
    	  
    	  // Need to create an empty response to avoid npe in jbossws (RIFTSAW-154)
    	  responseMessage = MessageFactory.newInstance().createMessage();
      }
      
      return responseMessage;
    }
    catch (Exception e)
    {
      throw new RuntimeException("Failed to invoke BPEL process: "+e.getMessage(), e);
    }
  }

  public String resolveOperationName(Element payload)
  {
    if(soapAdapter.isRPC())
    {
      return payload.getLocalName();
    }
    else
    {
      QName elementName = new QName(payload.getNamespaceURI(), payload.getLocalName());
      Operation op = new WSDLParser(wsdlDefinition).getDocLitOperation(
          this.serviceQName, getPortName(), elementName
      );

      return op.getName();
    }
  }

  public boolean isResponseExpected(Element payload)
  {
	Operation op=null;
	
    if(soapAdapter.isRPC())
    {
        QName elementName = new QName(payload.getNamespaceURI(), payload.getLocalName());
        op = new WSDLParser(wsdlDefinition).getRPCOperation(
            this.serviceQName, getPortName(), elementName
        );
    }
    else
    {
      QName elementName = new QName(payload.getNamespaceURI(), payload.getLocalName());
      op = new WSDLParser(wsdlDefinition).getDocLitOperation(
          this.serviceQName, getPortName(), elementName
      );
    }
    
    if (op == null) {
    	throw new RuntimeException("Failed to locate operation definition for: "+payload);
    }

    return op.getOutput() != null;
  }

  public static Element getMessagePayload(SOAPEnvelope soapEnvelope)
      throws SOAPException
  {
    SOAPBody body = soapEnvelope.getBody();
    Element messageElement = null;     // first child of type Element
    NodeList children = body.getChildNodes();
    for(int i=0; i<children.getLength(); i++)
    {
      Node tmp = children.item(i);
      if(Node.ELEMENT_NODE == tmp.getNodeType())
      {
        messageElement = (Element)tmp;
        break;
      }
    }
    return messageElement;
  }

  public String getEndpointId() {
	  return(null);
  }

  public String getServiceName() {
	  return(null);
  }

  public String getWsdlLocation() {
	  return(null);
  }

  public String getPortName() {
	  return(null);
  }

  private BPELEngine getEngine()
  {
    try
    {
      InitialContext ctx = new InitialContext();
      return (BPELEngine)ctx.lookup("bpel/Engine");
    }
    catch (NamingException e)
    {
      throw new RuntimeException("Failed to initialize BPEL engine");
    }
  }
}
