/*
 * Copyright 2010 Ralf Joachim
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.castor.cpa.test.framework;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;

import org.castor.cpa.test.framework.xml.CpactfConf;
import org.castor.cpa.test.framework.xml.DataSource;
import org.castor.cpa.test.framework.xml.Database;
import org.castor.cpa.test.framework.xml.DatabaseChoice;
import org.castor.cpa.test.framework.xml.Driver;
import org.castor.cpa.test.framework.xml.Jndi;
import org.castor.cpa.test.framework.xml.Manager;
import org.castor.cpa.test.framework.xml.Param;
import org.castor.cpa.test.framework.xml.Transaction;
import org.castor.cpa.test.framework.xml.types.DatabaseEngineType;
import org.castor.cpa.test.framework.xml.types.TransactionModeType;
import org.castor.jdo.util.JDOConfFactory;
import org.exolab.castor.util.DTDResolver;
import org.exolab.castor.xml.MarshalException;
import org.exolab.castor.xml.Unmarshaller;
import org.exolab.castor.xml.ValidationException;
import org.xml.sax.InputSource;

/**
 * Registry for CPA configuration.
 * 
 * @author <a href="mailto:ralf DOT joachim AT syscon DOT eu">Ralf Joachim</a>
 * @version $Revision$ $Date: 2006-04-25 15:08:23 -0600 (Tue, 25 Apr 2006) $
 */
public final class CPAConfigRegistry {
    //--------------------------------------------------------------------------

    private String _jdoConfBaseURL;
    
    private String _defaultDatabaseName;
    
    private String _defaultTransactionName;
    
    private final Map<String, Database> _databases
        = new HashMap<String, Database>();
    
    private final Map<String, Transaction> _transactions
        = new HashMap<String, Transaction>();
    
    //--------------------------------------------------------------------------

    public void loadConfiguration(final String url) {
        InputSource source = new InputSource(url);
        _jdoConfBaseURL = source.getSystemId();
        
        CpactfConf cpactfconf = null;
        try {
            Unmarshaller unmarshaller = new Unmarshaller(CpactfConf.class);
            unmarshaller.setEntityResolver(new DTDResolver(null));
            cpactfconf = (CpactfConf) unmarshaller.unmarshal(source);
        } catch (MarshalException e) {
            throw new CPAConfigException(e); 
        } catch (ValidationException e) {
            throw new CPAConfigException(e);
        }

        _defaultDatabaseName = cpactfconf.getDefaultDatabase();
        _defaultTransactionName = cpactfconf.getDefaultTransaction();
        
        Iterator<? extends Database> dbIter = cpactfconf.iterateDatabase();
        while (dbIter.hasNext()) {
            Database database = dbIter.next();
            _databases.put(database.getName(), database);
        }
        
        Iterator<? extends Transaction> transIter = cpactfconf.iterateTransaction();
        while (transIter.hasNext()) {
            Transaction trans = transIter.next();
            _transactions.put(trans.getName(), trans);
        }
    }
    
    //--------------------------------------------------------------------------

    public String getJdoConfBaseURL() { return _jdoConfBaseURL; }
    
    public String getDefaultDatabaseName() { return _defaultDatabaseName; }
    
    public String getDefaultTransactionName() { return _defaultTransactionName; }
    
    public DatabaseEngineType getEngine(final String db) {
        if (!_databases.containsKey(db)) {
            throw new CPAConfigException("Database config '" + db + "' not found.");
        }

        Database database = _databases.get(db);
        
        if (database.getEngine() == null) {
            throw new CPAConfigException("No engine specified "
                    + "in database config '" + db + "'.");
        }
        
        return database.getEngine();
    }
    
    public org.castor.jdo.conf.Database createDbConfig(final String name, final String db) {
        if (name == null) {
            throw new CPAConfigException("No configuration name specified.");
        } else if (!_databases.containsKey(db)) {
            throw new CPAConfigException("Database config '" + db + "' not found.");
        }

        Database database = _databases.get(db);
        
        if (database.getEngine() == null) {
            throw new CPAConfigException("No engine specified "
                    + "in database config '" + db + "'.");
        }
        String engine = database.getEngine().toString();
        
        DatabaseChoice choice = database.getDatabaseChoice();
        if (choice == null) {
            throw new CPAConfigException("Neither driver, datasource nor jndi specified "
                    + "in database config '" + db + "'.");
        }
        
        org.castor.jdo.conf.Database dbConfig;
        if (choice.getDriver() != null) {
            dbConfig = JDOConfFactory.createDatabase(name, engine, 
                    createDriver(db, choice.getDriver()));
        } else if (choice.getDataSource() != null) {
            dbConfig = JDOConfFactory.createDatabase(name, engine, 
                    createDataSource(db, choice.getDataSource()));
        } else if (choice.getJndi() != null) {
            dbConfig = JDOConfFactory.createDatabase(name, engine, 
                    createJNDI(db, choice.getJndi()));
        } else {
            throw new CPAConfigException("Neither driver, datasource nor jndi specified "
                    + "in database config '" + db + "'.");
        }
        return dbConfig;
    }
    
    private org.castor.jdo.conf.Driver createDriver(final String db, final Driver driver) {
        String classname = driver.getClassName();
        if (classname == null) {
            throw new CPAConfigException("No classname specified for driver "
                    + "in database config '" + db + "'.");
        }

        String connect = driver.getUrl();
        if (connect == null) {
            throw new CPAConfigException("No connect string specified for driver "
                    + "in database config '" + db + "'.");
        }

        String user = null;
        String password = null;
        for (int i = 0; i < driver.getParamCount(); i++) {
            Param param = driver.getParam(i);
            if ("user".equalsIgnoreCase(param.getName())) { user = param.getValue(); }
            if ("password".equalsIgnoreCase(param.getName())) { password = param.getValue(); }
        }
        if (user == null) {
            throw new CPAConfigException("Parameter 'user' is missing for driver "
                    + "in database config '" + db + "'.");
        }
        if (password == null) {
            throw new CPAConfigException("Parameter 'password' is missing for driver "
                    + "in database config '" + db + "'.");
        }
        
        return JDOConfFactory.createDriver(classname, connect, user, password);
    }
    
    private org.castor.jdo.conf.DataSource createDataSource(final String db, final DataSource ds) {
        String classname = ds.getClassName();
        if (classname == null) {
            throw new CPAConfigException("No classname specified for datasource "
                    + "in database config '" + db + "'.");
        }

        Properties props = new Properties();
        for (int i = 0; i < ds.getParamCount(); i++) {
            Param param = ds.getParam(i);
            props.put(param.getName(), param.getValue());
        }
        
        return JDOConfFactory.createDataSource(classname, props);
    }
    
    private org.castor.jdo.conf.Jndi createJNDI(final String db, final Jndi jndi) {
        String name = jndi.getName();
        if (name == null) {
            throw new CPAConfigException("No JNDI name specified "
                    + "in database config '" + db + "'.");
        }
        
        return JDOConfFactory.createJNDI(name);
    }
    
    public org.castor.jdo.conf.TransactionDemarcation createTxConfig(final String tx) {
        if (!_transactions.containsKey(tx)) {
            throw new CPAConfigException("Transaction config '" + tx + "' not found.");
        }

        Transaction trans = _transactions.get(tx);
        
        if (trans.getMode() == TransactionModeType.LOCAL) {
            return JDOConfFactory.createLocalTransactionDemarcation();
        } else if (trans.getMode() == TransactionModeType.GLOBAL) {
            Manager manager = trans.getManager();
            if (manager == null) {
                throw new CPAConfigException("No manager definition found "
                        + "in global transaction config '" + tx + "'.");
            }
            
            String name = manager.getName();
            if (name == null) {
                throw new CPAConfigException("No manager name specified "
                        + "in global transaction config '" + tx + "'.");
            }
            
            Properties props = new Properties();
            for (int i = 0; i < manager.getParamCount(); i++) {
                Param param = manager.getParam(i);
                props.put(param.getName(), param.getValue());
            }
            
            return JDOConfFactory.createGlobalTransactionDemarcation(name, props);
        } else {
            throw new CPAConfigException("Neither local nor global mode specified "
                    + "in transaction config '" + tx + "'.");
        }
    }

    //--------------------------------------------------------------------------
}
