/*
 *  Copyright 2008 biaoping.yin
 *
 *  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" bboss persistent,
 *  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 com.frameworkset.common.poolman.management;

import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
//import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.apache.log4j.Logger;

import com.frameworkset.common.poolman.PoolManConstants;
import com.frameworkset.util.ValueObjectUtil;
import com.frameworkset.velocity.BBossVelocityUtil;

/**
 * This service configures ObjectPools by first looking for 'poolman.xml' in the
 * CLASSPATH. If it finds it, it parses the database entries and generic Class
 * entries and creates MBeans and pools for them, adding each one to an
 * appropriate PoolManager and the MBeanServer. If it cannot find this xml file,
 * it looks for 'poolman.props' and loads those properties in order to create
 * the pools (with no MBeans). If it cannot find poolman.props, it looks for the
 * deprecated pool.props file and treats it as if it were named 'poolman.props'.
 * If it cannot find any of these files, it throws an Exception.
 */
public class PoolManConfiguration implements Serializable {
	private static Logger log = Logger.getLogger(PoolManConfiguration.class);

	private String configFile;
	private ConfigParser handler;

	private ArrayList datasources;
	private ArrayList genericObjects;

	private boolean useJMX = PoolManConstants.DEFAULT_USE_JMX;
	public static String jndi_principal = null;
	public static String jndi_credentials = null;
	private String[] filterdbname = null;
	private String dbnamespace;
	public static String pooltemplates;

	/**
	 * 用户自定义的适配器
	 */
	public Map<String, String> adaptors = new HashMap<String, String>();

	public PoolManConfiguration(String configFile, String dbname) {

		// this.configFile = configFile;
		// this.datasources = new ArrayList();
		// this.genericObjects = new ArrayList();
		// this.filterdbname = dbname;
		this(configFile, null, dbname == null ? null : new String[] { dbname });

	}

	public PoolManConfiguration(String configFile, String dbnamespace,
			String[] dbnames) {

		this.configFile = configFile;
		this.datasources = new ArrayList();
		this.genericObjects = new ArrayList();
		this.filterdbname = dbnames;
		this.dbnamespace = dbnamespace;

	}

	public PoolManConfiguration(String configFile) {

		this(configFile, null);
	}

	public boolean isUsingJMX() {
		return handler.isManagementJMX();
	}

	/** Load DataSource info from XML and create a Service for each entry set. */
	public void loadConfiguration(Map context) throws Exception {

		// first try XML
		try {
			parseXML(context);
		} catch (NullPointerException ne) {
			ne.printStackTrace();
			// then try deprecated properties
			System.out.println("\n** ERROR: Unable to find XML file "
					+ configFile + ": " + ne);
			// don't try the props files anymore, it's been over a year
			// System.out.println("** WARNING: Attempting to use deprecated properties files\n");
			// this.datasources =
			// parseProperties(PoolManConstants.PROPS_CONFIG_FILE);

		} catch (Exception e) {
			System.out.println("\n** ERROR: Unable to parse XML file "
					+ configFile + ": " + e);
		}

	}

	public ArrayList getDataSources() {
		return this.datasources;
	}

	public ArrayList getGenericPools() {
		return this.genericObjects;
	}

	public Properties getAdminProperties() {
		return handler.getAdminProps();
	}

	private static Object lock = new Object();

	private static void initpooltemplates() {
		if (pooltemplates == null) {
			synchronized (lock) {
				if (pooltemplates == null) {
					InputStream reader = null;
					ByteArrayOutputStream swriter = null;
					OutputStream temp = null;
					try {
						reader = ValueObjectUtil
								.getInputStreamFromFile(PoolManConstants.XML_CONFIG_FILE_TEMPLATE);

						swriter = new ByteArrayOutputStream();
						temp = new BufferedOutputStream(swriter);

						int len = 0;
						byte[] buffer = new byte[1024];
						while ((len = reader.read(buffer)) > 0) {
							temp.write(buffer, 0, len);
						}
						temp.flush();
						pooltemplates = swriter.toString("UTF-8");

					} catch (Exception e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} finally {
						if (reader != null)
							try {
								reader.close();
							} catch (IOException e) {
							}
						if (swriter != null)
							try {
								swriter.close();
							} catch (IOException e) {
							}
						if (temp != null)
							try {
								temp.close();
							} catch (IOException e) {
							}
					}
					// pooltemplates =
					// ValueObjectUtil.getFileContent(PoolManConstants.XML_CONFIG_FILE_TEMPLATE,"UTF-8");
				}
			}

		}
	}

	private void parseXML(Map context) throws Exception {

		/* CHANGED TO USE JAXP */

		if (!this.configFile.equals(PoolManConstants.XML_CONFIG_FILE_TEMPLATE)) {
			URL confURL = PoolManConfiguration.class.getClassLoader()
					.getResource(configFile);
			if (confURL == null)
				PoolManConfiguration.class.getClassLoader().getResource(
						"/" + configFile);

			if (confURL == null)
				getTCL().getResource(configFile);
			if (confURL == null)
				getTCL().getResource("/" + configFile);
			if (confURL == null)
				confURL = ClassLoader.getSystemResource(configFile);
			if (confURL == null)
				confURL = ClassLoader.getSystemResource("/" + configFile);
			String url = "";
			if (confURL == null) {
				url = System.getProperty("user.dir");

				url += "/" + configFile;
				log.debug("Pool Config file:" + System.getProperty("user.dir")
						+ "/" + configFile);
			} else
				url = confURL.toString();
			this.handler = new ConfigParser(url, this.dbnamespace,
					this.filterdbname);

			SAXParserFactory factory = SAXParserFactory.newInstance();
			factory.setNamespaceAware(false);
			factory.setValidating(false);
			SAXParser parser = factory.newSAXParser();
			parser.parse(url, handler);
			this.adaptors = handler.getAdaptors();
			this.datasources = handler.getDataSourceProperties();
			this.genericObjects = handler.getGenericProperties();
		} else {
			initpooltemplates();
			String poolconfig = BBossVelocityUtil.evaluate(context, "",
					pooltemplates);
			InputStream in = null;
			ByteArrayInputStream sr = null;
			try {

				this.handler = new ConfigParser(
						PoolManConstants.XML_CONFIG_FILE_TEMPLATE,
						this.dbnamespace, this.filterdbname);

				SAXParserFactory factory = SAXParserFactory.newInstance();
				factory.setNamespaceAware(false);
				factory.setValidating(false);
				SAXParser parser = factory.newSAXParser();
				sr = new ByteArrayInputStream(poolconfig.getBytes());
				in = new java.io.BufferedInputStream(sr);
				parser.parse(in, handler);
				this.adaptors = handler.getAdaptors();
				this.datasources = handler.getDataSourceProperties();
				this.genericObjects = handler.getGenericProperties();
			} finally {
				if (sr != null) {
					try {
						sr.close();
					} catch (Exception e2) {

					}
				}
				if (in != null) {
					try {
						in.close();
					} catch (Exception e2) {

					}
				}
			}
		}

	}

	private static ClassLoader getTCL() throws IllegalAccessException,
			InvocationTargetException {
		Method method = null;
		try {
			method = (java.lang.Thread.class).getMethod(
					"getContextClassLoader", null);
		} catch (NoSuchMethodException e) {
			return null;
		}
		return (ClassLoader) method.invoke(Thread.currentThread(), null);
	}

	private ArrayList parseProperties(String propsfilename) throws Exception {

		// backwards compatibility is a b****

		HashMap datasources = new HashMap();
		this.getClass().getClassLoader().getParent().getSystemClassLoader()
				.getResource(null);
		InputStream is = null;
		Properties p = null;

		try {
			is = Thread.currentThread().getContextClassLoader()
					.getResourceAsStream(propsfilename);
			p = new Properties();
			p.load(is);
		} catch (Exception e) {
			if (propsfilename.equals(PoolManConstants.PROPS_CONFIG_FILE)) {
				try {
					is = Thread.currentThread().getContextClassLoader()
							.getResourceAsStream(
									PoolManConstants.OLDPROPS_CONFIG_FILE);
					p = new Properties();
					p.load(is);
				} catch (Exception e2) {
					throw new Exception(
							"Unable to find and read a valid poolman.xml config file. "
									+ "Please ensure that '"
									+ PoolManConstants.XML_CONFIG_FILE
									+ "' is in a directory that is in your CLASSPATH.\n");
				}
				System.out
						.println("\nPLEASE NOTE: You should replace the pool.props file with a valid poolman.xml file\n");
			} else {
				throw new Exception(
						"ERROR: Unable to find and read a valid PoolMan properties file.\n"
								+ "Please ensure that "
								+ PoolManConstants.XML_CONFIG_FILE
								+ " or at least "
								+ propsfilename
								+ " is in a directory that is in your CLASSPATH.\n");
			}
		}

		// now have props with each set of datasource entries delimited by a
		// number
		// split those into an ArrayList of Properties objects

		Properties theseProps = null;
		String entrySetNumber = null;

		for (Enumeration enum_ = p.keys(); enum_.hasMoreElements();) {

			String key = (String) enum_.nextElement();

			// get rid of the "_"
			String adjustedKey = key;
			if (key.indexOf("_") != -1) {
				StringBuffer sb = new StringBuffer(key.substring(0, key
						.indexOf("_")));
				sb.append(key.substring(key.indexOf("_") + 1, key.length()));
				adjustedKey = sb.toString();
				if ((!adjustedKey.toLowerCase().equals("dbname"))
						&& (adjustedKey.startsWith("db"))) {
					adjustedKey = adjustedKey
							.substring(2, adjustedKey.length());
				}
			}
			if (adjustedKey.toLowerCase().startsWith("cacherefresh")) {
				adjustedKey = "cacherefreshinterval"
						+ adjustedKey.substring(12, adjustedKey.length());
			}

			try {
				entrySetNumber = key.substring(key.indexOf('.') + 1, key
						.length());
			} catch (StringIndexOutOfBoundsException sbe) {
				throw new Exception("Unnumbered property in poolman.props: "
						+ key);
			}

			if (datasources.containsKey(entrySetNumber))
				theseProps = (Properties) datasources.get(entrySetNumber);
			else
				theseProps = new Properties();

			theseProps.setProperty(adjustedKey.substring(0, adjustedKey
					.indexOf('.')), p.getProperty(key));

			datasources.put(entrySetNumber, theseProps);

		}

		ArrayList finalDataSourceList = new ArrayList();
		for (Iterator iter = datasources.keySet().iterator(); iter.hasNext();) {
			Properties testp = (Properties) datasources.get(iter.next());
			finalDataSourceList.add(testp);
		}

		return finalDataSourceList;
	}

	/**
	 * @return the adaptors
	 */
	public Map<String, String> getAdaptors() {
		return adaptors;
	}

	/**
	 * @param adaptors
	 *            the adaptors to set
	 */
	public void setAdaptors(Map<String, String> adaptors) {
		this.adaptors = adaptors;
	}

}
