/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2012 Bull S.A.S.
 * 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
 *
 * --------------------------------------------------------------------------
 * $Id:
 * --------------------------------------------------------------------------
 */
package org.ow2.jonas.jndi.internal;

import org.ow2.util.log.Log;
import org.ow2.util.log.LogFactory;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.util.List;

/**
 * JNDI entries service
 * @author Jeremy Cazaux
 */
public class JndiEntriesService {

    /**
     * The list of JNDI entry name
     */
    private List<String> names;

    /**
     * The list of JNDI entry values
     */
    private List<String> values;

    /**
     * The list of JNDI entry tyoe
     */
    private List<String> types;

    /**
     * Logger
     */
    private static final Log logger = LogFactory.getLog(JndiEntriesService.class);

    /**
     * Strict consistency policy
     */
    public static final String STRICT_CONSISTENCY = "strict-consistency";

    /**
     * Last update policy
     */
    public static final String LAST_UPDATE = "last-update";

    /**
     * The policy of JNDI injection
     */
    private Policy policy;

    /**
     * The Context
     */
    private Context context;

    /**
     * Default constructor
     */
    public JndiEntriesService() {
    }

    /**
     * Inject all JNDI entries according to the JNDI policy
     * @throws JndiEntriesException {@link JndiEntriesException}
     */
    public void start() throws JndiEntriesException {

        if ((this.names.size() != this.values.size()) || (this.values.size() != this.types.size())) {
            throw new JndiEntriesException("Each key should be associated to a value and a type");
        }
        
		if (this.context == null) {
			setContext(getContext());
		}        

        int nbEntries = this.names.size();
        for (int i = 0; i < nbEntries; i++) {
            String jndiContext = this.names.get(i);
            String valueAsString = this.values.get(i);
            String type = this.types.get(i);

            Object value;
            if (Boolean.class.getName().equals(type)) {
                value = Boolean.valueOf(valueAsString);
            } else if (Byte.class.getName().equals(type)) {
                value = Byte.valueOf(valueAsString);
            } else if (Character.class.getName().equals(type)) {
                if (valueAsString.length() != 1) {
                    throw new JndiEntriesException("The type of the value " + valueAsString + " cannot be java.lang.Character.");
                }
                value = Character.valueOf(valueAsString.charAt(0));
            } else if (Double.class.getName().equals(type)) {
                value = Double.valueOf(valueAsString);
            } else if (Float.class.getName().equals(type)) {
                value = Float.valueOf(valueAsString);
            } else if (Integer.class.getName().equals(type)) {
                value = Integer.valueOf(valueAsString);
            } else if (Long.class.getName().equals(type)) {
                value = Long.valueOf(valueAsString);
            } else if (Short.class.getName().equals(type)) {
                value = Short.valueOf(valueAsString);
            } else if (String.class.getName().equals(type)) {
                value = valueAsString;
            } else {
                throw new JndiEntriesException("Type " + type + " unknown. It should be a basic Java type (java.lang.*)");
            }
		
            //lookup the JNDI context
            Object val = null;
            try {
                val = this.context.lookup(jndiContext);
            } catch (NamingException e) {
                //do nothing. This entry is not already injected into the JNDI context
            }

            //the entry doesn't exist
            if (val == null) {
                //inject the jndi entry...
                try {
                    this.context.bind(jndiContext, value);
                } catch (NamingException ex) {
                    throw new JndiEntriesException("Cannot inject value " + valueAsString + " into the JNDI context " + jndiContext, ex);
                }
            } else {
                if (Policy.STRICT_CONSISTENCY.equals(this.policy)) {
                     //the key is already inject. If the JNDI policy a strict consistency policy, throw an exception
                    throw new JndiEntriesException("The entry " + jndiContext + " is already injected in the JNDI context");
                } else if (Policy.LAST_UPDATE.equals(this.policy)) {
                    //else we update the value of the associated key
                    try {
                        this.context.rebind(jndiContext, value);
                    } catch (NamingException e) {
                        throw new JndiEntriesException("Cannot update the value " + valueAsString + " of the entry " + jndiContext, e);
                    }
                }
            }
        }
    }

    /**
     * If the JNDI policy is a strict-consistency policy, all JNDI entry'll be removed from the JNDI context
     * @throws JndiEntriesException {@link JndiEntriesException}
     */
    public void stop() throws JndiEntriesException {

        //if it's a strict-consistency policy, we remove jndi entries from the JNDI context
        if (Policy.STRICT_CONSISTENCY.equals(this.policy)) {
            int nbEntries = this.names.size();
            for (int i = 0; i < nbEntries; i++) {
                try {
                    this.context.unbind(this.names.get(i));
                } catch (NamingException e) {
                    logger.error("Cannot remove the entry " + this.names.get(i) + " from the JNDI context.", e);
                }
            }
        }
    }

    /**
     * @param names List of entry name as a String. The space character is the delimiter of each name
     */
    public void setNames(final List<String> names) {
        this.names = names;
    }

    /*
     * @param types List of entry type as a String. The pace character is the delimiter of each type
     */
    public void setTypes(final List<String> types) {
        this.types = types;
    }

    /**
     * @param values List of entry values as a String. The space character is the delimiter of each value
     */
    public void setValues(final List<String> values) {
        this.values = values;
    }

    /**
     * @param policy The JNDI policy to set
     */
    public void setPolicy(final String policy) throws JndiEntriesException {
        this.policy = getPolicyAsEnum(policy);
    }

    /**
     * @param context The {@link Context} to set
     */
    public void setContext(final Context context) {
        this.context = context;
    }
    /*
     * @return a new InitialContext
     */
    private Context getContext() {
        ClassLoader old = Thread.currentThread().getContextClassLoader();
        Thread.currentThread().setContextClassLoader(JndiEntriesService.class.getClassLoader());

        Context context = null;
        try {
            context = new InitialContext();
        } catch (NamingException e) {
            logger.error("Cannot create a new InitialContext", e);
        } finally {
            Thread.currentThread().setContextClassLoader(old);
        }
        return context;
    }

    /**
     * @param policy The policy of a JNDI injection as a String
     * @return The {@link Policy}
     */
    public static Policy getPolicyAsEnum(final String policy) throws JndiEntriesException {
        if (LAST_UPDATE.equals(policy)) {
            return Policy.LAST_UPDATE;
        } else if (STRICT_CONSISTENCY.equals(policy)) {
            return Policy.STRICT_CONSISTENCY;
        } else {
            throw new JndiEntriesException("Policy is incorrect. Correct values are " + Policy.STRICT_CONSISTENCY +
                    " and " + Policy.LAST_UPDATE);
        }
    }
}


