package org.apache.ojb.broker.metadata;

/* ====================================================================
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2001 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Apache" and "Apache Software Foundation" and
 *    "Apache ObjectRelationalBridge" must not be used to endorse or promote products
 *    derived from this software without prior written permission. For
 *    written permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    "Apache ObjectRelationalBridge", nor may "Apache" appear in their name, without
 *    prior written permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */

import java.io.Serializable;
import java.util.*;

import org.apache.ojb.broker.PBKey;
import org.apache.ojb.broker.PersistenceBrokerException;
import org.apache.ojb.broker.singlevm.PersistenceBrokerConfiguration;
import org.apache.ojb.broker.util.configuration.impl.OjbConfigurator;
import org.apache.ojb.broker.util.logging.LoggerFactory;

/**
 * The repository containing all mapping information of the PersistenceBroker.
 * The data is stored in a xml file "repository.xml" in the directory of the main class.
 * @author <a href="mailto:thma@apache.org">Thomas Mahler<a>
 * @version $Id: DescriptorRepository.java,v 1.19 2002/09/29 15:53:38 thma Exp $
 */
public class DescriptorRepository implements Serializable, XmlCapable, IsolationLevels
{
    /**
     * Default descriptor repository.
     */
    private static String defaultDescriptor;
    /**
     * Map manage all descriptor repository instances.
     */
    private static Map repositories = Collections.synchronizedMap(new HashMap());
    
    /**
     * The version identifier of the Repository.
     * Used to validate repository.xml against the dtd.
     */
    private static String VERSION = "0.9.6";


    /**
     * path to the xml repository file containing the mapping information
     */
    private String _repositoryfile = "repository.xml";

    /**
     * Key to identify this repository instance.
     */
    private PBKey pbKey;

    /**
     * This table holds all known Mapping descriptions.
     * Key values are the respective Class objects
     */
    private Hashtable descriptorTable = new Hashtable();

    /**
     * We need a lot the extent, to which a class belongs
     * (@see DescriptorRepository#getExtentClass). To speed up the costy
     * evaluation, we use this tiny hash map.
     */
    private HashMap extentTable = new HashMap();

    /**
     * The Descriptor of a default JDBC connection, to be used when no
     * individual descriptor is given for a certain class
     */
    private JdbcConnectionDescriptor defaultConnectionDescriptor;
    
    /**
     * the default siolation level used for this repository
     */
    private int defaultIsolationLevel = IsolationLevels.IL_DEFAULT;

    //*****************************************************
    // constructor

    /**
     * Constructor declaration
     */
    public DescriptorRepository() throws PersistenceBrokerException
    {
    }

    //*****************************************************
    // static methods

    public static DescriptorRepository getInstance(PBKey pbKey)
        throws MetadataException
    {
        DescriptorRepository ret = (DescriptorRepository) repositories.get(pbKey);
        if(ret == null)
        {
            RepositoryPersistor persistor = new RepositoryPersistor();
            try
            {
                ret = persistor.readFromFile(pbKey.getRepositoryFile());
                prepareJdbcConnectionDescriptor(pbKey, ret.getDefaultJdbcConnection());
                ret.setPBKey(pbKey);
                repositories.put(pbKey, ret);
            }
            catch (Throwable t)
            {
                LoggerFactory.getDefaultLogger().fatal(t);
                throw new MetadataException(t);
            }
        }
        return ret;
    }

    private static void prepareJdbcConnectionDescriptor(PBKey key, JdbcConnectionDescriptor jcd)
    {
        if(key.getUser() != null)
        {
            jcd.setUserName(key.getUser());
            jcd.setPassWord(key.getPassword());
        }
    }

    /**
     *
     */
    public static DescriptorRepository getDefaultInstance() throws MetadataException
    {
        if(defaultDescriptor == null)
        {
            defaultDescriptor = ((PersistenceBrokerConfiguration) OjbConfigurator.getInstance()
                                    .getConfigurationFor(null)).getRepositoryFilename();
        }
        return getInstance(defaultDescriptor);
    }


    /**
     * @deprecated
     * @param repositoryFileName
     * @return the DescriptorRepository instance
     * @throws MetadataException
     */
    public static DescriptorRepository getInstance(String repositoryFileName)
        throws MetadataException
    {
        return getInstance(new PBKey(repositoryFileName, null, null));
    }

    /**
     * returns the singleton instance
     * @deprecated
     * @return DescriptorRepository the singleton instance
     * @exception MetadataException
     */
    public static DescriptorRepository getInstance() throws MetadataException
    {
        if(defaultDescriptor == null)
        {
            defaultDescriptor = ((PersistenceBrokerConfiguration) OjbConfigurator.getInstance()
                                    .getConfigurationFor(null)).getRepositoryFilename();
        }
        return getInstance(defaultDescriptor);
    }


    //*****************************************************
    // methods


    private void setPBKey(PBKey key)
    {
        this.pbKey = key;
    }

    public PBKey getPBkey()
    {
        return this.pbKey;
    }

    /**
     * set the path to the xml repository
     */
    public void setRepositoryPath(String pathToRepository)
    {
        _repositoryfile = pathToRepository;
    }


    /**
     * returns a string representation
     */
    public String toString()
    {
        //return "DescriptorRepository\n" + defaultConnectionDescriptor + "\n" + descriptorTable + "\n" + "";
        return DescriptorRepository.class.getName()+" use repository " + this._repositoryfile;
    }

    /**
     * Method declaration
     *
     *
     * @param jcd
     *
     *
     */
    public void setDefaultJdbcConnection(JdbcConnectionDescriptor jcd)
    {
        defaultConnectionDescriptor = jcd;
    }

    /**
     * Return the {@link JdbcConnectionDescriptor} of the repository.
     */
    public JdbcConnectionDescriptor getDefaultJdbcConnection()
    {
        return defaultConnectionDescriptor;
    }

    /**
     * Add a ClassDescriptor to the internal Hashtable<br>
     * Set the Repository for ClassDescriptor
     */
    public void put(Class c, ClassDescriptor cld)
    {
        this.put(c.getName(), cld);
    }
    
    /**
     * Add a ClassDescriptor to the internal Hashtable<br>
     * Set the Repository for ClassDescriptor
     */
    public void put(String classname, ClassDescriptor cld)
    {
        cld.setRepository(this); // BRJ
        descriptorTable.put(classname, cld);
        Vector extentClasses = cld.getExtentClasses();
        for (int i = 0; i < extentClasses.size(); ++i)
        {
            addExtent(((Class) extentClasses.get(i)).getName(), cld);
        }
    }
    
        
    /**
     * Add a pair of extent/classdescriptor to the extentTable to gain speed
     * while retrieval of extents.
     * @param classname the name of the extent itself
     * @param cld the class descriptor, where it belongs to
     */
    void addExtent(String classname, ClassDescriptor cld)
    {
        synchronized (extentTable)
        {
            extentTable.put(classname, cld);
        }
    }    
    

    /**
     * lookup a ClassDescriptor in the internal Hashtable
     */
    public ClassDescriptor getDescriptorFor(Class c) throws ClassNotPersistenceCapableException
    {
        return this.getDescriptorFor(c.getName());
    }
    
    /**
     * lookup a ClassDescriptor in the internal Hashtable
     * @param strClassName a fully qualified class name as it is returned by Class.getName().
     */
    public ClassDescriptor getDescriptorFor(String strClassName) throws ClassNotPersistenceCapableException
    {
        ClassDescriptor result = (ClassDescriptor) descriptorTable.get(strClassName);
        if (result == null)
        {
            throw new ClassNotPersistenceCapableException(strClassName + " not found in OJB Repository");
        }
        else
        {
            return result;
        }
    }

    public boolean hasDescriptorFor(Class c)
    {
        ClassDescriptor result = (ClassDescriptor) descriptorTable.get(c.getName());
        return (result != null);
    }
    
    /**
     * returns the Extent to which the class clazz belongs.
     * This may be a baseclass,an interface or clazz itself, if no Extent
     * is defined.
     * throws a PersistenceBrokerException if clazz is not persistence capable,
     * i.e. if clazz is not defined in the DescriptorRepository.
     * @param the class to lookup the Extent for
     */
    public Class getExtentClass(Class clazz)
    {        
        //try to find an extent that contains clazz
        Class extentClass = null;
        synchronized (extentTable)
        {
            ClassDescriptor cld = (ClassDescriptor) extentTable.get(clazz.getName());
            if (cld != null)
            {
                // Note! MAR Changed to call getExtentClass recursively
                // Removed: extentClass = cld.getClassOfObject();                                
                extentClass = getExtentClass(cld.getClassOfObject());                                
            }
        }
        // if such an extent could not be found just return clazz itself.
        if (extentClass == null)
        {     
            extentClass = clazz;
        }
        return extentClass;
    }

    public Iterator iterator()
    {
        return descriptorTable.values().iterator();
    }

    /*
     * @see XmlCapable#toXML()
     */
    public String toXML()
    {
        RepositoryTags tags = RepositoryTags.getInstance();
        String eol = System.getProperty("line.separator");

        // 1. write XML header
        String strReturn = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + eol;
        
        strReturn += "<!DOCTYPE descriptor-repository SYSTEM \"repository.dtd\" [" + eol;
		strReturn += "<!ENTITY user SYSTEM \"repository_user.xml\">" + eol;
		strReturn += "<!ENTITY junit SYSTEM \"repository_junit.xml\">" + eol;
		strReturn += "<!ENTITY internal SYSTEM \"repository_internal.xml\"> ]>" + eol + eol;

		strReturn += "<!-- OJB RepositoryPersistor generated this file on " + new Date().toString() + " -->" + eol;
        

		// 1a. write opening tab and attributes
        strReturn += tags.getOpeningTagNonClosingById(MAPPING_REPOSITORY) + eol;
        strReturn += "  " + tags.getAttribute(REPOSITORY_VERSION, this.getVersion()) +eol;
        strReturn += "  " + tags.getAttribute(ISOLATION_LEVEL, this.getDefaultIsolationLevelAsString()) +eol;
        
        strReturn += ">" + eol;

        // 2. write default JdbcConnectionDescriptor
        strReturn += this.getDefaultJdbcConnection().toXML();

        // 3. write all ClassDescriptors
        Iterator i = this.iterator();
        while (i.hasNext())
        {
            strReturn += ((XmlCapable) i.next()).toXML() + eol;
        }
        // 4. write closing tag
        return strReturn + tags.getClosingTagById(MAPPING_REPOSITORY);

    }

    /**
     * returns IsolationLevel literal as matching
     * to the corresponding id
     * @return the IsolationLevel literal
     */
    private String getDefaultIsolationLevelAsString()
    {
        if (defaultIsolationLevel == IL_READ_UNCOMMITTED)
        {
            return LITERAL_IL_READ_UNCOMMITTED;
        }
        else if (defaultIsolationLevel == IL_READ_COMMITTED)
        {
            return LITERAL_IL_READ_COMMITTED;
        }
        else if (defaultIsolationLevel == IL_REPEATABLE_READ)
        {
            return LITERAL_IL_REPEATABLE_READ;
        }
        else if (defaultIsolationLevel == IL_SERIALIZABLE)
        {
            return LITERAL_IL_SERIALIZABLE;
        }
        else if (defaultIsolationLevel == IL_OPTIMISTIC)
        {
            return LITERAL_IL_OPTIMISTIC;
        }
        return LITERAL_IL_READ_UNCOMMITTED;
    }


    /**
     * @return all field descriptors for a class that belongs to a set of classes mapped
     * to the same table, otherwise the select queries produced won't contain the necessary
     * information to materialize extents mapped to the same class.
     */
    public Collection getFieldDescriptorsForMultiMappedTable(ClassDescriptor targetCld)
    {
        return getAllMappedColumns(getClassesMappedToSameTable(targetCld));
    }

    private Collection getAllMappedColumns(List classDescriptors)
    {
        Iterator it = classDescriptors.iterator();
        HashMap map = new HashMap();
        ClassDescriptor temp = null;
        while (it.hasNext())
        {
            temp = (ClassDescriptor) it.next();
            FieldDescriptor[] fields = temp.getFieldDescriptions();
            if (fields != null)
            {
                for (int i = 0; i < fields.length; i++)
                {
                    /**
                     * MBAIRD
                     * hashmap will only allow one entry per unique key, so no need to check contains(fields[i].getColumnName()).
                     */
                    map.put(fields[i].getColumnName(), fields[i]);
                }
            }
        }
        return map.values();
    }

    private List getClassesMappedToSameTable(ClassDescriptor targetCld)
    {
        //try to find an extent that contains clazz
        Iterator iter = descriptorTable.values().iterator();
        List retval = new ArrayList();
        while (iter.hasNext())
        {
            ClassDescriptor cld = (ClassDescriptor) iter.next();
            if (cld.getFullTableName() != null)
            {
                if (cld.getFullTableName().equals(targetCld.getFullTableName()))
                {
                    retval.add(cld);
                }
            }
        }
        return retval;
    }

	public Hashtable getDescriptorTable(){
		return descriptorTable;
	}

	public void printDescriptorTable(){
		Enumeration enum = descriptorTable.keys();
		Enumeration enum2 = descriptorTable.elements();
		while (enum.hasMoreElements()) {
			Object cl =  enum.nextElement();
			ClassDescriptor descriptor  = (ClassDescriptor)enum2.nextElement();
			System.out.println(cl + " - " + descriptor.getFullTableName());
		}
	}
	
	public static String getVersion()
	{
		return VERSION;	
	}

    /**
     * Returns the defaultIsolationLevel.
     * @return int
     */
    public int getDefaultIsolationLevel()
    {
        return defaultIsolationLevel;
    }

    /**
     * Sets the defaultIsolationLevel.
     * @param defaultIsolationLevel The defaultIsolationLevel to set
     */
    public void setDefaultIsolationLevel(int defaultIsolationLevel)
    {
        this.defaultIsolationLevel = defaultIsolationLevel;
    }

}