/***************************************
 *                                     *
 *  JBoss: The OpenSource J2EE WebOS   *
 *                                     *
 *  Distributable under LGPL license.  *
 *  See terms of license at gnu.org.   *
 *                                     *
 ***************************************/
 
package org.jboss.iiop;

import java.io.InputStream;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Properties;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.Name;
import javax.naming.Reference;
import javax.naming.spi.ObjectFactory;

import javax.ejb.spi.HandleDelegate;

import org.jboss.system.ServiceMBeanSupport;
import org.jboss.system.server.ServerConfigUtil;
import org.omg.CORBA.ORB;
import org.omg.CORBA.Policy;
import org.omg.PortableServer.IdAssignmentPolicy;
import org.omg.PortableServer.IdAssignmentPolicyValue;
import org.omg.PortableServer.LifespanPolicy;
import org.omg.PortableServer.LifespanPolicyValue;
import org.omg.PortableServer.POA;
import org.omg.PortableServer.POAHelper;

import org.w3c.dom.Element;

import org.jboss.iiop.naming.ORBInitialContextFactory;
import org.jboss.metadata.MetaData;
import org.jboss.security.SecurityDomain;
import org.jboss.system.Registry;
import org.jboss.proxy.ejb.handle.HandleDelegateImpl;

/**
 *  This is a JMX service that provides the default CORBA ORB
 *  for JBoss to use.
 *      
 *  @author <a href="mailto:osh@sparre.dk">Ole Husgaard</a>
 *  @author <a href="mailto:reverbel@ime.usp.br">Francisco Reverbel</a>
 *  @version $Revision: 1.38.2.3 $
 */
public class CorbaORBService
      extends ServiceMBeanSupport
      implements CorbaORBServiceMBean, ObjectFactory
{
   // Constants -----------------------------------------------------

   public static String ORB_NAME = "JBossCorbaORB";
   public static String POA_NAME = "JBossCorbaPOA";
   public static String IR_POA_NAME = "JBossCorbaInterfaceRepositoryPOA";
   public static String SSL_DOMAIN = "JBossCorbaSSLDomain";
    
   // Attributes ----------------------------------------------------

   private String orbClass = null;
   private String orbSingletonClass = null;
   private String orbSingletonDelegate = null;
   private String orbPropertiesFileName = "orb-properties-file-not-defined";
   private Element portableInterceptorInitializers = null;
   private int port = 0;
   private int sslPort = 0;
   private String sslDomain = null;
   private boolean sunJDK14IsLocalBugFix = true;

   // Static --------------------------------------------------------

   private static ORB orb;
   private static POA poa;
   private static POA irPoa;
   private static HandleDelegate hd;
   private static int oaSslPort;

   /** 
    * Addition of SSL components to IORs is disabled by default.
    * This must remain off for interoperation with IONA's ASP 6.0,
    * which does not accept IORs with SSL components.
    */
   private static boolean sslComponentsEnabledFlag = false;

   /** 
    * Sending an IIOP reply that carries both a CompleteEstablishContext 
    * (SAS accept) reply and an exception is enabled by default, because
    * the CSIv2 spect does not explicitly disallow an SAS accept in an
    * IIOP exception reply. This flag must be turned off for interoperation 
    * with IONA's ASP 6.0, which throws an ArrayIndexOutOfBoundsException 
    * when it receives an IIOP reply carrying both an application exception 
    * and a SAS reply CompleteEstablishContext.
    */
   private static boolean sendSasAcceptWithExceptionEnabledFlag = true;

   /**
    * Returns the actual SSL port. This method is intended to be called
    * by the CSIv2 IOR interceptor, which needs to know the SSL port.
    */
   public static int getTheActualSSLPort() 
   {
      return oaSslPort;
   }

   /**
    * Returns true if addition of SSL components to IORs is enabled.
    * This method is intended to be called by the CSIv2 IOR interceptor.
    */
   public static boolean getSSLComponentsEnabledFlag()
   {
      return sslComponentsEnabledFlag;
   }
   
   /**
    * Returns true if sending an SAS accept reply together with an IIOP
    * exception reply is enabled. This method is intended to be called by 
    * the CSIv2 server request interceptor.
    */
   public static boolean getSendSASAcceptWithExceptionEnabledFlag()
   {
      return sendSasAcceptWithExceptionEnabledFlag;
   }

   // ServiceMBeanSupport overrides ---------------------------------

   protected void startService() 
      throws Exception 
   {

      Properties props = new Properties();

      // Read orb properties file into props
      ClassLoader cl = Thread.currentThread().getContextClassLoader();
      InputStream is = cl.getResourceAsStream(orbPropertiesFileName);
      props.load(is);
      String oaiAddr = props.getProperty("OAIAddr");
      if (oaiAddr == null)
         oaiAddr = ServerConfigUtil.getSpecificBindAddress();
      if (oaiAddr != null)
         props.setProperty("OAIAddr", oaiAddr);
      log.debug("Using OAIAddr=" + oaiAddr);

      // Set ORB initialization properties
      Properties systemProps = System.getProperties();
      if (orbClass != null) {
         props.put("org.omg.CORBA.ORBClass", orbClass);
         systemProps.put("org.omg.CORBA.ORBClass", orbClass);
      }
      if (orbSingletonClass != null) {
         props.put("org.omg.CORBA.ORBSingletonClass", orbSingletonClass);
         systemProps.put("org.omg.CORBA.ORBSingletonClass", orbSingletonClass);
      }
      if (orbSingletonDelegate != null)
         systemProps.put(org.jboss.system.ORBSingleton.DELEGATE_CLASS_KEY,
                         orbSingletonDelegate);

      // JacORB-specific hack: add jacorb.config.log.verbosity to the system 
      // properties so that it is seen by JacORB at configuration time.
      // This allows us (by setting jacorb.config.log.verbosity=0) to get rid 
      // of the messages "jacorb.home unset! Will use '.'" and 
      // "File ./jacorb.properties for configuration jacorb not found", which
      // would otherwise be sent to the standard output.
      String str = props.getProperty("jacorb.config.log.verbosity");
      if (str != null)
         systemProps.put("jacorb.config.log.verbosity", str);

      // This is for SUN JDK bug with isLocal
      if (sunJDK14IsLocalBugFix)
      {
         /* Validate that the class can actually be loaded. It cannot be used
         under java 5 for example because the SunJDK14IsLocalBugFix base class
         does not exist
         */
         try
         {
            Class SunJDK14IsLocalBugFix = cl.loadClass("org.jboss.iiop.SunJDK14IsLocalBugFix");
            log.debug("Was able to load SunJDK14IsLocalBugFix, class="+SunJDK14IsLocalBugFix);
            // Its loadable to use it
            systemProps.put("javax.rmi.CORBA.UtilClass", "org.jboss.iiop.SunJDK14IsLocalBugFix");
         }
         catch(Throwable t)
         {
            log.debug("Ignoring sunJDK14IsLocalBugFix=true due to inability to load org.jboss.iiop.SunJDK14IsLocalBugFix", t);
         }
      }

      System.setProperties(systemProps);
      
      // Add portable interceptor initializers
      Iterator initializerElements = 
            MetaData.getChildrenByTagName(portableInterceptorInitializers, 
                                          "initializer");
      if (initializerElements != null)
      {
         while (initializerElements.hasNext())
         {
            Element initializerElement = (Element) initializerElements.next();
            String portableInterceptorInitializerClassName = 
                  MetaData.getElementContent(initializerElement);
                  log.debug("Adding portable interceptor initializer: " +
                           portableInterceptorInitializerClassName);
            if (portableInterceptorInitializerClassName != null 
                  && !portableInterceptorInitializerClassName.equals(""))
               props.put("org.omg.PortableInterceptor.ORBInitializerClass."
                         + portableInterceptorInitializerClassName, "");
         }
      }

      // Allows overriding of OAPort/OASSLPort 
      // from config file through MBean config
      if (port != 0)
         props.put("OAPort", Integer.toString(this.port));
      if (sslPort != 0)
         props.put("OASSLPort", Integer.toString(this.sslPort));

      // Keep the actual SSL port in a static field 
      // (the CSIv2 IOR interceptor needs this value)
      String oaSslPortString = props.getProperty("OASSLPort");
      if (oaSslPortString != null)
         oaSslPort = Integer.parseInt(oaSslPortString);

      // Allow SSL domain to be specified through MBean config
      if (sslDomain != null) 
      {
         InitialContext ctx = new InitialContext();
         log.debug("sslDomain: " + sslDomain);
         try 
         {
            SecurityDomain domain = (SecurityDomain) ctx.lookup(sslDomain);

            // Make SSL domain available to socket factories
            Registry.bind(SSL_DOMAIN, domain);
         }
         catch (Exception e)
         {
            log.warn("Security domain " + sslDomain + " not found");
            if (log.isDebugEnabled())
               log.debug("Exception looking up " + sslDomain + ": ", e);
         }
      }

      // Initialize the ORB
      orb = ORB.init(new String[0], props);
      bind(ORB_NAME, "org.omg.CORBA.ORB");
      CorbaORB.setInstance(orb);
      ORBInitialContextFactory.setORB(orb);

      // Initialize the POA
      poa = POAHelper.narrow(orb.resolve_initial_references("RootPOA"));
      poa.the_POAManager().activate();
      bind(POA_NAME, "org.omg.PortableServer.POA");

      // Make the ORB work
      new Thread(
         new Runnable() {
            public void run() {
               orb.run();
            }
         }, "ORB thread"
      ).start(); 

      // Create a POA for interface repositories
      try {
         LifespanPolicy lifespanPolicy = 
            poa.create_lifespan_policy(LifespanPolicyValue.PERSISTENT);
         IdAssignmentPolicy idAssignmentPolicy = 
            poa.create_id_assignment_policy(IdAssignmentPolicyValue.USER_ID);

         irPoa = poa.create_POA("IR", null,
                                new Policy[]{lifespanPolicy, 
                                             idAssignmentPolicy});
         bind(IR_POA_NAME, "org.omg.PortableServer.POA");
         
         // Activate the poa
         irPoa.the_POAManager().activate();
         
      } catch (Exception ex) {
         getLog().error("Error in IR POA initialization", ex);
      }
      
      // Keep a HandleDelegate
      hd = new HandleDelegateImpl();
   }

   protected void stopService() throws Exception
   {
      /*
      if( mJSR77 != null )
      {
          RMI_IIOPResource.destroy(
             getServer(),
             ORB_NAME
          );
          mJSR77 = null;
      }
      */
      try {
         // Unbind from JNDI
         unbind(ORB_NAME);
         unbind(POA_NAME);
         unbind(IR_POA_NAME);

         // Stop ORB
         orb.shutdown(false);
         
         // Unbind SSL domain
         Registry.unbind(SSL_DOMAIN);
      } catch (Exception e) {
         log.error("Exception while stopping ORB service", e);
      }
   }

   // CorbaORBServiceMBean implementation ---------------------------

   public ORB getORB()
   {
      return orb;
   }

   public HandleDelegate getHandleDelegate()
   {
      return hd;
   }
   
   public String getORBClass()
   {
      return orbClass;
   }

   public void setORBClass(String orbClass)
   {
      this.orbClass = orbClass;
   }

   public String getORBSingletonClass()
   {
      return orbSingletonClass;
   }

   public void setORBSingletonClass(String orbSingletonClass)
   {
      this.orbSingletonClass = orbSingletonClass;
   }

   public String getORBSingletonDelegate()
   {
      return orbSingletonDelegate;
   }

   public void setORBSingletonDelegate(String orbSingletonDelegate)
   {
      this.orbSingletonDelegate = orbSingletonDelegate;
   }

   public void setORBPropertiesFileName(String orbPropertiesFileName)
   {
      this.orbPropertiesFileName = orbPropertiesFileName;
   }

   public String getORBPropertiesFileName()
   {
      return orbPropertiesFileName;
   }

   public Element getPortableInterceptorInitializers()
   {
      return portableInterceptorInitializers;
   }

   public void setPortableInterceptorInitializers(
                                       Element portableInterceptorInitializers)
   {
      this.portableInterceptorInitializers = portableInterceptorInitializers;
   }

   public void setPort(int port)
   {
      this.port = port;
   }

   public int getPort()
   {
      return this.port;
   }

   public void setSSLPort(int sslPort)
   {
      this.sslPort = sslPort;
   }

   public int getSSLPort()
   {
      return this.sslPort;
   }

   public void setSecurityDomain(String sslDomain)
   {
      this.sslDomain = sslDomain;
   }

   public String getSecurityDomain()
   {
      return this.sslDomain;
   }

   public boolean getSSLComponentsEnabled()
   {
      return CorbaORBService.sslComponentsEnabledFlag;
   }

   public void setSSLComponentsEnabled(boolean sslComponentsEnabled)
   {
      CorbaORBService.sslComponentsEnabledFlag = sslComponentsEnabled;
   }

   public boolean getSendSASAcceptWithExceptionEnabled()
   {
      return CorbaORBService.sendSasAcceptWithExceptionEnabledFlag;
   }

   public void setSendSASAcceptWithExceptionEnabled(boolean value)
   {
      CorbaORBService.sendSasAcceptWithExceptionEnabledFlag = value;
   }

   public boolean getSunJDK14IsLocalBugFix()
   {
      return sunJDK14IsLocalBugFix;
   }

   public void setSunJDK14IsLocalBugFix(boolean sunJDK14IsLocalBugFix)
   {
      this.sunJDK14IsLocalBugFix = sunJDK14IsLocalBugFix;
   }

   // ObjectFactory implementation ----------------------------------

   public Object getObjectInstance(Object obj, Name name,
                                   Context nameCtx, Hashtable environment)
      throws Exception
   {
      String s = name.toString();
      if (getLog().isTraceEnabled())
         getLog().trace("getObjectInstance: obj.getClass().getName=\"" +
                        obj.getClass().getName() +
                        "\n                   name=" + s);
      if (ORB_NAME.equals(s))
         return orb;
      if (POA_NAME.equals(s))
         return poa;
      if (IR_POA_NAME.equals(s))
         return irPoa;
      return null;
   }


   // Private -------------------------------------------------------

   private void bind(String name, String className)
      throws Exception
   {
      Reference ref = new Reference(className, getClass().getName(), null);
      new InitialContext().bind("java:/"+name, ref);
   }

   private void unbind(String name)
      throws Exception
   {
      new InitialContext().unbind("java:/"+name);
   }

}