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

  $Archive: /njcl_v2/src/com/novell/service/nds/naming/net/NetPartitionDirContext.java $
  $Revision: 11 $
  $Modtime: 11/22/02 3:05p $
 
  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
 * Represents the second level of (partition Context) objects in the
 * Partition name sapce.
 */
public class NetPartitionDirContext
   extends AtomicDirContext
   implements NdsDataAccessor, PartitionDirContextWrappable
{
   protected NetEnvironment net;
   private static NdsPartitionNameParser parser =
                  new NdsPartitionNameParser();
   private String thisPartnName;
   private PartitionFunctions pf;

   /**
    * @param name    This Partition's name
    * @param env     Net environment (from nds namespace)
    */
   public NetPartitionDirContext (
         Name name,
         NetEnvironment env)
      throws NamingException
   {
      // Creat an NDS session through the NetEnvironment object
      net = (NetEnvironment) env.clone ();

      // Check whether the passed name is a valid one
      pf = new PartitionFunctions (env);

      // Save its name
      thisPartnName = name.toString ();

   } /* NetPartitionDirContext () */


   /**
   * @param name    This Partition's name
   * @param env     Net environment (from nds namespace)
   */
   public NetPartitionDirContext(
            String name,
            NetEnvironment env)
        throws NamingException
   {
      // Creat an NDS session through the NetEnvironment object
      net = (NetEnvironment)env.clone();

      // Check whether the passed name is a valid one
      pf = new PartitionFunctions(env);
      if(!pf.IsPartitionRoot(name))
         throw new NameNotFoundException();

      // Save its name
      thisPartnName = name;
   }


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

   /** @internal
    *
    */
   public String getNameInNamespace ()
      throws NamingException
   {
      return (thisPartnName);
   }

   /**
   * 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() > 1)
          throw cont.fillInException(new InvalidNameException());
      else
         return new HeadTail(name, null);
   }

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

   /**
   * @param name    name to lookup
   * @param cont    continuation object
   */
   protected Object a_lookup(
         String name,
         Continuation cont)
      throws NamingException
   {
      if(isEmpty(name))
      {
         cont.setSuccess();
         return this;
      }

      Object obj = null;

      try
      {
         // Create a replica object and return it.
         obj = new NetReplicaDirContext(name, thisPartnName, net);
         cont.setSuccess();
      }
      catch(NamingException e)
      {
         cont.setError(this, name);
         throw cont.fillInException(e);
      }
      return obj;
   }

   /**
   * @param name    name to lookup
   * @param cont    continuation object
   */
   protected Object a_lookupLink(
         String name,
         Continuation cont)
      throws NamingException
   {
      return a_lookup(name, cont);
   }

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

      try
      {
          obj = new NetReplicaNameClassEnumerator(net, thisPartnName);
          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;

      try
      {
          obj = new NetReplicaBindingEnumerator(net, thisPartnName);
          cont.setSuccess();
      }
      catch(NamingException e)
      {
         cont.setError(this, "");
         throw cont.fillInException(e);
      }
      return obj;
   }

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

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

   protected void a_unbind(
         String name,
         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_destroySubcontext(
         String name,
         Continuation cont)
      throws NamingException
   {

      // Do Remove Replica here
      try
      {
         pf.RemoveReplica(name, thisPartnName);
         cont.setSuccess();

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

   protected Context a_createSubcontext(
         String name,
         Continuation cont)
      throws NamingException
   {
      // Should not support this - we have to have replica type attribute
      return (Context)notSupported(name, cont);
   }

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


   /**
    *
    *
   public Properties addToEnvironment (
         Properties additions)
      throws NamingException
   {
      return (net.addToEnvironment (additions));

   } * addToEnvironment () */

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

   } /* addToEnvironment () */

   /**
    *
    *
   public Properties removeFromEnvironment (
         Properties deletions)
      throws NamingException
   {
      return (net.removeFromEnvironment (deletions));

   } * removeFromEnvironment () */

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

   } /* removeFromEnvironment () */

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

   } /* getEnvironment () */


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


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

   /**
   * ----------------AtomicDirContext toolkits's methods -------------
   */


   /**
   * No schema schema's, not supported
   */
   protected DirContext a_getSchema (
         Continuation cont)
      throws NamingException
   {
      cont.setError (this, "");
      throw cont.fillInException (new OperationNotSupportedException ());
   }

   /**
   * No schema schema's, not supported
   */
   protected DirContext a_getSchemaClassDefinition (
         Continuation cont)
      throws NamingException
   {
      cont.setError (this, "");
      throw cont.fillInException (new OperationNotSupportedException ());
   }


   /**
   *
   *
   */
   protected Attributes a_getAttributes (
         String name,
         String[] attrIDs,
         Continuation cont)
      throws NamingException
   {
      NetPartitionAttributes ret = null;

      if (! isEmpty (name))
      {
         resolveNext (name, cont);
         return null;
      }
      try
      {
         ret = new NetPartitionAttributes(thisPartnName, net, attrIDs);
         cont.setSuccess();
      }
      catch(NamingException ee)
      {
         cont.setError (this, name);
         throw cont.fillInException(ee);
      }
      return ret;
   }

   /**
    *
    */
   protected void a_modifyAttributes (
         String name,
         int mod_op,
         Attributes attrs,
         Continuation cont)
      throws NamingException
   {
      if (isEmpty (name))
      {
         notSupported(name, cont);
      }
      else
      {
         resolveNext (name, cont);
      }
   }

   /**
    *
    */
   protected void a_modifyAttributes (
         String name,
         ModificationItem[] mods,
         Continuation cont)
      throws NamingException
   {
      if (isEmpty (name))
      {
        notSupported(name, cont);
      }
      else
      {
         resolveNext (name, cont);
      }
   }

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

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

   /**
   *
   *
   */
   protected DirContext a_createSubcontext (
         String name,
         Attributes attrs,
         Continuation cont)
      throws NamingException
   {

      // Do add replica here
      NetReplicaDirContext obj = null;

      // Issue an AddReplica call to bind the object in the namespace
      try
      {
         int type;
         // Pass the replicaType attribute to this one
         // First extract the replica type attribute from attr
         Attribute replicaType = attrs.get(
                                    NdsPartitionStrings.REPLICA_ATTRID_TYPE);
         if(replicaType == null)
         {
            cont.setError(this, name);
            throw cont.fillInException(new InvalidAttributesException (NdsPartitionStrings.REPLICA_ATTRID_TYPE));
         }
         Enumeration enum = replicaType.getAll();
         // Replica type is a single valued attribute
         // So, pick the first one
         if(enum.hasMoreElements())
         {
            type = ((NdsInteger)enum.nextElement()).intValue();
         }
         else
         {
            // throw exception
            cont.setError(this, name);
            throw cont.fillInException(new InvalidAttributeValueException());
         }
         pf.AddReplica(name, thisPartnName, type);
         obj = new NetReplicaDirContext(name, thisPartnName, net);
         cont.setSuccess();
      }
      catch(NamingException e)
      {
         cont.setError(this, name);
         throw cont.fillInException(e);
      }
      return (DirContext)obj;

   }


   /**
   *
   */
   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 () */


   /*
   *----------------------Utility Methods -----------------------------
   */

    /**
    * This is called by methods that need to perform name resolution.
    * The name is looked up and the Continuation object is told to continue.
    */
   private void resolveNext (
         String name,
         Continuation cont)
      throws NamingException
   {
      Object obj = a_lookup (name, cont);
      if (null != obj)
      {
         cont.setContinue (obj, name, this, "");
      }
   }


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

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


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


   // ******************** NetPartitionDirContext 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 NetPartitionDirContext (thisPartnName, net));
/*
      return (factory.getContextInstance (
                           name.toString (),
                           baseClass,
                           environment));
*/
   }

} /* NetPartitionDirContext */
