/*
 * 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.apache.ode.bpel.iapi.EndpointReference;
import org.jboss.dependency.spi.ControllerContext;
import org.jboss.deployers.client.spi.DeployerClient;
import org.jboss.deployers.client.spi.Deployment;
import org.jboss.deployers.spi.attachments.MutableAttachments;
import org.jboss.deployers.structure.spi.ClassLoaderFactory;
import org.jboss.deployers.structure.spi.DeploymentUnit;
import org.jboss.deployers.structure.spi.DeploymentUnitFilter;
import org.jboss.deployers.vfs.spi.client.VFSDeploymentFactory;
import org.jboss.kernel.Kernel;
import org.jboss.metadata.web.jboss.JBossWebMetaData;
import org.jboss.soa.bpel.runtime.engine.ode.ExecutionEnvironment;
import org.jboss.soa.bpel.runtime.engine.ode.UDDIClientFactory;
import org.jboss.soa.bpel.runtime.engine.ode.UDDIRegistration;
import org.jboss.soa.bpel.runtime.integration.KernelLocator;
import org.jboss.soa.bpel.runtime.integration.ServerConfig;
import org.jboss.soa.bpel.runtime.integration.ServerConfigFactory;
import org.jboss.virtual.VFS;
import org.jboss.virtual.VirtualFile;

import javax.wsdl.Definition;
import javax.xml.namespace.QName;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Manages creation and destroyal of web service endpoints and clients. 
 * This instance is stateful and retains references to both endpoints and clients.
 * As such it should only exists once.
 *
 * @see org.jboss.soa.bpel.runtime.ws.BaseWebServiceEndpoint
 * @see org.jboss.soa.bpel.runtime.ws.WebServiceClient
 * @see org.jboss.soa.bpel.runtime.engine.ode.JAXWSBindingContext
 *
 * @author Heiko.Braun <heiko.braun@jboss.com>
 */
public class EndpointManager
{
  protected final Log log = LogFactory.getLog(getClass());
  // we should consider having our own OdeConfigProperties class if we need more properties
  private final static String BPEL_UDDI_REGISTRATION = "uddi.registration";

  private Map<String, ServiceEndpointReference> endpointMapping = new ConcurrentHashMap<String, ServiceEndpointReference>();
  private ServerConfig serverConfig;

  private ExecutionEnvironment executionEnvironment;
  private UDDIRegistration uddiRegistration = null;

  public EndpointManager(ExecutionEnvironment executionEnvironment)
  {
    this.executionEnvironment = executionEnvironment;
    this.serverConfig = ServerConfigFactory.getServerConfig();
    if (isUDDIRegistration()) {
    	try {
    		uddiRegistration = UDDIClientFactory.newInstance(executionEnvironment.getOdeConfig().getProperties());
    	} catch (Exception e) {
    		log.error(e.getMessage());
    		log.error("Running without UDDI integration.");
    	}
    }
  }

  public EndpointReference createEndpoint(EndpointMetaData metaData, WSDLReference wsdlRef, final ClassLoader classLoader)
      throws EndpointManagementException
  {
    try
    {

      // create deployment structure  (maybe replaced by shrinkwrap)
      File warArchive = new DeploymentBuilder(serverConfig)
          .setEndpoint(metaData.getEndpointId())
          .setWSDL(new File(wsdlRef.getWsdlURL().toURI()))
          .build();

      //Deployment deployment = createInMemoryDeployment(endpointId);
      Deployment deployment = createVFSDeployment(warArchive);

      // generate provider impl
      WebServiceProviderFactory providerFactory = new WebServiceProviderFactory();

      BaseWebServiceEndpoint providerImpl =
          providerFactory.createProvider(
              metaData.getServiceName(),
              metaData.getPortName(),
              metaData.getEndpointId(),
              wsdlRef,
              classLoader
          );

      log.debug("Created dynamic endpoint class " + providerImpl.getClass().getName());

      // Classloading
      ClassLoaderFactory clf = new DelegatingClassLoaderFactory(classLoader);

      // WebMetaData
      URL serviceUrl = new WSDLParser(wsdlRef.getDefinition()).getServiceLocationURL(metaData.getServiceName(), metaData.getPortName());
      String[] webContext = deriveWebContextFromServiceUrl(serviceUrl);

      WebMetaDataFactory wmdFactory = new WebMetaDataFactory(
          metaData.getEndpointId(), webContext[0], webContext[1], providerImpl.getClass().getName()
      );

      MutableAttachments mutableAttachments =
          (MutableAttachments)deployment.getPredeterminedManagedObjects();

      // Applies to in memory only. Not used with VFS underneath
      //mutableAttachments.addAttachment(StructureMetaData.class, new StructureMetaDataImpl());
      mutableAttachments.addAttachment(ClassLoaderFactory.class, clf);
      mutableAttachments.addAttachment(JBossWebMetaData.class, wmdFactory.createWebMetaData(classLoader));
      mutableAttachments.addAttachment(DeploymentUnitFilter.class, new RiftsawWSDeploymentUnitFilter());

      getMainDeployer().deploy(deployment);

      ServiceEndpointReference ref = new ServiceEndpointReference(
          metaData.getEndpointId(), serviceUrl.toExternalForm(), deployment.getName()
      );

      ref.setArchiveLocation(warArchive.getAbsolutePath());

      endpointMapping.put(
          createEndpointKey(metaData.getServiceName(), metaData.getPortName()),
          ref
      );
      if (uddiRegistration!=null) {
    	  uddiRegistration.registerEPR(metaData.getServiceName().getLocalPart(), metaData.getPortName(), serviceUrl);
      }

      return ref;
    }
    catch (Exception e)
    {
      throw new EndpointManagementException("Failed to create endpoint", e);
    }
  }

  private String[] deriveWebContextFromServiceUrl(URL serviceUrl)
  {
    // metadata based on soapAddress
    String urlPathInfo = serviceUrl.getPath(); // has always leading slash
    urlPathInfo = urlPathInfo.substring(1, urlPathInfo.length());

    String actualWebContext;
    String actualUrlPattern;
    if(urlPathInfo.indexOf("/")!=-1)
    {
      int index = urlPathInfo.indexOf("/");
      actualWebContext = urlPathInfo.substring(0, index);
      actualUrlPattern = urlPathInfo.substring(index, urlPathInfo.length())+"/*";
    }
    else
    {
      actualWebContext = urlPathInfo;
      actualUrlPattern = "/*";
    }

    return new String[] {actualWebContext, actualUrlPattern};
  }
  private String createEndpointKey(QName service, String port)
  {
    return service.toString()+":"+port;
  }

  /*private Deployment createInMemoryDeployment(String endpointId)
  {
    AbstractDeployment deployment = new AbstractDeployment();
    deployment.setName("file://BPELEndpoint_"+endpointId+".war");
    return deployment;
  }*/

  private Deployment createVFSDeployment(File war)
      throws IOException
  {
    VirtualFile webAppVFS = VFS.getRoot(war.toURL());
    return VFSDeploymentFactory.getInstance().createVFSDeployment(webAppVFS);
  }

  public void removeEndpoint(QName service, String port) throws EndpointManagementException
  {
    String key = createEndpointKey(service, port);
    ServiceEndpointReference ref = endpointMapping.get(key);
    if(null==ref)
      throw new IllegalStateException("Unable to resolve ServiceEndpointReference for key: "+key);
    
    String deploymentId = ref.getDeploymentName();

    if(ref!=null)
    {
      try
      {
        // undeploy call
        getMainDeployer().undeploy(deploymentId);

        // unregister
        endpointMapping.remove(key);

        // remove physical artifacts
        File warArchive = new File(ref.getArchiveLocation());
        if(warArchive.exists())
        {
          if(!deleteDirectory(warArchive)) log.warn(warArchive + " could no be deleted");          
        }
        else
        {
          log.warn(ref.getArchiveLocation() + " cannot be removed (doesn't exist).");
        }
        if (uddiRegistration!=null) {
        	uddiRegistration.unRegisterEPR(service, port);
        }
      }
      catch (Exception e)
      {
        throw new EndpointManagementException("Failed to undeploy "+deploymentId, e);
      }
    }
    else
    {
      log.warn("Endpoint not found for removal: "+key);
    }
  }

  private DeployerClient getMainDeployer()
  {
    Kernel mc = KernelLocator.getKernel();
    ControllerContext context = mc.getController().getInstalledContext("MainDeployer");
    DeployerClient mainDeployer = (DeployerClient)context.getTarget();
    return mainDeployer;
  }

  public EndpointReference maintains(QName service, String port)
  {
    String key = createEndpointKey(service, port);
    return endpointMapping.get(key);
  }

  public class DelegatingClassLoaderFactory implements ClassLoaderFactory
  {
    private ClassLoader delegate;

    public DelegatingClassLoaderFactory(final ClassLoader delegate)
    {
      this.delegate = delegate;
    }

    public ClassLoader createClassLoader(DeploymentUnit unit) throws Exception
    {
      return delegate;
    }

    public void removeClassLoader(DeploymentUnit unit) throws Exception
    {
      // cleanup?
    }
  }

  public class RiftsawWSDeploymentUnitFilter implements DeploymentUnitFilter
  {
    public boolean accepts(DeploymentUnit unit)
    {
      // When I am attached, it's always a BPEL deployment
      return false;
    }
  }

  public WebServiceClient createClient(
      EndpointMetaData metaData,Definition wsdlDefinition, URL wsdlUrl)
      throws EndpointManagementException
  {
    try
    {
      WebServiceClient client =
          new WebServiceClient(metaData, wsdlDefinition, wsdlUrl, executionEnvironment);

      return client;

    }
    catch (Exception e)
    {
      throw new EndpointManagementException("Failed to create endpoint", e);
    }
  }

  static public boolean deleteDirectory(File path)
  {
    if( path.exists() ) {
      File[] files = path.listFiles();
      for(int i=0; i<files.length; i++) {
         if(files[i].isDirectory()) {
           deleteDirectory(files[i]);
         }
         else {
           files[i].delete();
         }
      }
    }
    return( path.delete() );
  }
  
  public boolean isUDDIRegistration() {
      return Boolean.valueOf(executionEnvironment.getOdeConfig().getProperty(BPEL_UDDI_REGISTRATION, "false"));
  }

}
