/*
 * 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 javassist.*;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ClassFile;
import javassist.bytecode.ConstPool;
import javassist.bytecode.annotation.Annotation;
import javassist.bytecode.annotation.EnumMemberValue;
import javassist.bytecode.annotation.StringMemberValue;

import javax.xml.namespace.QName;

/**
 * Creates JAX-WS Provider classes using javassist.
 * These provider classes can then be deployed to JBossWS in memory.<p>
 * The javassist generated class basically just carries the meta data,
 * while the actual implementation resides in {@link org.jboss.soa.bpel.runtime.ws.BaseWebServiceEndpoint}
 *
 * @author Heiko.Braun <heiko.braun@jboss.com>
 */
public class WebServiceProviderFactory
{
  private final static String PKG_PREFIX = WebServiceProviderFactory.class.getPackage().getName()+".";

  public BaseWebServiceEndpoint createProvider(
      QName service, String port,
      String endpointId,
      WSDLReference wsdlRef,
      ClassLoader loader,
      java.io.File handlerFile
  )
      throws Exception
  {
    ClassPool pool = new ClassPool(true);
    pool.appendClassPath(new LoaderClassPath(loader));

    // Imports
    pool.importPackage("java.lang");
    pool.importPackage("javax.xml.ws");
    pool.importPackage("javax.jws");

    CtClass stringType = pool.get("java.lang.String");    
    
    String implClassName = PKG_PREFIX+"BPELWebServiceEndpoint_"+endpointId;
    //CtClass impl = pool.makeClass(implClassName);
    
    // Load an existing class representing the template for a Web Service provider
    // This was necessary, as javassist does not provide a way to set an implemented
    // interface that supports generics. Although the super class (AbstractWebServiceEndpoint)
    // implements this Provider<SOAPMessage> interface, CXF requires the actual web service
    // implementation class to directly define this interface. (RIFTSAW-123)
    CtClass impl = pool.get("org.jboss.soa.bpel.runtime.ws.TemplateWebServiceEndpoint");
    impl.setName(implClassName);

    // AbstractWebServiceEndpoint.endpointId property
    CtField idField = new CtField(stringType, "endpointId", impl);
    idField.setModifiers(Modifier.PUBLIC);
    impl.addField(idField, "\""+endpointId+"\"");

    // AbstractWebServiceEndpoint.serviceName property
    CtField serviceField = new CtField(stringType, "serviceName", impl);
    serviceField.setModifiers(Modifier.PUBLIC);
    impl.addField(serviceField, "\""+service.toString()+"\"");

     // AbstractWebServiceEndpoint.wsdlLocation property
    CtField wsdlLocationField = new CtField(stringType, "wsdlLocation", impl);
    wsdlLocationField.setModifiers(Modifier.PUBLIC);
    impl.addField(wsdlLocationField, "\""+wsdlRef.getWsdlURL().toExternalForm()+"\"");

     // AbstractWebServiceEndpoint.portName property
    CtField portNameField = new CtField(stringType, "portName", impl);
    portNameField.setModifiers(Modifier.PUBLIC);
    impl.addField(portNameField, "\""+port+"\"");

    // Annotations
    ClassFile classFile = impl.getClassFile();
    classFile.setVersionToJava5();
    ConstPool constantPool = classFile.getConstPool();

    /*
    @WebServiceProvider(
      portName="HelloPort",
      serviceName="HelloService",
      targetNamespace="http://helloservice.org/wsdl",
      wsdlLocation="WEB-INF/wsdl/HelloService.wsdl"
    )
    @ServiceMode(value=Service.Mode.MESSAGE)
     */
    AnnotationsAttribute attr = new
        AnnotationsAttribute(constantPool, AnnotationsAttribute.visibleTag);

    // --
    Annotation providerAnnotation = new Annotation(
        "javax.xml.ws.WebServiceProvider", constantPool);

    providerAnnotation.addMemberValue(
        "serviceName",
        new StringMemberValue(service.getLocalPart(), constantPool)
    );
    providerAnnotation.addMemberValue(
        "portName",
        new StringMemberValue(port, constantPool)
    );
    providerAnnotation.addMemberValue(
        "targetNamespace",
        new StringMemberValue(service.getNamespaceURI(), constantPool)
    );
    providerAnnotation.addMemberValue(
        "wsdlLocation",
        new StringMemberValue(wsdlRef.getWsdlURL().toExternalForm(), constantPool)
    );
    attr.addAnnotation(providerAnnotation);

    // --
    Annotation annotation2 = new Annotation("javax.xml.ws.ServiceMode",
        constantPool);
    EnumMemberValue enumValue = new EnumMemberValue(constantPool);
    enumValue.setType("javax.xml.ws.Service$Mode");
    enumValue.setValue("MESSAGE");
    annotation2.addMemberValue("value", enumValue);

    attr.addAnnotation(annotation2);

    classFile.addAttribute(attr);
    
    
    // Check if handler chain should be established
    if (handlerFile != null) {
	    Annotation handlerChain = new Annotation("javax.jws.HandlerChain", constantPool);
	    	    
	    handlerChain.addMemberValue("file",
	    			new StringMemberValue("/"+handlerFile.getName(), constantPool));
	    
	    attr.addAnnotation(handlerChain);
    }
    

    createStringGetter(impl, stringType, "endpointId", "getEndpointId");
    createStringGetter(impl, stringType, "serviceName", "getServiceName");
    createStringGetter(impl, stringType, "wsdlLocation", "getWsdlLocation");
    createStringGetter(impl, stringType, "portName", "getPortName");

    // ------------

    // freeze
    impl.stopPruning(false);
    impl.toClass(loader);
    JavaUtils.clearBlacklists(loader);

    // test it
    Class clazz = loader.loadClass(implClassName);
    
    BaseWebServiceEndpoint obj = (BaseWebServiceEndpoint)clazz.newInstance();

    return obj;
  }

  private void createStringGetter(CtClass impl, CtClass stringType, String property, String methodName)
      throws Exception
  {
    CtMethod method = new CtMethod(
        stringType, methodName,
        new CtClass[]{},
        impl
    );

    // Method body
    StringBuffer body = new StringBuffer();
    body.append("{");
    body.append("return this."+property+";");
    body.append("}");

    method.setBody(body.toString());
    
    impl.addMethod(method);

  }
}
