/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2015, 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.wsf.stack.cxf.configuration;

import java.lang.reflect.Constructor;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.xml.stream.XMLStreamReader;
import javax.xml.ws.handler.Handler;
import javax.xml.ws.soap.SOAPBinding;

import org.apache.cxf.Bus;
import org.apache.cxf.annotations.UseAsyncMethod;
import org.apache.cxf.buslifecycle.BusLifeCycleListener;
import org.apache.cxf.buslifecycle.BusLifeCycleManager;
import org.apache.cxf.configuration.Configurer;
import org.apache.cxf.endpoint.ServerLifeCycleManager;
import org.apache.cxf.interceptor.OneWayProcessorInterceptor;
import org.apache.cxf.management.InstrumentationManager;
import org.apache.cxf.management.counters.CounterRepository;
import org.apache.cxf.management.interceptor.ResponseTimeMessageInInterceptor;
import org.apache.cxf.management.interceptor.ResponseTimeMessageInvokerInterceptor;
import org.apache.cxf.management.interceptor.ResponseTimeMessageOutInterceptor;
import org.apache.cxf.resource.ResourceManager;
import org.apache.cxf.resource.ResourceResolver;
import org.apache.cxf.service.factory.FactoryBeanListener;
import org.apache.cxf.service.factory.FactoryBeanListenerManager;
import org.apache.cxf.service.invoker.Invoker;
import org.apache.cxf.staxutils.XMLStreamReaderWrapper;
import org.apache.cxf.transport.http.HttpDestinationFactory;
import org.apache.cxf.transport.servlet.ServletDestinationFactory;
import org.apache.cxf.workqueue.AutomaticWorkQueue;
import org.apache.cxf.workqueue.AutomaticWorkQueueImpl;
import org.apache.cxf.workqueue.WorkQueueManager;
import org.apache.cxf.ws.addressing.WSAddressingFeature;
import org.apache.cxf.ws.discovery.listeners.WSDiscoveryServerListener;
import org.apache.cxf.ws.policy.AlternativeSelector;
import org.apache.cxf.ws.policy.PolicyEngine;
import org.apache.cxf.ws.policy.selector.MaximalAlternativeSelector;
import org.apache.cxf.ws.rm.RMManager;
import org.apache.cxf.wsdl.WSDLManager;
import org.apache.cxf.wsdl11.WSDLManagerImpl;
import org.jboss.ws.api.annotation.PolicySets;
import org.jboss.ws.api.binding.BindingCustomization;
import org.jboss.wsf.spi.SPIProvider;
import org.jboss.wsf.spi.WSFException;
import org.jboss.wsf.spi.classloading.ClassLoaderProvider;
import org.jboss.wsf.spi.deployment.AnnotationsInfo;
import org.jboss.wsf.spi.deployment.ArchiveDeployment;
import org.jboss.wsf.spi.deployment.Deployment;
import org.jboss.wsf.spi.metadata.config.SOAPAddressRewriteMetadata;
import org.jboss.wsf.spi.metadata.webservices.JBossWebservicesMetaData;
import org.jboss.wsf.spi.security.JASPIAuthenticationProvider;
import org.jboss.wsf.stack.cxf.JBossWSInvoker;
import org.jboss.wsf.stack.cxf.Loggers;
import org.jboss.wsf.stack.cxf.Messages;
import org.jboss.wsf.stack.cxf.addressRewrite.SoapAddressRewriteHelper;
import org.jboss.wsf.stack.cxf.client.Constants;
import org.jboss.wsf.stack.cxf.client.configuration.FeatureUtils;
import org.jboss.wsf.stack.cxf.client.configuration.InterceptorUtils;
import org.jboss.wsf.stack.cxf.client.configuration.JBossWSBusFactory;
import org.jboss.wsf.stack.cxf.client.configuration.JBossWSConfigurerImpl;
import org.jboss.wsf.stack.cxf.deployment.EndpointImpl;
import org.jboss.wsf.stack.cxf.deployment.WSDLFilePublisher;
import org.jboss.wsf.stack.cxf.extensions.policy.PolicySetsAnnotationListener;
import org.jboss.wsf.stack.cxf.interceptor.EndpointAssociationInterceptor;
import org.jboss.wsf.stack.cxf.interceptor.GracefulShutdownInterceptor;
import org.jboss.wsf.stack.cxf.interceptor.HandlerAuthInterceptor;
import org.jboss.wsf.stack.cxf.interceptor.NsCtxSelectorStoreInterceptor;
import org.jboss.wsf.stack.cxf.interceptor.WSDLSoapAddressRewriteInterceptor;
import org.jboss.wsf.stack.cxf.management.InstrumentationManagerExtImpl;
import org.jboss.wsf.stack.cxf.metadata.services.DDBeans;
import org.jboss.wsf.stack.cxf.metadata.services.DDEndpoint;
import org.jboss.wsf.stack.cxf.security.authentication.AuthenticationMgrSubjectCreatingInterceptor;

/**
 * A wrapper of the Bus for performing most of the configurations required on it by JBossWS
 * 
 * @author [email protected]
 * @since 25-Mar-2010
 *
 */
public class BusHolder
{
   private boolean configured = false;

   protected DDBeans metadata;
   protected List<EndpointImpl> endpoints = new LinkedList<EndpointImpl>();
   
   protected Bus bus;
   protected BusHolderLifeCycleListener busHolderListener;
   protected FactoryBeanListener policySetsListener;
   
   public BusHolder()
   {
      
   }
   
   public BusHolder(Bus bus)
   {
      setBus(bus);
   }

   public BusHolder(DDBeans metadata)
   {
      super();
      this.metadata = metadata;
      bus = new JBossWSBusFactory().createBus();
      //Force servlet transport to prevent CXF from using Jetty / http server or other transports
      bus.setExtension(new ServletDestinationFactory(), HttpDestinationFactory.class);
   }

   /**
    * Update the Bus held by the this instance using the provided parameters.
    * This basically prepares the bus for being used with JBossWS.
    * 
    * @param resolver               The ResourceResolver to configure, if any
    * @param configurer             The JBossWSCXFConfigurer to install in the bus, if any
    * @param wsmd                   The current JBossWebservicesMetaData, if any
    * @param dep                    The current deployment
    */
   public void configure(ResourceResolver resolver, Configurer configurer, JBossWebservicesMetaData wsmd, Deployment dep)
   {
      if (configured)
      {
         throw Messages.MESSAGES.busAlreadyConfigured(bus);
      }
      
      bus.setProperty(org.jboss.wsf.stack.cxf.client.Constants.DEPLOYMENT_BUS, true);
      busHolderListener = new BusHolderLifeCycleListener();
      bus.getExtension(BusLifeCycleManager.class).registerLifeCycleListener(busHolderListener);
      setWSDLManagerStreamWrapper(bus);
      
      if (configurer != null)
      {
         bus.setExtension(configurer, Configurer.class);
      }
      Map<String, String> props = getProperties(wsmd);
      
      setInterceptors(bus, dep, props);
      dep.addAttachment(Bus.class, bus);

      try
      {
         final JASPIAuthenticationProvider jaspiProvider = SPIProvider.getInstance().getSPI(
               JASPIAuthenticationProvider.class,
               ClassLoaderProvider.getDefaultProvider().getServerIntegrationClassLoader());
         
         if (jaspiProvider != null && jaspiProvider.enableServerAuthentication(dep, wsmd))
         {
            bus.getInInterceptors().add(new AuthenticationMgrSubjectCreatingInterceptor());
         }
      }
      catch (WSFException e)
      {
         Loggers.DEPLOYMENT_LOGGER.cannotFindJaspiClasses();
      }
      
      setResourceResolver(bus, resolver);
      
      if (bus.getExtension(PolicyEngine.class) != null) 
      {
         bus.getExtension(PolicyEngine.class).setAlternativeSelector(getAlternativeSelector(props));
      }     
      setCXFManagement(bus, props); //*first* enabled cxf management if required, *then* add anything else which could be manageable (e.g. work queues)
      setAdditionalWorkQueues(bus, props); 
      setWSDiscovery(bus, props);
      
      AnnotationsInfo ai = dep.getAttachment(AnnotationsInfo.class);
      if (ai == null || ai.hasAnnotatedClasses(PolicySets.class.getName())) {
         policySetsListener = new PolicySetsAnnotationListener(dep.getClassLoader());
         bus.getExtension(FactoryBeanListenerManager.class).addListener(policySetsListener);
      }
      
      //default to USE_ORIGINAL_THREAD = true; this can be overridden by simply setting the property in the endpoint or in the message using an interceptor
      //this forces one way operation to use original thread, which is required for ejb webserivce endpoints to avoid authorization failures from ejb container
      //and is a performance improvement in general when running in-container, as CXF needs to cache the message to free the thread, which is expensive
      //(moreover the user can tune the web container thread pool instead of expecting cxf to fork new threads)
      bus.setProperty(OneWayProcessorInterceptor.USE_ORIGINAL_THREAD, true);
      
      //[JBWS-3135] enable decoupled faultTo. This is an optional feature in cxf and we need this to be default to make it same behavior with native stack
      bus.setProperty("org.apache.cxf.ws.addressing.decoupled_fault_support", true);
      
      FeatureUtils.addFeatures(bus, bus, props);

      for (DDEndpoint dde : metadata.getEndpoints())
      {
         EndpointImpl endpoint = new EndpointImpl(bus, newInstance(dde.getImplementor()));
         if (dde.getInvoker() != null)
            endpoint.setInvoker(newInvokerInstance(dde.getInvoker(), dep));
         endpoint.setAddress(dde.getAddress());
         endpoint.setEndpointName(dde.getPortName());
         endpoint.setServiceName(dde.getServiceName());
         endpoint.setWsdlLocation(dde.getWsdlLocation());
         setHandlers(endpoint, dde);
         if (dde.getProperties() != null)
         {
            Map<String, Object> p = new HashMap<String, Object>();
            p.putAll(dde.getProperties());
            endpoint.setProperties(p);
         }
         if (dde.isAddressingEnabled()) 
         {
            WSAddressingFeature addressingFeature = new WSAddressingFeature();
            addressingFeature.setAddressingRequired(dde.isAddressingRequired());
            addressingFeature.setResponses(dde.getAddressingResponses());
            endpoint.getFeatures().add(addressingFeature);
         }
         endpoint.setPublishedEndpointUrl(dde.getPublishedEndpointUrl());
         endpoint.setSOAPAddressRewriteMetadata(dep.getAttachment(SOAPAddressRewriteMetadata.class));
         endpoint.publish();
         endpoints.add(endpoint);
         if (dde.isMtomEnabled())
         {
            SOAPBinding binding = (SOAPBinding) endpoint.getBinding();
            binding.setMTOMEnabled(true);
         }
      }
      configured = true;
   }
   
   @SuppressWarnings("rawtypes")
   private static void setHandlers(EndpointImpl endpoint, DDEndpoint dde)
   {
      List<String> handlers = dde.getHandlers();
      if (handlers != null && !handlers.isEmpty())
      {
         List<Handler> handlerInstances = new LinkedList<Handler>();
         for (String handler : handlers)
         {
            handlerInstances.add((Handler) newInstance(handler));
         }
         endpoint.setHandlers(handlerInstances);
      }
   }
   
   public void close()
   {
      //Move this stuff to the bus (our own impl)?
      RMManager rmManager = bus.getExtension(RMManager.class);
      if (rmManager != null)
      {
         rmManager.shutdown();
      }
      
      for (EndpointImpl endpoint : endpoints)
      {
         if (endpoint.isPublished())
         {
            endpoint.stop();
         }
      }
      endpoints.clear();
      
      //call bus shutdown unless the listener tells us shutdown has already been asked
      if (busHolderListener == null || !busHolderListener.isPreShutdown())
      {
         bus.shutdown(true);
      }
      busHolderListener = null;
      bus.getExtension(FactoryBeanListenerManager.class).removeListener(policySetsListener);
      policySetsListener = null;