/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2008, 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.iiop;

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

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

import org.jboss.iiop.naming.ORBInitialContextFactory;
import org.jboss.metadata.MetaData;
import org.jboss.proxy.ejb.handle.HandleDelegateImpl;
import org.jboss.security.SecurityDomain;
import org.jboss.system.Registry;
import org.jboss.system.ServiceMBeanSupport;
import org.jboss.config.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.IdUniquenessPolicyValue;
import org.omg.PortableServer.LifespanPolicy;
import org.omg.PortableServer.LifespanPolicyValue;
import org.omg.PortableServer.POA;
import org.omg.PortableServer.POAHelper;
import org.omg.PortableServer.RequestProcessingPolicyValue;
import org.omg.PortableServer.ServantRetentionPolicyValue;
import org.w3c.dom.Element;

/**
 *  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: 99241 $
 */
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;

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

   private static ORB orb;
   private static POA poa;
   private static POA otsPoa;
   private static POA otsResourcePoa;
   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;

   /**
    * True if the OTS context should be sent along with outgoing requests. 
    */
   private static boolean otsContextPropagationEnabledFlag = false;

   /**
    * 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;
   }
   
   /**
    * Returns true if the OTS context should be sent along with outgoing 
    * requests. This method is intended to be called by the OTS client request 
    * interceptor installed in the JBoss server.
    */
   public static boolean getOTSContextPropagationEnabledFlag()
   {
      return otsContextPropagationEnabledFlag;
   }
   
   /**
    * Returns the CORBA transaction service main POA. Our OTS implementation
    * uses this POA for all OTS objects that are not Resource instances.
    */
   public static POA getOtsPoa()
   {
      return otsPoa;
   }
   
   /**
    * Returns the CORBA transaction service secondary POA. Our OTS 
    * implementation uses this POA for OTS Resources, so that they can have
    * their own default servant. (The name clash between Terminator::rollback 
    * and Resource::rollback disallows us to use a single default servant for
    * all OTS objects.)  
    */
   public static POA getOtsResourcePoa()
   {
      return otsResourcePoa;
   }

   // 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);

      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");
            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"));
      bind(POA_NAME, "org.omg.PortableServer.POA");

      // We will now create POAs for the CORBA transaction service. Those POAs
      // will remain in the holding state until the CORBA transaction service
      // MBean gets deployed.
      //
      // The early creation of OTS POAs serve the purpose of avoiding spurious
      // rollbacks that could happen between the ORB activation and the CORBA
      // transaction service activation. Such a rollback could happen if a 
      // resource sent us a reply_completion request in that time interval. 
      // The resource would get an OBJECT_NOT_EXIST exception and would presume
      // that the transaction had been rolled back. To avoid this problem, we
      // use the fact that CORBA ORBs do not throw OBJECT_NOT_EXIST while the
      // target POA is in the holding state. We also use the fact that JacORB 
      // release 2.2.2 (JBoss patch 3) does not throw OBJECT_NOT_EXIST if the 
      // target POA was not yet created and root POA is in the holding state. 
      // So we create the OTS POAs while the root POA is in the holding state. 
      // Then we activate the root POA, but keep the OTS POAs in the holding 
      // state. The OTS MBean will activate those POAs when it is started.

      Policy[] policies = { // Will create OTS POAs with these policies.
         poa.create_lifespan_policy(
                           LifespanPolicyValue.PERSISTENT),
         poa.create_id_assignment_policy(
                           IdAssignmentPolicyValue.USER_ID),
         poa.create_servant_retention_policy(
                           ServantRetentionPolicyValue.NON_RETAIN),
         poa.create_request_processing_policy(
                           RequestProcessingPolicyValue.USE_DEFAULT_SERVANT),
         poa.create_id_uniqueness_policy(
                           IdUniquenessPolicyValue.MULTIPLE_ID),
      };
      
      // Create a POA for OTS objects that are not Resource instances
      otsPoa = poa.create_POA("OTS", null, policies);
      
      // Create another POA for OTS Resources, so that they can have their own
      // default servant. (The name clash between Terminator::rollback and 
      // Resource::rollback disallows us to use a single default servant for 
      // all OTS objects.)
      otsResourcePoa = 
         poa.create_POA("OTSResources", otsPoa.the_POAManager(), policies);

      // OTS POAs already exist, we can activate the root POA.
      poa.the_POAManager().activate();

      // 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 getOTSContextPropagationEnabled()
   {
      return CorbaORBService.otsContextPropagationEnabledFlag;
   }

   public void setOTSContextPropagationEnabled(boolean value)
   {
      CorbaORBService.otsContextPropagationEnabledFlag = value;
   }

   public boolean getSunJDK14IsLocalBugFix()
   {
      return false;
   }

   public void setSunJDK14IsLocalBugFix(boolean sunJDK14IsLocalBugFix)
   {
      getLog().warn("Not setting SunJDK14IsLocalBugFix since JDK1.4 is no longer supported.");
   }

   // 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);
   }

}

