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

  $Archive: /njcl_v2rmi/src/com/novell/service/nds/naming/net/JavaObject.java $
  $Revision: 6 $
  $Modtime: 1/05/01 3:59p $

  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.io.*;

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

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

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

import com.novell.service.jncpv2r.net.NetObjectInfo;


/**@internal
 *
 */
public class JavaObject
{
   /**
    * Encode an object in NDS attributes.
    * Supports binding Referenceable or Reference, and Serializable.
    *
    * If the object supports the Referenceable interface then encode
    * the reference to the object. See encodeReference() for details.
    *<p>
    * If the object is serializable, it is stored as follows:
    * javaClassName
    *   value: Object.getClass();
    * javaSerializedData
    *   value: serialized form of Object (in binary form).
    */
   protected static Attributes encodeObject (
         char separator,
         Object obj,
         Attributes attrs,
         boolean cloned)
      throws NamingException
   {
      Attribute objectClass;

      if (attrs == null)
      {
         attrs = new BasicAttributes ();
      }

      objectClass = getObjectClass (attrs);

      // References
      if (obj instanceof Referenceable)
      {
         return (encodeReference (
                                    separator,
                   ((Referenceable) obj).getReference (),
                                    attrs,
                                    objectClass));
      }
      else if (obj instanceof Reference)
      {
         return (encodeReference (
                                    separator,
                        (Reference) obj,
                                    attrs,
                                    objectClass));
      }
      // Serializable Object
      else if (obj instanceof java.io.Serializable)
      {
         encodeSerializedObject (obj, attrs, objectClass);
      }
      else
      {
         throw (new IllegalArgumentException ());
      }
      return (attrs);

   } /* encodeObject () */

   /*
    * Decode an object from NDS attribute(s).
    * The object may be a Reference, a Serialized object, or a Remote object.
    *
    * See encodeObject() and encodeReference() for details on formats
    * expected.
    */
   static Object decodeObject (
         Attributes attrs)
      throws NamingException
   {
      Attribute attr;
      Attribute objectClass;

      objectClass = getObjectClass (attrs);

      // Get codebase, which is used in all 3 cases.
      String codebase = javaCodeBase (attrs);

      try
      {
         if (objectClass.contains (new NdsClassName ("javaSerializedObject")))
         {
            return (decodeSerializedObject (attrs));
         }
         else if (objectClass.contains (new NdsClassName ("javaNamingReference")))
         {
            return (decodeReference (attrs, codebase));
         }
         else
         {
            throw (new NamingException ());
         }
      }
      catch (IOException e)
      {
         NamingException ne = new NamingException ();

         ne.setRootCause (e);
         throw (ne);
      }

   } /* decodeObject () */

   /**
    * Convert a Reference object into several NDS attributes.
    *
    * A Reference is stored as the following attributes:
    * javaClassName
    *   value: Reference.getClassName();
    * javaFactory
    *   value: Reference.getFactoryClassName();
    * javaCodeBase
    *   value: Reference.getFactoryClassLocation();
    * javaReferenceAddress
    *   value: #0#typeA#valA
    *   value: #1#typeB#valB
    *   value: #2#typeC##[serialized RefAddr C]
    *   value: #3#typeD#valD
    *
    * where
    * -  the first character denotes the separator
    * -  the number following the first separator denotes the position
    *    of the RefAddr within the Reference
    * -  "typeA" is RefAddr.getType()
    * -  ## denotes that the Base64-encoded form of the non-StringRefAddr
    *    is to follow; otherwise the value that follows is
    *    StringRefAddr.getContents()
    *
    * The default separator is the hash character (#).
    * May provide property for this in future.
    */
   private static Attributes encodeReference (
         char separator,
         Reference ref,
         Attributes attrs,
         Attribute objectClass)
      throws NamingException
   {
      String s;

      objectClass.add (new NdsClassName ("javaNamingReference"));
      attrs.put (objectClass);

      if ((s = ref.getClassName ()) != null)
      {
         attrs.put (new BasicAttribute ("javaClassName", new NdsCaseExactString (s)));
      }
      if ((s = ref.getFactoryClassName ()) != null)
      {
         attrs.put (new BasicAttribute ("javaFactory", new NdsCaseExactString (s)));
      }
      if ((s = ref.getFactoryClassLocation ()) != null)
      {
         attrs.put (new BasicAttribute ("javaCodeBase", new NdsCaseExactString (s)));
      }

      int count = ref.size ();

      if (count > 0)
      {
         Attribute refAttr = new BasicAttribute("javaReferenceAddress");
         RefAddr refAddr;
         BASE64Encoder encoder = null;

         for (int i = 0; i < count; i++)
         {
            refAddr = ref.get (i);

            if (refAddr instanceof StringRefAddr)
            {
               refAttr.add (new NdsCaseExactString (""+ separator + i +
                  separator + refAddr.getType () +
                  separator + refAddr.getContent ()));
            }
            else
            {
               if (encoder == null)
                  encoder = new BASE64Encoder ();

               refAttr.add (new NdsCaseExactString (""+ separator + i +
                  separator + refAddr.getType () +
                  separator + separator +
                  encoder.encodeBuffer (serializeObject (refAddr))));
            }
         }
         attrs.put(refAttr);
      }
      return (attrs);

   } /* encodeReference () */

   /*
    * Restore a Reference object from several LDAP attributes
    */
   private static Reference decodeReference (
         Attributes attrs,
         String codebase)
      throws NamingException, IOException
   {
      Attribute attr;
      String className;
      String factory = null;

      if ((attr = attrs.get("javaClassName")) != null)
      {
         className = (String)((NdsCaseExactString) attr.get ()).getCaseExactString ();
      }
      else
      {
         throw new InvalidAttributesException("javaClassName" + " attribute is required");
      }

      if ((attr = attrs.get("javaFactory")) != null)
      {
         factory = (String)((NdsCaseExactString) attr.get()).getCaseExactString ();
      }

      Reference ref = new Reference(className, factory, codebase);

      /*
       * string encoding of a RefAddr is either:
       *
       *   #posn#<type>#<address>
       * or
       *   #posn#<type>##<base64-encoded address>
       */
      if ((attr = attrs.get("javaReferenceAddress")) != null)
      {
         String val, posnStr, type;
         char separator;
         int start, sep, posn;
         BASE64Decoder decoder = null;

//         ClassLoader cl = (codebase != null ? LdapCtxFactory.helper.getURLClassLoader(codebase) : null);

         for (NamingEnumeration vals = attr.getAll(); vals.hasMore(); )
         {
            val = (String)((NdsCaseExactString) vals.next()).getCaseExactString ();

            if (val.length() == 0)
            {
               throw new InvalidAttributeValueException("malformed " + "javaReferenceAddress" + " attribute - " + "empty attribute value");
            }

            // first character denotes encoding separator
            separator = val.charAt(0);
            start = 1;  // skip over separator

            // extract position within Reference
            if ((sep = val.indexOf(separator, start)) < 0)
            {
               throw new InvalidAttributeValueException("malformed " + "javaReferenceAddress" + " attribute - " + "separator '" + separator + "'" + "not found");
            }
            if ((posnStr = val.substring(start, sep)) == null)
            {
               throw new InvalidAttributeValueException("malformed " + "javaReferenceAddress" + " attribute - " + "empty RefAddr position");
            }
            try
            {
               posn = Integer.parseInt(posnStr);
            }
            catch (NumberFormatException nfe)
            {
               throw new InvalidAttributeValueException("malformed " + "javaReferenceAddress" + " attribute - " + "RefAddr position not an integer");
            }
            start = sep + 1; // skip over position and trailing separator

            // extract type
            if ((sep = val.indexOf(separator, start)) < 0)
            {
               throw new InvalidAttributeValueException("malformed " + "javaReferenceAddress" + " attribute - " + "RefAddr type not found");
            }
            if ((type = val.substring(start, sep)) == null)
            {
               throw new InvalidAttributeValueException("malformed " + "javaReferenceAddress" + " attribute - " + "empty RefAddr type");
            }
            start = sep + 1; // skip over type and trailing separator

            // extract content
            if (start == val.length())
            {
               // Empty content
               ref.add(posn, new StringRefAddr(type, null));
            }
            else if (val.charAt(start) == separator)
            {
               // Double separators indicate a non-StringRefAddr
               // Content is a Base64-encoded serialized RefAddr

               ++start;  // skip over consecutive separator
               // %%% RL: exception if empty after double separator

               if (decoder == null)
                  decoder = new BASE64Decoder();

//               RefAddr ra = (RefAddr) deserializeObject(decoder.decodeBuffer(val.substring(start)), cl);
               RefAddr ra = (RefAddr) deserializeObject(decoder.decodeBuffer(val.substring(start)));

               ref.add(posn, ra);
            }
            else
            {
               // Single separator indicates a StringRefAddr
               ref.add (posn, new StringRefAddr(type, val.substring(start)));
            }
         }
      }
      return (ref);

   } /*  */

   /*
    *
    */
   private static Attributes encodeRemoteObject (
         Object obj,
         Attributes attrs,
         Attribute objectClass)
      throws NamingException
   {
      return (null);

   } /* encodeRemoteObject () */

   /*
    *
    */
   private static Object decodeRemoteObject (
         Attributes attrs)
      throws NamingException
   {
       return (null);

   } /* decodeRemoteObject () */

   /*
    *
    */
   private static Attributes encodeSerializedObject (
         Object obj,
         Attributes attrs,
         Attribute objectClass)
      throws NamingException
   {
      objectClass.add (new NdsClassName ("javaSerializedObject"));
      attrs.put (objectClass);
      attrs.put ("javaClassName", new NdsCaseExactString (obj.getClass ().getName ()));
      attrs.put ("javaSerializedData", new NdsOctetString (serializeObject (obj)));

      return (attrs);

   } /* encodeSerializedObject () */

   /*
    * Deserializes a byte array into an object.
    */
   private static Object decodeSerializedObject (
         Attributes attrs)
      throws NamingException
   {
      return (deserializeObject (javaSerializedData (attrs)));

   } /* decodeSerializedObject () */

   /**
    *
    */
   private static Attribute getObjectClass (
         Attributes attrs)
      throws NamingException
   {
      Attribute objectClass;

      objectClass = attrs.get ("Object Class");
      if (objectClass == null)
      {
         objectClass = new BasicAttribute ("Object Class", new NdsClassName ("javaContainer"));
      }
      return (objectClass);
   }

   /**
    *
    */
   private static String javaCodeBase (
         Attributes attrs)
      throws NamingException
   {
      Attribute javaCodeBase;

      javaCodeBase = attrs.get ("javaCodeBase");
      if (javaCodeBase != null)
      {
         return ((String)((NdsCaseExactString) javaCodeBase.get ()).getCaseExactString ());
      }
      return (null);
   }

   /**
    *
    */
   private static byte[] javaSerializedData (
         Attributes attrs)
      throws NamingException
   {
      Attribute javaSerializedData;

      javaSerializedData = attrs.get ("javaSerializedData");
      if (javaSerializedData != null)
      {
         NdsOctetString octetString;

         octetString = (NdsOctetString) javaSerializedData.get ();
         return (octetString.getOctetString ());
      }
      throw (new NamingException ());
   }

   /*
    * Serialize an object into a byte array
    */
   private static byte[] serializeObject (
         Object obj)
      throws NamingException
   {
      try
      {
         ByteArrayOutputStream baos = new ByteArrayOutputStream ();
         ObjectOutputStream oos = new ObjectOutputStream (baos);

         oos.writeObject (obj);
         oos.close ();

         return (baos.toByteArray ());
      }
      catch (IOException e)
      {
         NamingException ne = new NamingException ();

         ne.setRootCause (e);
         throw (ne);
      }

   } /* serializeObject () */

   /*
    * Deserializes a byte array into an object.
    */
   private static Object deserializeObject (
         byte[] obj)
      throws NamingException
   {
      try
      {
         // Create ObjectInputStream for deserialization
         ByteArrayInputStream bais = new ByteArrayInputStream (obj);
         ObjectInputStream ois = new ObjectInputStream (bais);

         try
         {
            return ois.readObject ();
         }
         catch (ClassNotFoundException e)
         {
            NamingException ne = new NamingException ();

            ne.setRootCause (e);
            throw (ne);
         }
         finally
         {
            ois.close ();
         }
      }
      catch (IOException e)
      {
         NamingException ne = new NamingException ();

         ne.setRootCause (e);
         throw (ne);
      }

   } /* deserializeObject () */

} /* JavaObject */


