/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999 Bull S.A.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * Initial developer(s): Guillaume Sauthier
 * Contributor(s):
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */
package org.ow2.jonas.lib.util;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;

import javax.naming.BinaryRefAddr;
import javax.naming.RefAddr;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OptionalDataException;

/**
 * JNDIUtils groups all commonly used methods for JNDI.
 * @author Guillaume Sauthier
 */
public class JNDIUtils {

    /**
     * Private empty Constructor for Utility classes
     */
    private JNDIUtils() { }

    /**
     * Return an array of byte from a given object
     * @param obj the object from which we must extract the bytes.
     * @return the byte[] from an object
     */
    public static byte[] getBytesFromObject(Object obj) {
        return getBytesFromObject(obj, null);
    }
    
    /**
     * Return an array of byte from a given object
     * @param obj the object from which we must extract the bytes.
     * @param logger logger to log exceptions
     * @return the byte[] from an object
     */
    public static byte[] getBytesFromObject(Object obj, Logger logger) {

        if (obj == null) {
            return null;
        }

        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        ObjectOutputStream oos = null;
        byte[] bytes = null;

        try {
            oos = new ObjectOutputStream(baos);
            oos.writeObject(obj);
            bytes = baos.toByteArray();
        } catch (Exception e) {
            //Can't tranform
            return null;
        } finally {
            try {
                oos.close();
                baos.close();
            } catch (Exception e) {
                if (logger != null) {
                    logger.log(BasicLevel.DEBUG, "Cannot close output streams : '" + e.getMessage() + "'");
                }
            }
        }
        return bytes;
    }

    /**
     * Return an object from an array of bytes. Useful for BinaryRefAddr
     * Useful for BinaryRefAddr
     * @param bytes an array of bytes
     * @return an object or null if there is an error of if it's empty
     */
    public static Object getObjectFromBytes(byte[] bytes) {
        return getObjectFromBytes(bytes, null);    
    }
    
    /**
     * Return an object from an array of bytes.
     * Useful for BinaryRefAddr
     * @param bytes an array of bytes
     * @param logger logger to log exceptions
     * @return an object or null if there is an error of if it's empty
     */
    public static Object getObjectFromBytes(byte[] bytes, Logger logger) {
        //Declaration
        ByteArrayInputStream bis = null;
        ObjectInputStream ois = null;
        Object obj = null;

        if (bytes == null) {
            return null;
        }

        bis = new ByteArrayInputStream(bytes);
        try {
            ois = new ObjectInputStream(bis);
            obj = ois.readObject();

        } catch (ClassNotFoundException cfe) {
            if (logger != null) {
                logger.log(BasicLevel.DEBUG, "Cannot get object from bytes : " + cfe.getMessage());
            }
        } catch (OptionalDataException ode) {
            if (logger != null) {
                logger.log(BasicLevel.DEBUG, "Cannot get object from bytes : " + ode.getMessage());
            }
        } catch (IOException ioe) {
            if (logger != null) {
                logger.log(BasicLevel.DEBUG, "Cannot get object from bytes : " + ioe.getMessage());
            }
        } finally {
            try {
                bis.close();
                ois.close();
            } catch (Exception e) {
                if (logger != null) {
                    logger.log(BasicLevel.DEBUG, "Cannot close input stream : " + e.getMessage());
                }
            }
        }
        return obj;
    }

    /**
     * Insert a key/value String/String couple in the given Reference as a StringRefAddr.
     * @param ref Reference to be modified
     * @param key Address' key
     * @param value Address' value
     */
    public static void insertInto(final Reference ref, final String key, final String value) {
        if (value != null) {
            RefAddr addr = new StringRefAddr(key, value);
            ref.add(addr);
        } // else, print a debug message ?
    }

    /**
     * Insert a key/value String/Object couple in the given Reference after serialization of the value.
     * @param ref Reference to be modified
     * @param key Address' key
     * @param value Object value (will be serialized)
     */
    public static void insertInto(final Reference ref, final String key, final Object value) {
        if (value != null) {
            RefAddr addr = new BinaryRefAddr(key, getBytesFromObject(value));
            ref.add(addr);
        } // else, print a debug message ?
    }

    /**
     * Extract a String value with the given key, from the Reference.
     * @param ref Reference that holds the value within
     * @param key Key of the value
     * @return the String value
     */
    public static String extractStringFrom(final Reference ref, final String key) {
        StringRefAddr addr = (StringRefAddr) ref.get(key);
        if (addr == null) {
            return null;
        }
        return (String) addr.getContent();
    }

    /**
     * Extract an Object from the Reference. Notice that this object have to
     * be castable to the given Class parameter. The ClassLoader where this
     * class live is used to deserialize the object.
     * If it cannot load the requested class, a ClassLoading error will probably occur.
     * @param ref Reference that holds the serialized version of the requested object
     * @param key Object's key
     * @param type Object's type (may be an interface)
     * @return an Object casted with Class<type>
     */
    public static <T> T extractObjectFrom(final Reference ref,
                                          final String key,
                                          final Class<T> type) {
        BinaryRefAddr addr = (BinaryRefAddr) ref.get(key);
        if (addr == null) {
            return null;
        }
        byte[] serialized = (byte[]) addr.getContent();
        Object result = getObjectFromBytes(serialized);
        return type.cast(result);
    }

}
