/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2008 Bull S.A.S.
 * Contact: jonas-team@ow2.org
 *
 * Application Versioning System
 * Copyright (C) 2008 France Telecom R&D
 *
 * 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
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

// Use the org.ow2.carol.jndi.spi instead of an org.ow2.jonas.* package in
// order to make the transition between MultiContext and VirtualJNDILookup
// transparent for CAROL bundle inclusions.
package org.ow2.carol.jndi.spi;

import java.util.Hashtable;

import javax.naming.Binding;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.NameClassPair;
import javax.naming.NameParser;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;

import org.ow2.jonas.versioning.VersioningService;

/**
 * Handles virtual JNDI lookup.
 * @author S. Ali Tokmen
 */
public class VirtualJNDILookup implements Context {

    /**
     * Parent context.
     */
    private Context parent;

    /**
     * Versioning service.
     */
    private static VersioningService versioningService;

    /**
     * Saves the parent context.
     * @param parent Parent context.
     */
    public VirtualJNDILookup(final Context parent) {
        this.parent = parent;
    }

    /**
     * Retrieves the named object using the prefixed naming strategy:
     * <ul>
     * <li>First, the name prefix + name is looked up.
     * <li>If not found, name alone is looked up.
     * <li>If still not found, name without the prefix is looked up.
     * </ul>
     * Note that name is looked up directly if no prefix exists.
     * @param name Name of the object to look up
     * @return The object bound to <tt>name</tt>
     * @throws NamingException If a naming exception is encountered
     */
    public Object lookup(final String name) throws NamingException {
        String prefix = getPrefix();
        if (prefix != null) {
            try {
                // First, look for prefix + name
                return parent.lookup(prefix + name);
            } catch (NamingException ignored) {
                try {
                    // prefix + name not found, try name
                    return parent.lookup(name);
                } catch (NamingException ne) {
                    // name alone not found, try name - prefix
                    if (name.startsWith(prefix)) {
                        // This is the last trial, don't catch the
                        // NamingException
                        return parent.lookup(name.substring(prefix.length()));
                    } else {
                        // name doesn't start with prefix and a lookup trial
                        // on name has already been done: throw back the
                        // NamingException
                        throw ne;
                    }
                }
            }
        }

        // No prefix present, call parent as usual
        return parent.lookup(name);
    }

    /**
     * @see VirtualJNDILookup#lookup(String)
     * @param name Name of the object to look up
     * @return The object bound to <tt>name</tt>
     * @throws NamingException If a naming exception is encountered
     */
    public Object lookup(final Name name) throws NamingException {
        return lookup(name.toString());
    }

    /**
     * Retrieves the named object using the prefixed naming strategy:
     * <ul>
     * <li>First, the name prefix + name is looked up.
     * <li>If not found, name alone is looked up.
     * <li>If still not found, name without the prefix is looked up.
     * </ul>
     * Note that name is looked up directly if no prefix exists.
     * @param name Name of the object to look up
     * @return The object bound to <tt>name</tt>, not following the terminal
     *         link (if any)
     * @throws NamingException if a naming exception is encountered
     */
    public Object lookupLink(final String name) throws NamingException {
        String prefix = getPrefix();
        if (prefix != null) {
            try {
                // First, look for prefix + name
                return parent.lookupLink(prefix + name);
            } catch (NamingException ignored) {
                try {
                    // prefix + name not found, try name
                    return parent.lookupLink(name);
                } catch (NamingException ne) {
                    // name alone not found, try name - prefix
                    if (name.startsWith(prefix)) {
                        // This is the last trial, don't catch the
                        // NamingException
                        return parent.lookupLink(name.substring(prefix.length()));
                    } else {
                        // name doesn't start with prefix and a lookup trial
                        // on name has already been done: throw back the
                        // NamingException
                        throw ne;
                    }
                }
            }
        }

        // No prefix present, call parent as usual
        return parent.lookupLink(name);
    }

    /**
     * @see VirtualJNDILookup#lookupLink(String)
     * @param name Name of the object to look up
     * @return The object bound to <tt>name</tt>, not following the terminal
     *         link (if any)
     * @throws NamingException if a naming exception is encountered
     */
    public Object lookupLink(final Name name) throws NamingException {
        return lookupLink(name.toString());
    }

    /**
     * Gets the JNDI naming prefix. This can be set using the EAR or the via the
     * InitialContext properties.
     * @return JNDI naming prefix to use, null if none found.
     */
    protected String getPrefix() {
        String prefix;
        try {
            prefix = (String) parent.lookup("java:comp/env/JNDILookupPrefix");
        } catch (NamingException e) {
            prefix = null;
        }

        if (prefix != null) {
            return prefix;
        } else {
            // TODO: get prefix from environment
            return null;
        }
    }

    /**
     * @param versioningService The versioning service to set.
     */
    public static void setVersioningService(final VersioningService versioningService) {
        VirtualJNDILookup.versioningService = versioningService;
    }

    /**
     * Sets the versioning service to null.
     */
    public static void unsetVersioningService() {
        VirtualJNDILookup.versioningService = null;
    }

    /**
     * @return The versioning service.
     */
    public static VersioningService getVersioningService() {
        return VirtualJNDILookup.versioningService;
    }

    /**
     * Calls the parent.
     * {@inheritDoc}
     */
    public Object addToEnvironment(final String propName, final Object propVal) throws NamingException {
        return parent.addToEnvironment(propName, propVal);
    }

    /**
     * Calls the parent.
     * {@inheritDoc}
     */
    public void bind(final Name name, final Object obj) throws NamingException {
        parent.bind(name, obj);
    }

    /**
     * Calls the parent.
     * {@inheritDoc}
     */
    public void bind(final String name, final Object obj) throws NamingException {
        parent.bind(name, obj);
    }

    /**
     * Calls the parent.
     * {@inheritDoc}
     */
    public void close() throws NamingException {
        parent.close();
    }

    /**
     * Calls the parent.
     * {@inheritDoc}
     */
    public Name composeName(final Name name, final Name prefix) throws NamingException {
        return parent.composeName(name, prefix);
    }

    /**
     * Calls the parent.
     * {@inheritDoc}
     */
    public String composeName(final String name, final String prefix) throws NamingException {
        return parent.composeName(name, prefix);
    }

    /**
     * Calls the parent.
     * {@inheritDoc}
     */
    public Context createSubcontext(final Name name) throws NamingException {
        return parent.createSubcontext(name);
    }

    /**
     * Calls the parent.
     * {@inheritDoc}
     */
    public Context createSubcontext(final String name) throws NamingException {
        return parent.createSubcontext(name);
    }

    /**
     * Calls the parent.
     * {@inheritDoc}
     */
    public void destroySubcontext(final Name name) throws NamingException {
        parent.destroySubcontext(name);
    }

    /**
     * Calls the parent.
     * {@inheritDoc}
     */
    public void destroySubcontext(final String name) throws NamingException {
        parent.destroySubcontext(name);
    }

    /**
     * Calls the parent.
     * {@inheritDoc}
     */
    public Hashtable<?, ?> getEnvironment() throws NamingException {
        return parent.getEnvironment();
    }

    /**
     * Calls the parent.
     * {@inheritDoc}
     */
    public String getNameInNamespace() throws NamingException {
        return parent.getNameInNamespace();
    }

    /**
     * Calls the parent.
     * {@inheritDoc}
     */
    public NameParser getNameParser(final Name name) throws NamingException {
        return parent.getNameParser(name);
    }

    /**
     * Calls the parent.
     * {@inheritDoc}
     */
    public NameParser getNameParser(final String name) throws NamingException {
        return parent.getNameParser(name);
    }

    /**
     * Calls the parent.
     * {@inheritDoc}
     */
    public NamingEnumeration<NameClassPair> list(final Name name) throws NamingException {
        return parent.list(name);
    }

    /**
     * Calls the parent.
     * {@inheritDoc}
     */
    public NamingEnumeration<NameClassPair> list(final String name) throws NamingException {
        return parent.list(name);
    }

    /**
     * Calls the parent.
     * {@inheritDoc}
     */
    public NamingEnumeration<Binding> listBindings(final Name name) throws NamingException {
        return parent.listBindings(name);
    }

    /**
     * Calls the parent.
     * {@inheritDoc}
     */
    public NamingEnumeration<Binding> listBindings(final String name) throws NamingException {
        return parent.listBindings(name);
    }

    /**
     * Calls the parent.
     * {@inheritDoc}
     */
    public void rebind(final Name name, final Object obj) throws NamingException {
        parent.rebind(name, obj);
    }

    /**
     * Calls the parent.
     * {@inheritDoc}
     */
    public void rebind(final String name, final Object obj) throws NamingException {
        parent.rebind(name, obj);
    }

    /**
     * Calls the parent.
     * {@inheritDoc}
     */
    public Object removeFromEnvironment(final String propName) throws NamingException {
        return parent.removeFromEnvironment(propName);
    }

    /**
     * Calls the parent.
     * {@inheritDoc}
     */
    public void rename(final Name oldName, final Name newName) throws NamingException {
        parent.rename(oldName, newName);
    }

    /**
     * Calls the parent.
     * {@inheritDoc}
     */
    public void rename(final String oldName, final String newName) throws NamingException {
        parent.rename(oldName, newName);
    }

    /**
     * Calls the parent.
     * {@inheritDoc}
     */
    public void unbind(final Name name) throws NamingException {
        parent.unbind(name);
    }

    /**
     * Calls the parent.
     * {@inheritDoc}
     */
    public void unbind(final String name) throws NamingException {
        parent.unbind(name);
    }
}
