/*
 * 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.
 */
package org.jboss.iiop;

import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.spi.ObjectFactory;

import org.apache.avalon.framework.configuration.Configuration;
import org.jboss.logging.Logger;
import org.jboss.system.ServiceMBeanSupport;
import org.omg.CORBA.ORB;
import org.omg.CORBA.Policy;
import org.omg.CosNaming.Binding;
import org.omg.CosNaming.BindingHolder;
import org.omg.CosNaming.BindingIteratorHolder;
import org.omg.CosNaming.BindingListHolder;
import org.omg.CosNaming.BindingType;
import org.omg.CosNaming.NameComponent;
import org.omg.CosNaming.NamingContext;
import org.omg.CosNaming.NamingContextExt;
import org.omg.CosNaming.NamingContextHelper;
import org.omg.CosNaming.NamingContextExtHelper;
import org.omg.PortableServer.IdAssignmentPolicyValue;
import org.omg.PortableServer.LifespanPolicyValue;
import org.omg.PortableServer.POA;

/**
 * This is a JMX service that provides the default CORBA naming service
 * for JBoss to use.
 *      
 * @author  <a href="mailto:reverbel@ime.usp.br">Francisco Reverbel</a>
 * @version $Revision: 1.2.2.3 $
 */
public class CorbaNamingService
   extends ServiceMBeanSupport
   implements CorbaNamingServiceMBean, ObjectFactory
{
   // Constants -----------------------------------------------------
   public static String NAMING_NAME = "JBossCorbaNaming";
    
   // Attributes ----------------------------------------------------

   /** The POA used by the CORBA naming service. */
   private POA namingPOA;

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

   /** Root naming context (returned by <code>getObjectInstance()</code>). */
   private static NamingContextExt namingService;

   /** List the CORBA naming service contents.
    * 
    * @return
    */ 
   public String list()
   {
      StringBuffer buf = new StringBuffer();
      rlist(namingService, new NameComponent[0], buf);
      return buf.toString();
   }

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

   protected void startService()
      throws Exception
   {
      Context ctx;
      ORB orb;
      POA rootPOA;

      try {
         ctx = new InitialContext();
      }
      catch (NamingException e) {
         throw new RuntimeException("Cannot get intial JNDI context: " + e);
      }
      try {
         orb = (ORB)ctx.lookup("java:/" + CorbaORBService.ORB_NAME);
      } 
      catch (NamingException e) {
         throw new RuntimeException("Cannot lookup java:/" 
                                    + CorbaORBService.ORB_NAME + ": " + e);
      }
      try {
         rootPOA = (POA)ctx.lookup("java:/" + CorbaORBService.POA_NAME);
      } 
      catch (NamingException e) {
         throw new RuntimeException("Cannot lookup java:/" 
                                    + CorbaORBService.POA_NAME + ": " + e);
      }

      // Create the naming server POA as a child of the root POA
      Policy[] policies = new Policy[2];
      policies[0] = 
         rootPOA.create_id_assignment_policy(IdAssignmentPolicyValue.USER_ID);
      policies[1] = 
         rootPOA.create_lifespan_policy(LifespanPolicyValue.PERSISTENT);
      namingPOA = rootPOA.create_POA("Naming", null, policies);
      namingPOA.the_POAManager().activate();

      // Create the naming service
      org.jacorb.naming.NamingContextImpl.init(orb, rootPOA);
      NamingContextImpl ns = new NamingContextImpl(namingPOA);
      Configuration config = ((org.jacorb.orb.ORB)orb).getConfiguration();
      ns.configure(config); // configure the name service using the ORB config
      byte[] rootContextId = "root".getBytes();
      namingPOA.activate_object_with_id(rootContextId, ns);
      namingService = NamingContextExtHelper.narrow(
                  namingPOA.create_reference_with_id(rootContextId, 
                                "IDL:omg.org/CosNaming/NamingContextExt:1.0"));
      bind(NAMING_NAME, "org.omg.CosNaming.NamingContextExt");
      getLog().info("Naming: ["+orb.object_to_string(namingService)+"]");
   }
    
   protected void stopService()
   {
      // Unbind from JNDI
      try {
         unbind(NAMING_NAME);
      } catch (Exception e) {
         log.error("Exception while stopping CORBA naming service", e);
      }

      // Destroy naming POA
      try {
         namingPOA.destroy(false, false);
      } catch (Exception e) {
         log.error("Exception while stopping CORBA naming service", e);
      }
   }
    
   // 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 (NAMING_NAME.equals(s))
         return namingService;
      else
         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);
   }

   private static void rlist(NamingContext ctx, NameComponent[] base,
      StringBuffer buf)
   {
      BindingListHolder listHolder = new BindingListHolder(new Binding[0]);
      BindingIteratorHolder iterHolder = new BindingIteratorHolder();
      ctx.list(0, listHolder, iterHolder);
      BindingHolder bindingHolder = new BindingHolder();

      if (iterHolder.value == null )
         return;

      NameComponent[] name = new NameComponent[base.length + 1];
      for (int i = 0; i < base.length; i++)
         name[i] = base[i];

      while (iterHolder.value.next_one(bindingHolder))
      {
         Binding binding = bindingHolder.value;
         name[name.length - 1] = binding.binding_name[0];
         try
         {
            String stringName = namingService.to_string(name);
            buf.append(stringName);
         }
         catch(Exception e)
         {
            buf.append(e.getMessage());
         }

         if (binding.binding_type.value() == BindingType._ncontext)
         {
            // this entry is for a subcontext
            // add trailing '/' just to distinguish
            // a subcontext from a regular object
            buf.append('/');
            buf.append('\n');

            // recursively list the subcontext contents
            try
            {
               NamingContext subCtx =
                  NamingContextHelper.narrow(ctx.resolve(binding.binding_name));
               rlist(subCtx, name, buf);
            }
            catch(Exception e)
            {
               buf.append(e.getMessage());
            }
         }
         else
         {
            buf.append('\n');
         }
      }
   }

   // Static inner class --------------------------------------------

   /**
    * This subclass of <code>org.jacorb.naming.NamingContextImpl</code>
    * overrides the method <code>new_context()</code>, because its
    * implementation in <code>org.jacorb.naming.NamingContextImpl</code>
    * is not suitable for our in-VM naming server. The superclass 
    * implementation of <code>new_context()</code> assumes that naming context
    * states are persistently stored and requires a servant activator that
    * reads context states from persistent storage.
    */
   static class NamingContextImpl
      extends org.jacorb.naming.NamingContextImpl
   {
      private POA poa;
      private int childCount = 0;
      private static final Logger logger = 
                              Logger.getLogger(NamingContextImpl.class);

      NamingContextImpl(POA poa)
      {
         this.poa = poa;
      }

      public NamingContext new_context() 
      {
         try {
            NamingContextImpl newContextImpl = new NamingContextImpl(poa);
            byte[] oid = (new String(poa.servant_to_id(this)) +  
                          "/ctx" + (++childCount)).getBytes();
            poa.activate_object_with_id(oid, newContextImpl);
            return NamingContextExtHelper.narrow(
                        poa.create_reference_with_id(oid, 
                                "IDL:omg.org/CosNaming/NamingContextExt:1.0"));
         }
         catch (Exception e) {
            logger.error("Cannot create CORBA naming context", e);
            return null;
         }
      }
   }

}
