/*
 * JBoss, Home of Professional Open Source
 * Copyright 2005, JBoss Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt 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.
 */
// $Id: Deployment.java,v 1.4.2.9 2005/12/01 19:49:13 dimitris Exp $
package org.jboss.net.axis;

// axis config and utils

import org.jboss.axis.ConfigurationException;
import org.jboss.axis.MessageContext;
import org.jboss.axis.deployment.wsdd.WSDDConstants;
import org.jboss.axis.deployment.wsdd.WSDDDeployment;
import org.jboss.axis.deployment.wsdd.WSDDException;
import org.jboss.axis.deployment.wsdd.WSDDProvider;
import org.jboss.axis.deployment.wsdd.WSDDService;
import org.jboss.axis.deployment.wsdd.WSDDTypeMapping;
import org.jboss.axis.encoding.TypeMappingRegistry;
import org.jboss.axis.handlers.soap.SOAPService;
import org.jboss.logging.Logger;
import org.w3c.dom.Element;

import javax.xml.namespace.QName;
import javax.xml.rpc.encoding.DeserializerFactory;
import java.util.Iterator;
import java.util.List;

/**
 * <p/>
 * This subclass represents a wsdd deployment
 * with scoped typemappings and service-specific
 * classloading features. It may also serve as a place to
 * statically register some other jboss-specific
 * implementation classes in the axis package.
 * </p>
 * <p/>
 * WSDDDeployment is used by Axis in two respects: Two parse a
 * deployment descriptor and to host the accumulated
 * configuration data. We use that circumstance to build up
 * an additional deployment scope in jboss.net - services are
 * registered in the central registry/engine but still pertain
 * their relation (especially wrt typemapping delegation) to their
 * original deployment. We do that by annotating the scope deployments
 * in the service options.
 * </p>
 * <p/>
 * This design is IMHO more clever and needs less
 * overhead than the original approach of mapping services to
 * classloaders. Furthermore, it allows us to introduce some JSR109
 * related deployment concepts without global effect.
 * </p>
 *
 * @author <a href="mailto:Christoph.Jung@infor.de">Christoph G. Jung</a>
 * @version $Revision: 1.4.2.9 $
 * @since 09.03.2002
 */

public class Deployment extends WSDDDeployment
{

   private static Logger log = Logger.getLogger(Deployment.class);

   //
   // Attributes
   //

   /**
    * holds the classloader related to this deployment
    * which is the thread context classloader at creation time
    */
   protected ClassLoader deploymentLoader;

   /**
    * these are our local typemappings
    */
   protected List typeMappings = new java.util.ArrayList();

   /**
    * whether our registry has already been built
    */
   protected boolean tmrCreated;
   
   /** 
    * this handler provider will be injected into Axis to load 
    * implementations from the right classloaders
    */
   static
   {
      WSDDProvider.registerProvider(WSDDConstants.QNAME_HANDLER_PROVIDER,
              new ServiceClassLoaderAwareWSDDHandlerProvider());
   }

   //
   // Constructors
   // 

   /**
    * Constructor for Deployment. Is used with server-config.wsdd
    * in order to build the engine configuration. Is used with
    * actual deployment documents to build scoped deployments.
    * After calling the super constructor, we rebuild the typemappings
    * with our typemapping implementation that may contain additional
    * typemapping options. Unfortunately, you will have to do an
    * enclosing Thread.currentThread().setContextClassLoader(loader)
    * because of the f**cked constructor functionality of the super class.
    * Its a 1st grade Java rule, guys! So rather use the static factory method.
    */

   protected Deployment(Element e, ClassLoader loader) throws WSDDException
   {
      super(e);
      this.deploymentLoader = loader;
      // if axis did use more factories, we did not have to do this
      Element[] elements = getChildElements(e, "typeMapping");
      for (int i = 0; i < elements.length; i++)
      {
         TypeMapping mapping = new TypeMapping(elements[i]);
         deployTypeMapping(mapping);
      }
   }

   /**
    * the safe "constructor"
    */
   public static Deployment makeSafeDeployment(Element e, ClassLoader loader) throws WSDDException
   {
      ClassLoader old = Thread.currentThread().getContextClassLoader();
      Thread.currentThread().setContextClassLoader(loader);
      try
      {
         return new Deployment(e, loader);
      }
      finally
      {
         Thread.currentThread().setContextClassLoader(old);
      }
   }
   
   //
   // protected helpers
   //

   /**
    * return the deployment loader of this deployment
    */
   protected ClassLoader getDeploymentLoader()
   {
      return deploymentLoader;
   }

   /**
    * return the scoped deployment for this service
    */
   protected static Deployment getDeployment(WSDDService service)
   {
      return (
              (Deployment)service.getParametersTable().get(Constants.SERVICE_DEPLOYMENT_PARAMETER));
   }

   //
   // Public API
   //

   /* (non-Javadoc)
    * this is called whenever a service is parsed. This means that
    * we will mark it to belong to us (and our classloader). Furthermore,
    * upon existance of a dedicated paramter flag, we will shift the 
    * jaxrpc handler chain of the service into a separate property such 
    * that the provider class instead of the SoapService can control 
    * when it will be called.
    * @see org.jboss.axis.deployment.wsdd.WSDDDeployment#deployService(org.jboss.axis.deployment.wsdd.WSDDService)
    */

   public void deployService(WSDDService service)
   {
      // registers deployment instance in service
      // note that besides this entry, there will be only strings 
      // in the hashtable so maybe axis will hickup at some places
      // when it does not expect a real instance value
      service.getParametersTable().put(Constants.SERVICE_DEPLOYMENT_PARAMETER,
              this);
       
      // do the handler chain shifting only once for parsing  
      if (service.getHandlerInfoChain() != null && "true".equals(service.getParameter(Constants.USE_PROVIDER_HANDLER_CHAIN)))
      {
         service.getParametersTable().put(Constants.PROVIDER_HANDLER_CHAIN, service.getHandlerInfoChain());
         service.setParameter(Constants.USE_PROVIDER_HANDLER_CHAIN, "false");
         service.setHandlerInfoChain(null);
      }
      super.deployService(service);
   }

   /**
    * overwrite to equip with options
    */
   public void deployTypeMapping(WSDDTypeMapping typeMapping)
           throws WSDDException
   {
      // we only register our own stuff ;-)
      if (typeMapping instanceof TypeMapping)
      {
         if (tmrCreated)
         {
            try
            {
               installTypeMappingWithOptions((TypeMapping)typeMapping);
            }
            catch (ConfigurationException e)
            {
               throw new WSDDException("Could not install type mapping with options." + e);
            }
         }
         else
         {
            typeMappings.add(typeMapping);
         }
      }
   }

   /* 
    * overrides the getdeployedservices method in order
    * to prebuild the service instances (and hence 
    * address classloading issues when browsing
    * the services list during a "get")
    * implicitely requires a current message context
    * for which the serviceservlet must cater.
    * @see org.jboss.axis.EngineConfiguration#getDeployedServices()
    */

   public Iterator getDeployedServices() throws ConfigurationException
   {
      List serviceDescs = new java.util.ArrayList();
      WSDDService[] services = getServices();
      for (int count = 0; count < services.length; count++)
      {
         try
         {
            serviceDescs.add(getService(services[count].getQName()).
                    getServiceDescription());
         }
         catch (ConfigurationException ex)
         {
            // If it's non-fatal, just keep on going
            log.debug("Ingoring non-fatal exception: ", ex);
         }
      }
      return serviceDescs.iterator();
   }

   /**
    * hides the "old" uncoped way of constructing services and their
    * tmrs and wraps it into a classloader aware shell
    *
    * @param serviceName
    * @return
    * @throws ConfigurationException
    */

   private SOAPService getServiceInternal(QName serviceName)
           throws ConfigurationException
   {
      // This hack sets the deployment loader as the TCL
      // If this is not done the org.jboss.test.jbossnet.admindevel.ExampleTestCase fails on redeployment
      // The reason is that the HelloObj[] class gets loaded from a stale classloader
      ClassLoader deploymentLoader = getDeploymentLoader();
      Thread.currentThread().setContextClassLoader(deploymentLoader);

      MessageContext currentContext = MessageContext.getCurrentContext();
      if (currentContext != null)
         currentContext.setClassLoader(deploymentLoader);

      return super.getService(serviceName);
   }

   /* overrides the getService method in order
    * to address scoping issues when setting service 
    * affinity. requires a current message context
    * for which the serviceservlet must cater.
    * @see org.jboss.axis.EngineConfiguration#getService(javax.xml.namespace.QName)
    */

   public SOAPService getService(QName serviceName) throws ConfigurationException
   {
      // we lookup the service meta-data
      WSDDService wsddService = getWSDDService(serviceName);
      if (wsddService != null)
      {
         // if its is there, we delegate to the scope deployment 
         // to do the real work 
         return getDeployment(wsddService).getServiceInternal(serviceName);
      }
      return null;
   }

   /* (non-Javadoc)
    * the only place were ive seen accesses to that ugly function was in
    * the client code when parsing replies to a non-configured service in order
    * to get operationdescs. Seems like this will not break anything ...
    * @see org.jboss.axis.EngineConfiguration#getServiceByNamespaceURI(java.lang.String)
    */

   public SOAPService getServiceByNamespaceURI(String arg0)
           throws ConfigurationException
   {
      return null;
   }

   /* (non-Javadoc)
    * when we deploy to a registry, we only want our services to be seen,
    * but do not want our typemappings there
    * @see org.jboss.axis.deployment.wsdd.WSDDDeployment#deployToRegistry(org.jboss.axis.deployment.wsdd.WSDDDeployment)
    */
   public void deployToRegistry(WSDDDeployment arg0)
           throws ConfigurationException
   {
      // do the usual deployment stuff (remember that super.typeMappings
      // will still be empty at this point).
      super.deployToRegistry(arg0);
      // pretend the same engine
      configureEngine(arg0.getEngine());
      // we delegate to the registries type mapping registry (and get
      // our local typemappings installed here before)
      getTypeMappingRegistry().delegate(arg0.getTypeMappingRegistry());
   }


   /* (non-Javadoc)
    * @see org.jboss.axis.EngineConfiguration#getTypeMappingRegistry()
    */
   public TypeMappingRegistry getTypeMappingRegistry()
           throws ConfigurationException
   {
      // if we need to create the tmr for the first time
      if (!tmrCreated)
      {
         tmrCreated = true;
         // we need to get all our typemappings known by our superclass
         // because we want to hide them from the global registry 
         for (Iterator allTms = typeMappings.iterator(); allTms.hasNext();)
         {
            TypeMapping nextMapping = (TypeMapping)allTms.next();
            installTypeMappingWithOptions(nextMapping);
         }
         tmrCreated = true;
      }
      // do the super call in each case
      return super.getTypeMappingRegistry();
   }

   /**
    * this helper serves to install typemappings with additional options
    */
   protected void installTypeMappingWithOptions(TypeMapping nextMapping) throws ConfigurationException
   {
      ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
      Thread.currentThread().setContextClassLoader(getDeploymentLoader());
      try
      {
         // first make sure that the mapping is installed correctly
         super.deployTypeMapping(nextMapping);
         // then access the factories in order to put the info there
         org.jboss.axis.encoding.TypeMapping axisMapping =
                 (org.jboss.axis.encoding.TypeMapping)getTypeMappingRegistry()
                 .getTypeMapping(nextMapping.getEncodingStyle());
         DeserializerFactory dser =
                 axisMapping.getDeserializer(nextMapping.getQName());
         if (dser instanceof ParameterizableDeserializerFactory)
         {
            // Load up our params
            ((ParameterizableDeserializerFactory)dser).setOptions(((TypeMapping)nextMapping).getParametersTable());
         }
      }
      finally
      {
         Thread.currentThread().setContextClassLoader(oldLoader);
      }
   }

}