/* **************************************************************************

  $Archive: /njcl_v2rmi/src/com/novell/service/nds/naming/net/NetPartitionRootContext.java $
  $Revision: 13 $
  $Modtime: 11/15/00 3:43p $
 
  Copyright (c) 1998 Novell, Inc.  All Rights Reserved.

  THIS WORK IS  SUBJECT  TO  U.S.  AND  INTERNATIONAL  COPYRIGHT  LAWS  AND
  TREATIES.   NO  PART  OF  THIS  WORK MAY BE  USED,  PRACTICED,  PERFORMED
  COPIED, DISTRIBUTED, REVISED, MODIFIED, TRANSLATED,  ABRIDGED, CONDENSED,
  EXPANDED,  COLLECTED,  COMPILED,  LINKED,  RECAST, TRANSFORMED OR ADAPTED
  WITHOUT THE PRIOR WRITTEN CONSENT OF NOVELL, INC. ANY USE OR EXPLOITATION
  OF THIS WORK WITHOUT AUTHORIZATION COULD SUBJECT THE PERPETRATOR TO
  CRIMINAL AND CIVIL LIABILITY.

****************************************************************************/

package com.novell.service.nds.naming.net;


import java.util.*;

import javax.naming.*;
import javax.naming.directory.*;

import com.sun.jndi.toolkit.ctx.*;

import com.novell.service.jncp.NSIException;

import com.novell.service.nds.*;
import com.novell.service.nds.naming.*;
import com.novell.service.nds.ldap.*;

import com.novell.service.rfc1960.Rfc1960Parser;
import com.novell.service.rfc1960.SearchStringComponent;

import com.novell.utility.naming.Environment;
import com.novell.utility.naming.directory.SearchEnumerator;
import com.novell.utility.naming.directory.SearchFilterFactory;


/** @internal
 * Root of the Partition name space.
 */
public class NetPartitionRootContext
   extends AtomicDirContext
   implements NdsPartition, NdsDataAccessor, PartitionDirContextWrappable
{
   private static NdsPartitionNameParser parser =
            new NdsPartitionNameParser();
   protected NetEnvironment netEnv;
   private static Hashtable partnOpns = new Hashtable();
   private PartitionFunctions pf;

   /**
    *
    *
   public NetPartitionRootContext ()
      throws NamingException
   {

   } *  */


   /**
    * @param p   Properties
    * @exception NamingException
    *
   public NetPartitionRootContext(
            Hashtable p)
        throws NamingException
   {
      // Create an NDS session through the NetEnvironment object
      netEnv = new NetEnvironment (p);
      pf = new PartitionFunctions (netEnv);

   } *  */

   /**
    * Called by getPartitionTreeRoot of the Partitionable interface
    */
   public NetPartitionRootContext (
         Name objectName,
         NetEnvironment n_env)
      throws NamingException
   {
      this.netEnv = (NetEnvironment) n_env.clone ();
      pf = new PartitionFunctions (netEnv);

   }


   // ******************** Context Interface ********************

   /** @internal
    *
    */
   public String getNameInNamespace ()
      throws NamingException
   {
      return ("[Root]");
   }

   /**
    *
    */
   public Object addToEnvironment (
         String propName,
         Object propVal)
      throws NamingException
   {
      return (netEnv.addToEnvironment (propName, propVal));

   } /* addToEnvironment () */

   /**
    *
    */
   public Object removeFromEnvironment (
         String propName)
      throws NamingException
   {
      return (netEnv.removeFromEnvironment (propName));

   } /* removeFromEnvironment () */

   /**
    *
    */
   public Hashtable getEnvironment ()
      throws NamingException
   {
      return (netEnv.getEnvironment ());

   } /* getEnvironment () */


   public void close ()
      throws NamingException
   {
      // ... no close necessary
   }


   // ******************** ComponentDirContext overrides ********************

   /**
    * Place up to 2 components in the head, rest of name in the tail
    * @param name                 name to parse
    * @param cont                 continuation object
    */
   protected HeadTail p_parseComponent (
         Name name,
         Continuation cont)
      throws NamingException
   {
       // If the name has more than 1 component (replica name) at this level,
       //throw exception
      if (name.size() > 2)
      {
          throw cont.fillInException(new InvalidNameException());
      }
      else if(name.size() == 2)
      {
         return new HeadTail(name.getPrefix(1), name.getSuffix(1));
      }
      else
      {
         return new HeadTail(name, null);
      }

   } /* p_parseComponent () */


   // ******************** AtomicDirContext overrides ********************

   /**
    * place first component in the head, rest in the tail
    * @param name                 name to parse
    * @param cont                 continuation object
    */
   protected StringHeadTail c_parseComponent (
         String name,
         Continuation cont)
      throws NamingException
   {
      try
      {
         CompoundName n = (CompoundName)parser.parse(name);
         if(n.isEmpty() || n.size() == 1)
         {
            return new StringHeadTail(name, null);
         }
         else
         {
            throw cont.fillInException(new InvalidNameException());
         }
      }
      catch (NamingException e)
      {
         throw cont.fillInException(e);
      }

   } /* c_parseComponent () */


   // ******************** AtomicContext Interface ********************

   /**
    *
    */
   protected void a_bind (
         String name,
         Object obj,
         Continuation cont)
      throws NamingException
   {
      notSupported (name, cont);
   }

   /**
    *
    */
   protected Context a_createSubcontext (
         String name,
         Continuation cont)
      throws NamingException
   {
      // Do split partition here

      // Verify that the name is not already bound
      if (pf.IsPartitionRoot (name))
      {
         cont.setError (this, name);
         throw cont.fillInException (new NameAlreadyBoundException ());
      }

      // Then create an instance of PartitionContext
      // And bind it by issuing a SplitPartition call
      try
      {
         // First, add the name to the Pending Operations List
         PendingOpObject p = new PendingOpObject(
                                    name,
                                    netEnv.getService (),
                                    NdsPartitionStrings.SPLIT);
         partnOpns.put(p, name);

         pf.SplitPartition(name, p.getUniqueId());

         //Remove the name from the List
         if(partnOpns.containsKey(p))
         {
            partnOpns.remove(p);
         }
         else
         {
            // Print out stuff for debugging
         }

         Context obj = new NetPartitionDirContext (name, netEnv);
//         NetPartitionDirContextFactory factory =
//               new NetPartitionDirContextFactory ();

//         Context obj = factory.createNdsPartitionContext (name, netEnv);

         cont.setSuccess();
         return obj;
      }
      catch(NamingException ee)
      {
         cont.setError(this, name);
         throw cont.fillInException(ee);
      }

   } /* a_createSubcontext () */

   /**
    *
    */
   protected void a_destroySubcontext (
         String name,
         Continuation cont)
      throws NamingException
   {
      // Do JoinPartition here
      // Verify that the name is indeed a partition name
      if(!pf.IsPartitionRoot(name))
      {
         cont.setError(this, name);
         throw cont.fillInException(new NameNotFoundException());
      }
      try
      {
         // First, add the name to the Pending Opertions List
         PendingOpObject p = new PendingOpObject (
                    name,
                    netEnv.getService (),
                    NdsPartitionStrings.JOIN);
         partnOpns.put(p, name);

         pf.JoinPartition(name, p.getUniqueId());

         //Remove the name from the List
         if(partnOpns.containsKey(p))
         {
            partnOpns.remove(p);
         }
         else
         {
            // Print stuff for debugging
         }
         cont.setSuccess();

      }
      catch(NamingException ee)
      {
         cont.setError(this, name);
         throw cont.fillInException(ee);
      }
   }

   /**
    *
    */
   protected NameParser a_getNameParser (
         Continuation cont)
      throws NamingException
   {
      return parser;
   }

   /**
    *
    */
   protected NamingEnumeration a_list(
         Continuation cont)
      throws NamingException
   {
      NamingEnumeration obj = null;

      try
      {
          obj = new NetPartitionNameClassEnumerator(
                            netEnv.getService (),
                            "[Root]");
          cont.setSuccess();
      }
      catch(NamingException e)
      {
         cont.setError(this, "");
         throw cont.fillInException(e);
      }
      return obj;

   }

   /**
    *
    */
   protected NamingEnumeration a_listBindings(
         Continuation cont)
      throws NamingException
   {
      NamingEnumeration obj = null;
      obj = new NetPartitionBindingEnumerator(netEnv, "[Root]");

      return obj;
   }

   /**
    *
    */
   protected Object a_lookup(
         String name,
         Continuation cont)
      throws NamingException
   {
      Object obj = null;

      if(isEmpty(name))
      {
         cont.setSuccess();
         return this;
      }
      // Verify whether the passed name is indeed a partition root dn
      // If yes, create a Partition object of the given name
      if(pf.IsAPartition(name))
      {
         CompoundName n = (CompoundName)parser.parse(name);
         obj = new NetPartitionDirContext(n, netEnv);
//        NetPartitionDirContextFactory factory =
//              new NetPartitionDirContextFactory ();

//        obj = factory.createNdsPartitionContext (name, netEnv);

         cont.setSuccess();
      }
      else
      {
         cont.setError(this, name);
         throw cont.fillInException(new NameNotFoundException());
      }
      return obj;

   } /*  */

   protected Object a_lookupLink(
         String name,
         Continuation cont)
      throws NamingException
   {
      return a_lookup(name, cont);
   }

   protected void a_rebind(
         String name,
         Object obj,
         Continuation cont)
      throws NamingException
   {
      notSupported(name, cont);
   }

   protected void a_rename(
         String oldname,
         Name newname,
         Continuation cont)
      throws NamingException
   {
        notSupported(oldname, cont);
   }

   protected void a_unbind(
         String name,
         Continuation cont)
      throws NamingException
   {
      notSupported(name, cont);
   }

      // ********** nns operations **********


   // ******************** ComponentDirContext Interface ********************

   /**
    *
    */
   protected void a_bind (
         String name,
         Object obj,
         Attributes attrs,
         Continuation cont)
      throws NamingException
   {
      NamingException e = new OperationNotSupportedException ();

      cont.setError (this, name);
      throw (cont.fillInException (e));

   } /* c_bind () */

   /**
    *
    */
   protected DirContext a_createSubcontext (
         String name,
         Attributes attrs,
         Continuation cont)
      throws NamingException
   {
      NamingException e = new OperationNotSupportedException ();

      cont.setError (this, name);
      throw (cont.fillInException (e));

   } /* c_createSubcontext () */

   /**
    *
    */
   protected Attributes a_getAttributes (
         String name,
         String[] attributeNames,
         Continuation cont)
      throws NamingException
   {
      NamingException e = new OperationNotSupportedException ();

      cont.setError (this, name);
      throw (cont.fillInException (e));

   } /* c_getAttributes () */

   /**
    *
    */
   protected DirContext a_getSchema (
         Continuation cont)
      throws NamingException
   {
      NamingException e = new OperationNotSupportedException ();

      cont.setError (this, "");
      throw (cont.fillInException (e));

   } /*  */

   /**
    *
    */
   protected DirContext a_getSchemaClassDefinition (
         Continuation cont)
      throws NamingException
   {
      NamingException e = new OperationNotSupportedException ();

      cont.setError (this, "");
      throw (cont.fillInException (e));

   } /*  */

   /**
    *
    */
   protected void a_modifyAttributes (
         String name,
         int mod_op,
         Attributes attrs,
         Continuation cont)
      throws NamingException
   {
      NamingException e = new OperationNotSupportedException ();

      cont.setError (this, name);
      throw (cont.fillInException (e));

   } /*  */

   /**
    *
    */
   protected void a_modifyAttributes (
         String name,
         ModificationItem[] mods,
         Continuation cont)
      throws NamingException
   {
      NamingException e = new OperationNotSupportedException ();

      cont.setError (this, name);
      throw (cont.fillInException (e));

   } /*  */

   /**
    *
    */
   protected void a_rebind (
         String name,
         Object obj,
         Attributes attrs,
         Continuation cont)
      throws NamingException
   {
      NamingException e = new OperationNotSupportedException ();

      cont.setError (this, name);
      throw (cont.fillInException (e));

   } /*  */

   /**
    *
    */
   protected NamingEnumeration a_search (
         Attributes matchingAttrs,
         String[] returnAttrs,
         Continuation cont)
      throws NamingException
   {
      if (matchingAttrs == null || matchingAttrs.size () == 0)
      {
         // Return this object and all subcontexts.
         Vector searchResults = new Vector ();
/*
         // Add this object
         searchResults.addElement (new SearchResult (
                                          atomicName,
                                          getInstance (),
                                          a_getAttributes (
                                                "",
                                                returnAttrs,
                                                cont)));
*/
         // Add all bindings
         NamingEnumeration be = listBindings ("");
         while (be.hasMoreElements ())
         {
            // Get next binding
            Binding binding = (Binding)be.next ();
            DirContext dsCtx = (DirContext)binding.getObject ();

            // Get this binding's attrs
            Attributes attrs = dsCtx.getAttributes ("", returnAttrs);

            // Add this binding
            searchResults.addElement (new SearchResult (
                                          binding.getName (),
                                          binding.getClassName(),
                                          null, //dsCtx,
                                          attrs,
                                          true));
         }

         cont.setSuccess ();
         return new SearchEnumerator (searchResults);
      }
      else
      {
         // Valid attribute set.  Convert to filter and use filter to search
         SearchFilterFactory sf = new SearchFilterFactory (matchingAttrs);
         SearchControls constraints = new SearchControls ();
         constraints.setReturningAttributes (returnAttrs);
         return a_search (
            "",
            sf.getExpression (),
            sf.getArgs (),
            constraints,
            cont);
      }

   } /* a_search () */

   /**
    *
    */
   protected NamingEnumeration a_search (
         String name,
         String filter,
         SearchControls cons,
         Continuation cont)
      throws NamingException
   {
      SearchControls c = (cons == null ? new SearchControls() : cons);

      return a_search (name, filter, null, cons, cont);
   }

   /**
    *
    */
   protected NamingEnumeration a_search (
         String name,
         String filterExpr,
         Object [] filterArgs,
         SearchControls cons,
         Continuation cont)
      throws NamingException
   {
      if (false == isEmpty (name))
      {
         Object obj = a_lookup (name, cont);
         if (null != obj)
         {
            cont.setContinue (obj, name, this, "");
         }
         return null;
      }

      if (cons == null)
      {
         cons = new SearchControls ();
      }

      int timeLim = cons.getTimeLimit ();
      long timeEnd = 0;
      long timeLeft = 0;
      boolean timed = false;
      long countLim = cons.getCountLimit ();
      boolean sized = countLim == 0 ? false : true;
      int scope = cons.getSearchScope ();

      if (timeLim != 0)
      {
         timeEnd = System.currentTimeMillis () + timeLim;
         timed = true;
      }

      // Parse the filter expression.
      Rfc1960Parser ssp;
      try
      {
        ssp = new Rfc1960Parser (filterExpr);
      }
      catch (IllegalArgumentException e)
      {
         cont.setError (this, name);
         InvalidSearchFilterException ne =
               new InvalidSearchFilterException ();
         ne.setRootCause (e);
         throw cont.fillInException (ne);
      }

      // Search this context
      Vector searchResults = new Vector ();
      try
      {
         SearchResult sr = searchObject (
                              ssp,
                              cons,
                              filterArgs);
         if (sr != null)
         {
            searchResults.addElement (sr);
         }

         // Bail out if OBJECT_SCOPE
         if (scope == SearchControls.OBJECT_SCOPE)
         {
            return new SearchEnumerator (searchResults);
         }
      }
      catch (NamingException ne)
      {
         cont.setError (this, name);
         throw cont.fillInException (ne);
      }

      // Construct new constraints based on those passed in.
      // Change scope to OBJECT_SCOPE if ONELEVEL_SCOPE is passed in.
      SearchControls constraints = new SearchControls (
                                 scope == SearchControls.ONELEVEL_SCOPE ?
                                          SearchControls.OBJECT_SCOPE :
                                          scope,
                                 countLim,
                                 timeLim,
                                 cons.getReturningAttributes (),
                                 cons.getReturningObjFlag (),
                                 cons.getDerefLinkFlag ());

      // Search bindings.
      NamingEnumeration be = listBindings ("");
      while (be.hasMoreElements ())
      {
         // Check and update constraints.
         if (timed)
         {
            if ((timeLeft = (timeEnd - System.currentTimeMillis ())) <= 0)
            {
               // Time limit has been reached.  Bail out.
               break;
            }
            constraints.setTimeLimit ((int)timeLeft);
         }
         if (sized)
         {
            if (countLim <= 0)
            {
               // Count limit has been reached.  Bail out.
               break;
            }
            constraints.setCountLimit (countLim);
         }

         // Get next binding
         Binding binding = (Binding) be.next ();
         Object bindObj = binding.getObject();
         if (!(bindObj instanceof DirContext))
            continue;
            
         String bindingName = binding.getName();
         DirContext dsCtx = (DirContext)bindObj;

         NamingEnumeration se;
         try
         {
            // Search this binding and add all SearchResults to vector
            se = dsCtx.search ("", filterExpr, filterArgs, constraints);
         }
         catch (NamingException ne)
         {
            cont.setError (this, name);
            throw cont.fillInException (ne);
         }

         while (se.hasMoreElements ())
         {
            SearchResult sr = (SearchResult) se.next ();

            String newName = sr.getName();
            if (newName.length() == 0)
               newName = bindingName;
            else
               newName = dsCtx.composeName(
                                       newName,
                                       bindingName);

            sr.setName(newName);
/*
            // Create new SearchResult with modified name
            // (prepend binding name to returned name)
            SearchResult newSr = new SearchResult (
                                 newName,
                                 sr.getObject (),
                                 sr.getAttributes ());
*/
            searchResults.addElement (sr);

            // Reduce count limit.
            if (sized)
            {
               countLim--;
            }
         }
      }

      cont.setSuccess ();
      return (new SearchEnumerator (searchResults));

   } /* a_search () */

      // ********** nns operations **********

   // ******************** Referenceable Interface ********************

   /**
    * Retrieves the Reference of this object.
    *
    * @return the Reference of this object.
    */
   public Reference getReference ()
      throws NamingException
   {
      return (null);

   } /* getReference () */


   // ******************** NdsPartition Interface ********************

   public void abortOperations (
            Name name)
        throws NdsPartitionOperationException
   {
       abortOperations(name.toString());
   }

   public void abortOperations(
            String name)
        throws NdsPartitionOperationException
   {
      // Issue the native call to abort the operation.
      pf.AbortOperations(name);

      // Also, remove the name on which abort is called from
      // the pending operations list
      if(partnOpns.containsKey(name))
      {
          partnOpns.remove(name);
      }
      else
      {
          // Print out stuff for debugging.
      }
   }

   /**
    *  Common code for setting up and throwing OperationNotSupported Exception.
    */
   private Object notSupported(
         String n,
         Continuation c)
      throws NamingException
   {
      c.setError(this, n);
      throw c.fillInException(new OperationNotSupportedException());
   }

   /**
    *  Code for implementing Abortable
    */
   public static Hashtable getPendingPartitionOperations ()
   {
      return partnOpns;
   }


   // ******************** NdsDataAccessor Interface ********************

   /**
    *
    */
   public String getBaseClass ()
      throws NSIException
   {
      return (Schema.PARTITION_ROOT);
   }


   /**
    *
    */
   public Environment getEnvironmentInstance ()
      throws NamingException
   {
      return (netEnv);
   }


   // ******************** NetPartitionRootContext class ********************

   /**
    * Helper method for a_search
    *
    * <p>This method examines the current object
    * </p>
    *
    * @param ssp                 The search string parser to use for
    *                            obtaining the needed attribute id's, compare
    *                            operations and operands.
    *
    * @param returnAttrs         The attributes that should be returned
    *                            if this context matches.
    * @param filterArgs          Array that contains the
    *                            attribute values to compare on replacement.
    * @return                    SearchResult on match, null otherwise.
    * @exception                 NamingException
    *
    */
   private SearchResult searchObject (
         Rfc1960Parser ssp,
         SearchControls cons,
         Object [] filterArgs)
      throws NamingException
   {
      Attributes attrs;
      SearchStringComponent comp;
      NdsAttributeValue value;
//      SSComponentEnumerator sscenum;
      String[] returnAttrs = cons.getReturningAttributes ();

      try
      {
         // Get all the attributes for this context
         attrs = getAttributes ("");

         // Compare each search string
         while (ssp.hasMoreElements ())
         {
            boolean compared = false;

            // Get next SearchStringComponent from parser
            comp = ssp.next ();

            // Compare componenet to each value in attribute
            Attribute attr = attrs.get (comp.getAttributeId ());
            if (attr != null)
            {
               Enumeration values = attr.getAll ();
               while (values.hasMoreElements ())
               {
                  if ((compared = compareValue (
                              comp,
                              (NdsAttributeValue)values.nextElement (),
                              filterArgs)))
                  {
                     break;
                  }
               }
            }

            // Store result of call to compareValue in parser.
            // This will allow parser to perform early out logic.
            ssp.setCompareResult (comp, compared);
         }

         // Check parser to see if we can bail out yet.
         if (false == ssp.compared ())
         {
            return null;
         }
      }
      catch (InvalidSearchFilterException e)
      {
         // Problem with the search filter and replacement params
         throw e;
      }
      catch (NamingException e)
      {
         // Do nothing. Assume search not found and return
         return null;
      }

      if (null != returnAttrs)
      {
         // Replace full set of attrs with subset asked for
         attrs = getAttributes ("", returnAttrs);
      }

      return new SearchResult (
                  "",
                  cons.getReturningObjFlag () ? getInstance () : null,
                  attrs,
                  true);
   }

   /**
    * Helper method for searchObject.  This method compares the value
    * specified in comp and filter to that in value.
    *
    * @param comp          The component to be compared
    *
    * @param value         The attributes value to compare against.
    *
    * @param filterArgs    An array that contains the
    *                      attribute values to compare.
    *
    * @return              true if the attribute values match.
    *
    */
   private boolean compareValue (
         SearchStringComponent comp,
         NdsAttributeValue value,
         Object [] filterArgs)
      throws NamingException
   {
      boolean compared = false;
      int operationType = comp.getOperationType ();
      NdsAttributeValue operand;

      // See if value supports the operation
      if (false == value.supportsMatchingRules (
            SearchStringComponent.EQUALS == operationType ?
               NdsAttributeValue.equality : (
            SearchStringComponent.APPROXIMATE == operationType ?
               NdsAttributeValue.approximate : (
            SearchStringComponent.GREATER_OR_EQUAL == operationType ?
               NdsAttributeValue.ordering : (
            SearchStringComponent.LESS_OR_EQUAL == operationType ?
               NdsAttributeValue.ordering : (
            SearchStringComponent.SUBSTRING == operationType ?
               NdsAttributeValue.substrings :
               NdsAttributeValue.none))))))
      {
         // Can't perform specified operation
         return false;
      }

      // Get operand as an NdsAttributeValue
      if (comp.operandReplacement ())
      {
         try
         {
            // Extract from array if using replacement param's
            operand = (NdsAttributeValue)
                  filterArgs [comp.getReplacementIndex ()];
         }
         catch (ArrayIndexOutOfBoundsException e)
         {
            NamingException ne = new InvalidSearchFilterException ();
            ne.setRootCause (e);
            throw ne;
         }
         catch (ClassCastException e)
         {
            // Wrong type in filterArgs
            return false;
         }
      }
      else
      {
         try
         {
            // Create from string
            operand = new LdapAttributeValueFactory ().createValue (
                                                value.getNdsSyntaxId (),
                                                comp.getOperand ());
         }
         catch (Exception e)
         {
            // Couldn't create value
            return false;
         }
      }

      try
      {
         // Compare attributes
         switch (operationType)
         {
            case SearchStringComponent.SUBSTRING:
            case SearchStringComponent.EQUALS:
               compared = value.equals (operand);
               break;

            case SearchStringComponent.APPROXIMATE:
               compared = value.approximate (operand);
               break;

            case SearchStringComponent.GREATER_OR_EQUAL:
               compared = 0 <= value.compareTo (operand);
               break;

            case SearchStringComponent.LESS_OR_EQUAL:
               compared = 0 >= value.compareTo (operand);
               break;

            case SearchStringComponent.PRESENT:
               compared = true;
               break;

            default:
               // Anything other than values above is not supported
               // (Should never get here)
               return false;
         }
      }
      catch (Exception e)
      {
         // Couldn't compare values
         return false;
      }

      return compared;
   }

   /**
    * Returns an instance of this object through the factory
    */
   protected Object getInstance ()
      throws NamingException
   {
      return (new NetPartitionRootContext (new CompositeName (), netEnv));
/*
      return (factory.getContextInstance (
                           name.toString (),
                           baseClass,
                           environment));
*/
   }

} /* NetPartitionRootContext */


