/*
 * Copyright (c) 2003-2004, KNOPFLERFISH project
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following
 * conditions are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above/*
 * Copyright (c) 2003-2004, KNOPFLERFISH project
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following
 * conditions are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above
 *   copyright notice, this list of conditions and the following
 *   disclaimer in the documentation and/or other materials
 *   provided with the distribution.
 *
 * - Neither the name of the KNOPFLERFISH project nor the names of its
 *   contributors may be used to endorse or promote products derived
 *   from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.knopflerfish.bundle.axis;

import java.io.InputStream;

import java.net.URL;

import org.apache.axis.EngineConfiguration;
import org.apache.axis.configuration.FileProvider;
import org.apache.axis.server.AxisServer;
import org.apache.axis.utils.XMLUtils;
//import org.apache.axis.WSDDEngineConfiguration;


import org.knopflerfish.service.log.LogRef;
import org.knopflerfish.axis.ObjectSOAPService;

import org.knopflerfish.util.servlet.ServletDescriptor;
import org.knopflerfish.util.servlet.WebApp;
import org.knopflerfish.util.servlet.WebAppDescriptor;

import org.osgi.framework.*;

import java.util.Map;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;

import org.knopflerfish.service.axis.AxisAdmin;

/** The <code>Activator</code> is the activator for the Axis OSGi bundle.
 *  Further it handles service registration events for SOAP services.
 * @author Lasse Helander (lars-erik.helander@home.se)
 */
public class Activator
   implements BundleActivator, ServiceListener {
   public static BundleContext axisBundle = null;
   public static LogRef log = null;
   private static AxisServer axisServer = null;
   private WebApp webApp = null;

  private AxisAdminImpl axisAdmin;

   public static AxisServer getAxisServer() {
      return axisServer;
   }

   public void start(BundleContext bc)
              throws BundleException {
      try {
         log = new LogRef(bc, true);
         axisBundle = bc;
	 setupAxis();
      } catch (Exception e) {
	log.error("Exception when starting bundle", e);
	throw new BundleException("Failed to start server");
      }
   }
  
  void setupAxis() throws Exception {
    ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
    
    try {
      Thread.currentThread().setContextClassLoader(Activator.class.getClassLoader());
      URL url = this.getClass().getResource("/axis/server-config.wsdd");
      InputStream is = url.openStream();
      
      EngineConfiguration fromBundleResource = new FileProvider(is);
      
      log.info("Configuration file read.");
      axisServer = new AxisServer(fromBundleResource);
      log.info("Axis server started.");
      webApp = new WebApp(getWebAppDescriptor());
      webApp.start(axisBundle);
      log.info("Web application started.");
      axisBundle.addServiceListener(this);   
    
      // Make sure we get services already registered
      ServiceReference[] srl = axisBundle.getServiceReferences(null, null);
      for(int i = 0; srl != null && i < srl.length; i++) {
	serviceChanged(new ServiceEvent(ServiceEvent.REGISTERED, srl[i]));
      }
      
      axisAdmin = new AxisAdminImpl(this);
      
      Hashtable props = new Hashtable();
      props.put(AxisAdmin.SOAP_SERVICE_NAME, "axisadmin");
      
      axisBundle.registerService(AxisAdmin.class.getName(),
				 axisAdmin,
				 props);
    } finally {
      Thread.currentThread().setContextClassLoader(oldLoader);
    }
   }

   public void stop(BundleContext bc)
             throws BundleException {
      try {
	 axisBundle.removeServiceListener(this);   
         webApp.stop(bc);
         webApp = null;
         axisBundle = null;
         axisServer = null;
         log.close();
         log = null;
      } catch (Exception e) {
         log.error("Exception when stopping bundle", e);
         throw new BundleException("Failed to stop server", e);
      }
   }

   private WebAppDescriptor getWebAppDescriptor() {
      WebAppDescriptor wad = new WebAppDescriptor();

      wad.servlet = new ServletDescriptor[1];
      wad.context = "/axis";
      wad.servlet[0] = new ServletDescriptor("/services", 
                                             new ServicesServlet());
      return wad;
   }
   
  
  Map exportedServices = new HashMap();
   
  public void serviceChanged(ServiceEvent event) {
    try {
      switch(event.getType()) {
      case ServiceEvent.REGISTERED:
	{
	  ServiceReference sr = event.getServiceReference();
	  String serviceName = (String) sr.getProperty(AxisAdmin.SOAP_SERVICE_NAME);
	  String[] classes   = (String[]) sr.getProperty(Constants.OBJECTCLASS);
	  String   allowedMethods   = (String) sr.getProperty(AxisAdmin.SOAP_SERVICE_METHODS);
	  if (serviceName != null) {
	    log.info("added service "+serviceName);
	    
	    // throws excpetion if name is invalid
	    assertServiceName(serviceName);
	    
	    Object serviceObj = axisBundle.getService(sr);
	    
	    getAxisServer().getClassCache()
	      .registerClass(serviceObj.getClass().getName(),
			     serviceObj.getClass());
	    
	    ObjectSOAPService soapService = 
	      new ObjectSOAPService(axisServer,serviceName,serviceObj,
				    classes,
				    allowedMethods);
	    
	    soapService.deploy();
	    
	    exportedServices.put(sr, soapService);
	  }
	} 
	break;
	case ServiceEvent.UNREGISTERING:
	  {
	    ServiceReference sr = event.getServiceReference();
	    String serviceName  = (String) sr.getProperty(AxisAdmin.SOAP_SERVICE_NAME);
	    if (serviceName != null) {
	      
	      ObjectSOAPService soapService 
		= (ObjectSOAPService)exportedServices.get(sr);
	      if(soapService != null) {
		Object serviceObj = soapService.getServiceObject();
		
		getAxisServer().getClassCache()
		  .deregisterClass(soapService.getClass().getName());
		
		soapService.undeploy();
		log.info("removed service "+serviceName);

		exportedServices.remove(sr);
	      }
	      
	      //   (new ObjectSOAPService(axisServer,serviceName,null)).undeploy();
	    }
	  }
	  break;
      }
    } catch (Exception e) {
      log.error("serviceChanged() failed", e);
    }
  }

  /**
   * Check if service name is OK for publishing as SOAP service.
   *
   * This incluced checking for previous registrations at the same name.
   *
   * @throws IllegalArgumentException if name is not valid
   */
  void assertServiceName(String serviceName) {
    if(serviceName == null) {
      throw new IllegalArgumentException("Service name cannot be null");
    }
    if("".equals(serviceName)) {
      throw new IllegalArgumentException("Service name cannot be empty string");
    }

    for(int i = 0; i < serviceName.length(); i++) {
      if(Character.isWhitespace(serviceName.charAt(i))) {
	throw new IllegalArgumentException("Service name '" + serviceName +
					   "' cannot contain whitespace");
      }
    }

    synchronized(exportedServices) {
      for(Iterator it = exportedServices.keySet().iterator(); it.hasNext();) {
	ServiceReference sr         = (ServiceReference)it.next();
	String           name       = (String)sr.getProperty(AxisAdmin.SOAP_SERVICE_NAME);
	//	Object           serviceObj = (String)exportedServices.get(sr);
	if(name.equals(serviceName)) {
	  throw new IllegalArgumentException("Service '" + name + 
					     "' is already exported");
	}
      }
    }
  }
}

