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

  $Archive: /njcl_v2/src/com/novell/service/server/ServerDirContext.java $
  $Revision: 17 $
  $Modtime: 1/28/00 12:46p $
 
  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.server;


import com.novell.utility.naming.directory.SearchFilterFactory;
import com.novell.utility.naming.*;
import com.novell.utility.naming.directory.*;
import com.novell.utility.naming.spi.*;
import com.novell.utility.naming.directory.*;
import com.novell.service.rfc1960.*;
import com.novell.service.jncp.*;
import com.novell.service.session.*;
import com.novell.service.session.xplat.*;
import javax.naming.*;
import javax.naming.directory.*;
import javax.naming.spi.*;
import com.sun.jndi.toolkit.ctx.*;
import java.util.*;


/**
*  Represents a NetWare Server. 
*
* <p>The Server has three bindings:
* <ul><li>File System
* <li>NCPExtensions
* <li>Bindery</ul>
*/
public class ServerDirContext
   extends ComponentDirContext
   implements Referenceable, NWServer
{
   private FlatNameParser parser;
   private ServerSchemaContext schema;
   private Hashtable environment;
   private Session session;
   private Hashtable bindings = new Hashtable();
   private Hashtable attributeValues = new Hashtable();
   private String serverName;

   /**
   * @internal
   *
   * Constructs a server context and sets up bindings and attributes.
   *
   * @param p    Contains the name of the NetWare server
   *                             in Environment.SERVICE_HOST
   * @param conn Connection to server
   */
   protected ServerDirContext(
         Hashtable h)
      throws NamingException, SessionException, java.rmi.RemoteException
   {
      session = (Session)h.get(com.novell.utility.naming.Environment.SESSION_OBJECT);
      environment = h; //no need to clone. Factory sends us our own copy

      serverName = session.getDomainName();

      schema = new ServerSchemaContext ("[Root]", true, serverName);

      Hashtable refProps = (Hashtable)environment.clone();
      ReferenceFactory refFact =
         new com.novell.service.bindery.ReferenceFactoryImpl();
      bindings.put(ServerStrings.BINDERY, refFact.createReference(refProps));

      refFact = new com.novell.service.ncpext.ReferenceFactoryImpl();
      bindings.put (ServerStrings.NCPEXTENSIONS, refFact.createReference(refProps));

      refFact = new com.novell.service.file.nw.naming.ReferenceFactoryImpl();
      bindings.put (ServerStrings.FILE, refFact.createReference(refProps));

      // Add attributes allowing for case insensitive compares
      StaticAttributeValue sa;
      sa = new ServerVersionsImpl(session);
      attributeValues.put(sa.getID().toUpperCase() ,sa);
      sa = new ServerDescriptionImpl(session);
      attributeValues.put(sa.getID().toUpperCase() ,sa);
      sa = new ServerCountsImpl(session);
      attributeValues.put(sa.getID().toUpperCase() ,sa);
      sa = new ServerDiagnosticsImpl(session);
      attributeValues.put(sa.getID().toUpperCase() ,sa);
      sa = new ServerLoginStatusImpl(session);
      attributeValues.put(sa.getID().toUpperCase() ,sa);
   }


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

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

  /**
   * @internal
   */
   protected Object c_lookup(
         Name name,
         Continuation cont)
      throws NamingException
   {
      if(name.isEmpty())
      {
         cont.setSuccess();
         return this;
      }

      try
      {
         Object obj = bindings.get(name.toString());
         if(obj instanceof Reference)
            obj = NamingManager.getObjectInstance(obj, null, null, getEnvironment());
         if(null == obj)
            throw new NameNotFoundException();
         cont.setSuccess();
         return obj;
      }
      catch(Exception e)
      {
         cont.setError(this, name);
         NameNotFoundException ne = new NameNotFoundException();
         ne.setRootCause(e);
         throw cont.fillInException(ne);
      }
   }

  /**
   * @internal
   *
   */
   protected Object c_lookupLink(
         Name name,
         Continuation cont)
      throws NamingException
   {
      return c_lookup(name, cont);
   }

  /**
   * @internal
   *
   */
   protected NamingEnumeration c_list(
         Name name,
         Continuation cont)
      throws NamingException
   {
      if(name.isEmpty())
      {
         cont.setSuccess();
         return new ServerNameClassEnumerator(bindings);
      }
      return (NamingEnumeration)resolveNext(name, cont);
   }

  /**
   * @internal
   *
   */
   protected NamingEnumeration c_listBindings(
         Name name,
         Continuation cont)
      throws NamingException
   {
      if (name.isEmpty())
      {
         cont.setSuccess();
         return new ServerBindingEnumerator(bindings, environment);
      }
      return (NamingEnumeration)resolveNext(name, cont);
   }

  /**
   * @internal
   *
   */
   protected void c_bind(
         Name name,
         Object obj,
         Continuation cont)
      throws NamingException
   {
      notSupported(name, cont);
   }

  /**
   * @internal
   *
   */
   protected void c_rebind(
         Name name,
         Object obj,
         Continuation cont)
      throws NamingException
   {
      notSupported(name, cont);
   }

  /**
   * @internal
   *
   */
   protected void c_unbind(
         Name name,
         Continuation cont)
      throws NamingException
   {
      notSupported(name, cont);
   }

  /**
   * @internal
   *
   */
   protected void c_rename(
         Name oldName,
         Name newName,
         Continuation cont)
      throws NamingException
   {
      notSupported(oldName, cont);
   }

  /**
   * @internal
   *
   */
   protected void c_destroySubcontext(
         Name name,
         Continuation cont)
      throws NamingException
   {
      notSupported(name, cont);
   }

  /**
   * @internal
   *
   */
   protected Context c_createSubcontext(
         Name name,
         Continuation cont)
      throws NamingException
   {
      return (Context)notSupported(name, cont);
   }

  /**
   * @internal
   *
   */
   protected NameParser c_getNameParser(
         Name name,
         Continuation cont)
      throws NamingException
   {
      if(name.isEmpty())
      {
         if(null == parser)
            parser = new FlatNameParser("server+" + serverName);

         return parser;
      }
      return (NameParser)resolveNext(name, cont);
   }

  /**
   * @internal
   *
   */
	public Object addToEnvironment(
   	   String name,
   	   Object value)
   	throws NamingException
	{
      return environment.put(name, value);
	}

  /**
   * @internal
   *
   */
	public Object removeFromEnvironment(
   	   String name)
   	throws NamingException
	{
      return environment.remove(name);
	}

  /**
   * @internal
   *
   */
	public Hashtable getEnvironment() throws NamingException
	{
	   return (Hashtable)environment.clone();
	}

  /**
   * @internal
   *
   */
   protected DirContext c_createSubcontext(
         Name name,
         Attributes attrs,
         Continuation cont)
      throws NamingException
   {
      return (DirContext)notSupported(name, cont);
   }

  /**
   * @internal
   *
   */
   protected Attributes c_getAttributes(
         Name name,
         String[] attrIds,
         Continuation cont)
      throws NamingException
   {
      if(name.isEmpty())
      {
         Attributes attrSet = new NAttributes(true);

         if(null == attrIds)
         {
            Enumeration e = attributeValues.elements();
            while (e.hasMoreElements())
            {
               attrSet.put(((StaticAttributeValue)
                     e.nextElement()).buildAttribute());
            }
         }
         else  // This is a subset of the WHOLE attribute set.
         {
            StaticAttributeValue value;
            for (int i = 0; i < attrIds.length; i++)
            {
               if(null != (value = (StaticAttributeValue)
                  attributeValues.get(attrIds[i].toUpperCase())))
               {
                  attrSet.put(value.buildAttribute());
               }
            }
         }
         cont.setSuccess();
         return attrSet;
      }
      return (Attributes)resolveNext(name, cont);
   }

  /**
   * @internal
   *
   */
   protected void c_modifyAttributes(
         Name name,
         int mod_op,
         Attributes attrs,
         Continuation cont)
      throws NamingException
   {
      if(name.isEmpty())
      {
         notSupported(name, cont);
      }
      else
      {
         resolveNext(name, cont);
      }
   }

  /**
   * @internal
   *
   */
   protected void c_modifyAttributes(
         Name name,
         ModificationItem[] mods,
         Continuation cont)
      throws NamingException
   {
      if(name.isEmpty())
      {
         notSupported(name, cont);
      }
      else
      {
         resolveNext(name, cont);
      }
   }


  /**
   * @internal
   *
   */
   protected void c_bind(
         Name name,
         Object obj,
         Attributes attrs,
         Continuation cont)
      throws NamingException
   {
      notSupported(name, cont);
   }

  /**
   * @internal
   *
   */
   protected void c_rebind(
         Name name,
         Object obj,
         Attributes attrs,
         Continuation cont)
      throws NamingException
   {
      notSupported(name, cont);
   }

  /**
   * @internal
   *
   */
   protected NamingEnumeration c_search(
         Name name,
         Attributes matchingAttrs,
         String[] attributesToReturn,
         Continuation cont)
      throws NamingException
   {
      if(name.isEmpty())
      {
         //Default SearchControls behavior is ONELEVEL_SCOPE which does not
         //include yourself. Server has no bindings to objects in its name
         //space so just return empty enumeration
         cont.setSuccess();
         return new SearchEnumerator();
      }
      else
      {
         return (NamingEnumeration)resolveNext(name, cont);
      }
   }

   /**
   * @internal
   *
   * Currently we do not cross name space bounderies in search. Since server
   * has no bindings to objects that are in its own name space we can short
   * circut almost all of the search constraints. If the
   * decision is ever made to cross bounderies in a search just uncomment
   * the commented out code and it should performing a depth first search
   */
   protected NamingEnumeration c_search(
         Name name,
         String filterExpr,
         Object[] filterArgs,
         SearchControls cons,
         Continuation cont)
      throws NamingException
   {
      if(!name.isEmpty())  //if name is not empty, resolve name and return.
      {
         return (NamingEnumeration)resolveNext(name, cont);
      }

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

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

      try
      {
         if(cons==null || cons.getSearchScope()==SearchControls.ONELEVEL_SCOPE)
         {
            cont.setSuccess ();
            return new SearchEnumerator();
         }
         Rfc1960Parser ssp = new Rfc1960Parser (filterExpr);

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

         /*
         if (scope == SearchControls.OBJECT_SCOPE)
            return new SearchEnumerator(searchResults);
         // 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());

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

            // Search this binding and add all SearchResults to vector
            DirContext binding = (DirContext)be.next().getObject();
            NamingEnumeration se =
               binding.search ("", filterExpr, filterArgs, constraints);
            while(se.hasMoreElements())
            {
               searchResults.addElement(se.next());
               if(sized)
                  countLim--;
            }
         }
         */

         cont.setSuccess ();
         return new SearchEnumerator (searchResults);
      }
      catch (IllegalArgumentException e)
      {
         cont.setError (this, name);
         InvalidSearchFilterException ne =
               new InvalidSearchFilterException ();
         ne.setRootCause (e);
         throw cont.fillInException (ne);
      }
      catch (NamingException ne)
      {
         cont.setError (this, name);
         throw cont.fillInException (ne);
      }
   }

  /**
   * @internal
   *
   */
   protected NamingEnumeration c_search(
         Name name,
         String filter,
         SearchControls cons,
         Continuation cont)
      throws NamingException
   {
      if(name.isEmpty())
         return c_search(name, filter, null, cons, cont);
      else
      {
         return (NamingEnumeration)resolveNext(name, cont);
      }
   }

  /**
   * @internal
   *
   */
   protected DirContext c_getSchema(
         Name name,
         Continuation cont)
      throws NamingException
   {
      if(name.isEmpty())
      {
         cont.setSuccess();
         return schema;
      }
      else
         return (DirContext)resolveNext(name, cont);
   }

  /**
   * @internal
   *
   */
   protected DirContext c_getSchemaClassDefinition(
         Name name,
         Continuation cont)
      throws NamingException
   {
      if(name.isEmpty())
      {
         DirContext ctx;

         ctx = (DirContext) schema.lookup (ServerStrings.CLASS_DEFINITION);
         ctx = (DirContext) ctx.lookup (ServerStrings.SERVER);

         Binding[] bindings = { 
            new Binding (
                           ServerStrings.SERVER,
                           ctx.getClass ().getName (),
                           ctx) };

         DirContext root = new StaticSchemaContext ("[Root]", bindings);

         cont.setSuccess();
         return (root);
      }
      else
         return (DirContext)resolveNext(name, cont);
   }

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

   /**
    * @internal
    *
    * Construct a Reference from yourself
    * <p> Uses Environment.SERVICE_HOST from environment
    */
   public Reference getReference()
   {
      try
      {
         return new com.novell.service.server.ReferenceFactoryImpl().
            createReference(environment);
      }
      catch (NamingException e)
      {
         return null;
      }
   }

	/*
   * Common code for resoving a non-empty name
   */
	private Object resolveNext(
	      Name n,
	      Continuation c)
	   throws NamingException
	{
      Object obj = c_lookup(n, c);
      if (obj != null)
         c.setContinue(obj, n, this);
      return null;
   }

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

   /*
    * Helper method for search() to examine the current object
    * @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                 On error
    */
   private SearchResult searchObject(
         Rfc1960Parser ssp,
         SearchControls cons,
         Object [] filterArgs)
      throws NamingException
   {
      SearchStringComponent comp;
      StaticAttributeValue sa;

      try
      {
         while(ssp.hasMoreElements())
         {
            boolean compared = false;
            comp = ssp.next();

            // Case insensitive check
            sa = (StaticAttributeValue)
               attributeValues.get(comp.getAttributeId().toUpperCase());
            if(sa != null)
            {
               if(comp.operandReplacement())
                  comp.setReplacementObject(
                     filterArgs[comp.getReplacementIndex()]);
               compared = sa.compare(comp);
            }
            // Store result of call to compareValue in parser.
            // This will allow parser to perform early out logic.
            ssp.setCompareResult(comp, compared);
         }

         if(!ssp.compared())  //check to see if compared
            return null;
         return new SearchResult(
            "",
            getClass().getName(),
            cons.getReturningObjFlag() ? this : null,
            getAttributes("", cons.getReturningAttributes()));
      }
      catch(ArrayIndexOutOfBoundsException e)
      {
         throw new InvalidSearchFilterException();
      }
   }

   // Implementation for methods in NWServer interface
  /**
   * Method implemented in NWServer interface.
   *
   * @see NWServer
   */
   public void loadNLM(
         String loadCommand)
      throws NSIException
   {
      try
      {
         ((CallsService)session.getService(
            CallsService.KEY)).loadNLM(loadCommand);
      }
      catch(Exception e)
      {
         throw NSIExceptionBuilder.build(e);
      }
   }

  /**
   * Method implemented in NWServer interface.
   *
   * @see NWServer
   */
   public void unloadNLM(
         String name)
      throws NSIException
   {
      try
      {
         ((CallsService)session.getService(
            CallsService.KEY)).unloadNLM(name);
      }
      catch(Exception e)
      {
         throw NSIExceptionBuilder.build(e);
      }
   }

  /**
   * Method implemented in NWServer interface.
   *
   * @see NWServer
   */
   public Calendar getTime()
      throws NSIException
   {
      try
      {
         Object[] byteHolder = new Object[1];
         ((CallsService)session.getService(
            CallsService.KEY)).getFileServerDateAndTime(byteHolder);
         byte[] ba = (byte[])byteHolder[0];
         Calendar cal = Calendar.getInstance();
         int month = ba[1];
         switch(month)
         {
            case 1  :month=Calendar.JANUARY; break;
            case 2  :month=Calendar.FEBRUARY; break;
            case 3  :month=Calendar.MARCH; break;
            case 4  :month=Calendar.APRIL; break;
            case 5  :month=Calendar.MAY; break;
            case 6  :month=Calendar.JUNE; break;
            case 7  :month=Calendar.JULY; break;
            case 8  :month=Calendar.AUGUST; break;
            case 9  :month=Calendar.SEPTEMBER; break;
            case 10 :month=Calendar.OCTOBER; break;
            case 11 :month=Calendar.NOVEMBER; break;
            case 12 :month=Calendar.DECEMBER; break;
         }
         cal.set((ba[0] & 0xff)+1900, month, ba[2], ba[3], ba[4], ba[5]);
         return cal;
      }
      catch(Exception e)
      {
         throw NSIExceptionBuilder.build(e);
      }
   }

  /**
   * Method implemented in NWServer interface.
   *
   * @see NWServer
   */
   public void setTime(
         Calendar cal)
      throws NSIException
   {
      try
      {
         int year = cal.get(Calendar.YEAR) - 1900;
         if(year<80 || year>179)
            throw NSIExceptionBuilder.build(0x8836); //invalid parameter
         int month = cal.get(Calendar.MONTH);
         switch(month)
         {
            case Calendar.JANUARY   :month=1; break;
            case Calendar.FEBRUARY  :month=2; break;
            case Calendar.MARCH     :month=3; break;
            case Calendar.APRIL     :month=4; break;
            case Calendar.MAY       :month=5; break;
            case Calendar.JUNE      :month=6; break;
            case Calendar.JULY      :month=7; break;
            case Calendar.AUGUST    :month=8; break;
            case Calendar.SEPTEMBER :month=9; break;
            case Calendar.OCTOBER   :month=10; break;
            case Calendar.NOVEMBER  :month=11; break;
            case Calendar.DECEMBER  :month=12; break;
         }

         ((CallsService)session.getService(CallsService.KEY)).
            setFileServerDateAndTime(year, month, cal.get(Calendar.DAY_OF_MONTH),
            cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE),
            cal.get(Calendar.SECOND));
      }
      catch(Exception e)
      {
         throw NSIExceptionBuilder.build(e);
      }
   }
}

/** @internal
 ********************************************************************
 *  Binding Enumeration of the bindings of the server context
 */
class ServerBindingEnumerator implements NamingEnumeration
{
   private Enumeration names;
   private Hashtable table;
   private Hashtable env;

   /**
    *  Sets up enumeration from hashtable containing server context bindings.
    */
   public ServerBindingEnumerator(Hashtable binds, Hashtable props)
   {
      table = binds;
      names = table.keys();
      env = props;
   }

  /**
   *
   */
   public boolean hasMoreElements()
   {
      return names.hasMoreElements();
   }

  /**
   *
   */
   public boolean hasMore()
   {
      return hasMoreElements();
   }

  /**
   *
   */
   public Object nextElement()
   {
      try
      {
         return next();
      }
      catch(NamingException e)
      {
         throw new NoSuchElementException(e.getMessage());
      }
   }

  /**
   *
   */
   public Object next() throws NamingException
   {
      try
      {
         String name = (String)names.nextElement();
         Object obj = NamingManager.getObjectInstance(
                        table.get(name),
                        null, null, env);
         if(obj==null)
            throw new NamingException();
         return new Binding(name, obj);
      }
      catch(Exception e)
      {
         NamingException ex = new NamingException();
         ex.setRootCause(e);
         throw ex;
      }
   }

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

/** @internal
 ********************************************************************
 *  NameClassPair Enumeration of the bindings of the server context
 */
class ServerNameClassEnumerator implements NamingEnumeration
{
   private Enumeration names;
   private Hashtable table;

   /**
    *  Sets up enumeration from hashtable containing server context bindings.
    */
   public ServerNameClassEnumerator(Hashtable binds)
   {
      table = binds;
      names = table.keys();
   }

  /**
   *
   */
   public boolean hasMoreElements()
   {
      return names.hasMoreElements();
   }

  /**
   *
   */
   public boolean hasMore()
   {
      return hasMoreElements();
   }

  /**
   *
   */
   public Object nextElement()
   {
      try
      {
         return next();
      }
      catch(NamingException e)
      {
         throw new NoSuchElementException(e.getMessage());
      }
   }

  /**
   *
   */
   public Object next()
      throws NamingException
   {
      String name = (String)names.nextElement();
      Object boundObj = table.get(name);
      String className = ((Reference)boundObj).getClassName();
      return new NameClassPair(name, className);
   }

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