/** * <pre> * This program is free software; you can redistribute it and/or modify it under the terms of * the GNU AFFERO GENERAL PUBLIC LICENSE as published by the Free Software Foundation; either version 3 of the License, * or (at your option) any later version. * * This program 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 AFFERO GENERAL PUBLIC LICENSE for more details. * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE along with this program; * if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * </pre> */ package com.meidusa.amoeba.context; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.apache.commons.pool.PoolableObjectFactory; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.apache.log4j.helpers.LogLog; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.EntityResolver; import org.xml.sax.ErrorHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import com.meidusa.amoeba.config.BeanObjectEntityConfig; import com.meidusa.amoeba.config.ConfigurationException; import com.meidusa.amoeba.config.DBServerConfig; import com.meidusa.amoeba.config.DocumentUtil; import com.meidusa.amoeba.config.ParameterMapping; import com.meidusa.amoeba.config.ProxyServerConfig; import com.meidusa.amoeba.heartbeat.HeartbeatDelayed; import com.meidusa.amoeba.heartbeat.HeartbeatManager; import com.meidusa.amoeba.heartbeat.Status; import com.meidusa.amoeba.net.ConnectionManager; import com.meidusa.amoeba.net.poolable.MultipleLoadBalanceObjectPool; import com.meidusa.amoeba.net.poolable.ObjectPool; import com.meidusa.amoeba.net.poolable.PoolableObject; import com.meidusa.amoeba.route.QueryRouter; import com.meidusa.amoeba.util.Initialisable; import com.meidusa.amoeba.util.InitialisationException; import com.meidusa.amoeba.util.Reporter; import com.meidusa.amoeba.util.StringUtil; /** * @author <a href=mailto:piratebase@sina.com>Struct chen</a> */ public class ProxyRuntimeContext implements Reporter { public static final String DEFAULT_SERVER_CONNECTION_MANAGER_CLASS = "com.meidusa.amoeba.net.AuthingableConnectionManager"; public static final String DEFAULT_REAL_POOL_CLASS = "com.meidusa.amoeba.net.poolable.PoolableObjectPool"; public static final String DEFAULT_VIRTUAL_POOL_CLASS = "com.meidusa.amoeba.server.MultipleServerPool"; protected static Logger logger = Logger.getLogger(ProxyRuntimeContext.class); private static ProxyRuntimeContext context = null; private ProxyServerConfig config; private Map<String, ConnectionManager> conMgrMap = new HashMap<String, ConnectionManager>(); private Map<String, ConnectionManager> readOnlyConMgrMap = Collections.unmodifiableMap(conMgrMap); private Map<String, ObjectPool> poolMap = new HashMap<String, ObjectPool>(); private Map<String, ObjectPool> readOnlyPoolMap = Collections.unmodifiableMap(poolMap); private List<ContextChangedListener> listeners = new ArrayList<ContextChangedListener>(); @SuppressWarnings("unchecked") private QueryRouter queryRouter; private RuntimeContext runtimeContext; private Map<String, Object> beanContext = new HashMap<String, Object>(); public RuntimeContext getRuntimeContext() { return runtimeContext; } public static ProxyRuntimeContext getInstance() { return context; } public static void setInstance(ProxyRuntimeContext context) { ProxyRuntimeContext.context = context; } protected String getDefaultServerConnectionManagerClassName() { return DEFAULT_SERVER_CONNECTION_MANAGER_CLASS; } protected String getDefaultRealPoolClassName() { return DEFAULT_REAL_POOL_CLASS; } protected String getDefaultVirtualPoolClassName() { return DEFAULT_VIRTUAL_POOL_CLASS; } public ProxyServerConfig getConfig() { return config; } public QueryRouter getQueryRouter() { return queryRouter; } public ProxyRuntimeContext(){ } public Map<String, ConnectionManager> getConnectionManagerList() { return readOnlyConMgrMap; } public Map<String, ObjectPool> getPoolMap() { return readOnlyPoolMap; } private List<Initialisable> initialisableList = new ArrayList<Initialisable>(); /** * * @param parent * @param dest * @return */ protected void inheritDBServerConfig(DBServerConfig parent ,DBServerConfig dest){ BeanObjectEntityConfig destBeanConfig = dest.getFactoryConfig(); BeanObjectEntityConfig parentBeanConfig = parent.getFactoryConfig(); if(destBeanConfig != null){ if(parentBeanConfig != null){ inheritBeanObjectEntityConfig(parentBeanConfig,destBeanConfig); } }else{ dest.setFactoryConfig(parentBeanConfig); } destBeanConfig = dest.getPoolConfig(); parentBeanConfig = parent.getPoolConfig(); if(destBeanConfig != null){ if(parentBeanConfig != null){ inheritBeanObjectEntityConfig(parentBeanConfig,destBeanConfig); } }else{ dest.setPoolConfig(parentBeanConfig); } if(dest.getVirtual()== null){ dest.setVirtual(parent.getVirtual()); } if(dest.getAbstractive()== null){ dest.setAbstractive(parent.getAbstractive()); } } public void addContextChangedListener(ContextChangedListener listener){ if(!listeners.contains(listener)){ this.listeners.add(listener); } } public void removeContextChangedListener(ContextChangedListener listener){ this.listeners.remove(listener); } public void notifyAllContextChangedListener(){ for(ContextChangedListener listener : listeners){ listener.doChange(); } } protected void inheritBeanObjectEntityConfig(BeanObjectEntityConfig parent,BeanObjectEntityConfig dest){ BeanObjectEntityConfig parentCloned = (BeanObjectEntityConfig)parent.clone(); if(!StringUtil.isEmpty(dest.getClassName())){ parentCloned.setClassName(dest.getClassName()); } /*if(!StringUtil.isEmpty(dest.getName())){ parentCloned.setName(dest.getName()); }*/ if(dest.getParams() != null){ if(parentCloned.getParams() == null){ parentCloned.setParams(dest.getParams()); }else{ parentCloned.getParams().putAll(dest.getParams()); } } dest.setClassName(parentCloned.getClassName()); //dest.setName(parentCloned.getName()); dest.setParams(parentCloned.getParams()); } private Object createBeanObjectEntity(BeanObjectEntityConfig config,boolean initEarly){ Object object = config.createBeanObject(initEarly); if(!StringUtil.isEmpty(config.getName())){ beanContext.put(config.getName(), object); } return object; } public void init(String file) { config = loadConfig(file); this.runtimeContext = (RuntimeContext)createBeanObjectEntity(config.getRuntimeConfig(),true); /*for (Map.Entry<String, BeanObjectEntityConfig> entry : config.getManagers().entrySet()) { BeanObjectEntityConfig beanObjectEntityConfig = entry.getValue(); try { ConnectionManager manager = (ConnectionManager) beanObjectEntityConfig.createBeanObject(false); manager.setName(entry.getKey()); initialisableList.add(manager); conMgrMap.put(manager.getName(), manager); } catch (Exception e) { throw new ConfigurationException("manager instance error", e); } }*/ for (Map.Entry<String, DBServerConfig> entry : config.getDbServers().entrySet()) { DBServerConfig dbServerConfig = (DBServerConfig)entry.getValue().clone(); String parent = dbServerConfig.getParent(); if(!StringUtil.isEmpty(parent)){ DBServerConfig parentConfig = config.getDbServers().get(parent); if(parentConfig == null || parentConfig.getParent() != null){ throw new ConfigurationException(entry.getKey()+" cannot found parent with name="+parent+" or parent's parent must be null"); } inheritDBServerConfig(parentConfig,dbServerConfig); } //ignore if dbServer is abstract if(dbServerConfig.getAbstractive() != null && dbServerConfig.getAbstractive().booleanValue()){ continue; } try { BeanObjectEntityConfig poolConfig = dbServerConfig.getPoolConfig(); ObjectPool pool = (ObjectPool) poolConfig.createBeanObject(false,conMgrMap); pool.setName(StringUtil.isEmpty(poolConfig.getName())?dbServerConfig.getName():poolConfig.getName()); if (pool instanceof Initialisable) { initialisableList.add((Initialisable) pool); } if (dbServerConfig.getFactoryConfig() != null) { PoolableObjectFactory factory = (PoolableObjectFactory) dbServerConfig.getFactoryConfig().createBeanObject(false,conMgrMap); if (factory instanceof Initialisable) { initialisableList.add((Initialisable) factory); } pool.setFactory(factory); } poolMap.put(entry.getKey(), pool); } catch (Exception e) { throw new ConfigurationException("createBean error dbServer="+dbServerConfig.getName(), e); } } if (config.getQueryRouterConfig() != null) { BeanObjectEntityConfig queryRouterConfig = config.getQueryRouterConfig(); try { queryRouter = (QueryRouter) queryRouterConfig.createBeanObject(false,conMgrMap); if (queryRouter instanceof Initialisable) { initialisableList.add((Initialisable) queryRouter); } if(queryRouter instanceof ContextChangedListener){ this.addContextChangedListener((ContextChangedListener)queryRouter); } } catch (Exception e) { throw new ConfigurationException("queryRouter instance error", e); } } initAllInitialisableBeans(); initialisableList.clear(); for (ConnectionManager cm : getConnectionManagerList().values()) { cm.start(); } initPools(); } protected void initPools() { for (Map.Entry<String, ObjectPool> entry : poolMap.entrySet()) { ObjectPool pool = entry.getValue(); if (pool instanceof MultipleLoadBalanceObjectPool) { MultipleLoadBalanceObjectPool multiPool = (MultipleLoadBalanceObjectPool) pool; multiPool.initAllPools(); } else { PoolableObject object = null; try { object = (PoolableObject) pool.borrowObject(); } catch (Exception e) { logger.error("init pool error!", e); } finally { if (object != null) { try { pool.returnObject(object); } catch (Exception e) { logger.error("return init pools error", e); } } } } if(pool instanceof ContextChangedListener){ this.addContextChangedListener((ContextChangedListener)pool); } } } private void initAllInitialisableBeans() { for (Initialisable bean : initialisableList) { try { bean.init(); if(bean instanceof ContextChangedListener){ this.addContextChangedListener((ContextChangedListener)bean); } } catch (InitialisationException e) { throw new ConfigurationException("Initialisation bean="+bean+" error", e); } } } private ProxyServerConfig loadConfig(String configFileName) { DocumentBuilder db; try { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setValidating(true); dbf.setNamespaceAware(false); db = dbf.newDocumentBuilder(); db.setEntityResolver(new EntityResolver() { public InputSource resolveEntity(String publicId, String systemId) { if (systemId.endsWith("amoeba.dtd")) { InputStream in = ProxyRuntimeContext.class.getResourceAsStream("/com/meidusa/amoeba/xml/amoeba.dtd"); if (in == null) { LogLog.error("Could not find [amoeba.dtd]. Used [" + ProxyRuntimeContext.class.getClassLoader() + "] class loader in the search."); return null; } else { return new InputSource(in); } } else { return null; } } }); db.setErrorHandler(new ErrorHandler() { public void warning(SAXParseException exception) { } public void error(SAXParseException exception) throws SAXException { logger.error(exception.getMessage() + " at (" + exception.getLineNumber() + ":" + exception.getColumnNumber() + ")"); throw exception; } public void fatalError(SAXParseException exception) throws SAXException { logger.fatal(exception.getMessage() + " at (" + exception.getLineNumber() + ":" + exception.getColumnNumber() + ")"); throw exception; } }); return loadConfigurationFile(configFileName, db); } catch (Exception e) { logger.fatal("Could not load configuration file, failing", e); throw new ConfigurationException("Error loading configuration file " + configFileName, e); } } private ProxyServerConfig loadConfigurationFile(String fileName, DocumentBuilder db) { Document doc = null; InputStream is = null; ProxyServerConfig config = new ProxyServerConfig(); try { is = new FileInputStream(new File(fileName)); if (is == null) { throw new Exception("Could not open file " + fileName); } doc = db.parse(is); } catch (Exception e) { final String s = "Caught exception while loading file " + fileName; logger.error(s, e); throw new ConfigurationException(s, e); } finally { if (is != null) { try { is.close(); } catch (IOException e) { logger.error("Unable to close input stream", e); } } } Element rootElement = doc.getDocumentElement(); NodeList children = rootElement.getChildNodes(); int childSize = children.getLength(); for (int i = 0; i < childSize; i++) { Node childNode = children.item(i); if (childNode instanceof Element) { Element child = (Element) childNode; final String nodeName = child.getNodeName(); if (nodeName.equals("proxy")) { loadProxyConfig(child, config); } else if (nodeName.equals("connectionManagerList")) { loadConnectionManagers(child, config); } else if (nodeName.equals("dbServerLoader")) { loadDbServerLoader(rootElement, config); } else if (nodeName.equals("queryRouter")) { loadQueryRouter(rootElement, config); } } } if (logger.isInfoEnabled()) { logger.info("Loaded Amoeba Proxy configuration from: " + fileName); } return config; } private void loadQueryRouter(Element current, ProxyServerConfig config) { BeanObjectEntityConfig queryRouter = DocumentUtil.loadBeanConfig(DocumentUtil.getTheOnlyElement(current, "queryRouter")); config.setQueryRouterConfig(queryRouter); } private void loadDbServerLoader(Element current, ProxyServerConfig config) { BeanObjectEntityConfig dbserverLoader = DocumentUtil.loadBeanConfig(DocumentUtil.getTheOnlyElement(current, "dbServerLoader")); DBServerConfigLoader loader = (DBServerConfigLoader)dbserverLoader.createBeanObject(true, this.getConnectionManagerList()); config.putAllServers(loader.loadConfig()); } private void loadConnectionManagers(Element current, ProxyServerConfig config) { NodeList children = current.getChildNodes(); int childSize = children.getLength(); for (int i = 0; i < childSize; i++) { Node childNode = children.item(i); if (childNode instanceof Element) { Element child = (Element) childNode; BeanObjectEntityConfig managerConfig = DocumentUtil.loadBeanConfig(child); if (StringUtil.isEmpty(managerConfig.getClassName())) { managerConfig.setClassName(getDefaultServerConnectionManagerClassName()); } config.addManager(managerConfig.getName(), managerConfig); } } //create bean and init for (Map.Entry<String, BeanObjectEntityConfig> entry : config.getManagers().entrySet()) { BeanObjectEntityConfig beanObjectEntityConfig = entry.getValue(); try { ConnectionManager manager = (ConnectionManager) createBeanObjectEntity(beanObjectEntityConfig,true); manager.setName(entry.getKey()); conMgrMap.put(manager.getName(), manager); } catch (Exception e) { throw new ConfigurationException("manager instance error", e); } } } private void loadProxyConfig(Element current, ProxyServerConfig config) { NodeList children = current.getChildNodes(); int childSize = children.getLength(); Map<String, Object> map = new HashMap<String, Object>(); for (int i = 0; i < childSize; i++) { Node childNode = children.item(i); if (childNode instanceof Element) { Element child = (Element) childNode; final String nodeName = child.getNodeName(); if (nodeName.equals("property")) { String key = child.getAttribute("name"); String value = child.getTextContent(); map.put(key, value); }else if(nodeName.equals("service")){ BeanObjectEntityConfig server = DocumentUtil.loadBeanConfig(child); config.addServerConfig(server); }else if(nodeName.equals("runtime")){ BeanObjectEntityConfig runtime = DocumentUtil.loadBeanConfig(child); config.setRuntimeConfig(runtime); } } } ParameterMapping.mappingObject(config, map,null); } public void appendReport(StringBuilder buffer, long now, long sinceLast, boolean reset, Level level) { for (Map.Entry<String, ObjectPool> entry : getPoolMap().entrySet()) { ObjectPool pool = entry.getValue(); String poolName = entry.getKey(); buffer.append("* Server pool=").append(poolName == null ? "default pool" : poolName).append("\n").append(" - pool active Size=").append(pool.getNumActive()); buffer.append(", pool Idle size=").append(pool.getNumIdle()).append("\n"); } } static class CloseObjectPoolHeartbeatDelayed extends HeartbeatDelayed{ private ObjectPool pool; public CloseObjectPoolHeartbeatDelayed(long nsTime, TimeUnit timeUnit,ObjectPool pool) { super(nsTime, timeUnit); this.pool = pool; } @Override public Status doCheck() { if(pool.getNumActive() == 0){ return Status.VALID; } return null; } public boolean isCycle(){ return false; } public void cancel(){ try { this.pool.close(); } catch (Exception e) { } } public boolean equals(Object obj) { if(obj instanceof CloseObjectPoolHeartbeatDelayed){ CloseObjectPoolHeartbeatDelayed other = (CloseObjectPoolHeartbeatDelayed)obj; return other.pool == this.pool && this.getClass() == obj.getClass(); }else{ return false; } } public int hashCode(){ return pool == null?this.getClass().hashCode():this.getClass().hashCode() + pool.hashCode(); } @Override public String getName() { return "closing Pool="+pool.getName(); } } private ObjectPool createObjectPool(DBServerConfig config) throws ConfigurationException{ ObjectPool pool = null; try { BeanObjectEntityConfig poolConfig = config.getPoolConfig(); pool = (ObjectPool) createBeanObjectEntity(poolConfig,true); pool.setName(StringUtil.isEmpty(poolConfig.getName())?config.getName():poolConfig.getName()); if (config.getFactoryConfig() != null) { PoolableObjectFactory factory = (PoolableObjectFactory) createBeanObjectEntity(config.getFactoryConfig(),true); pool.setFactory(factory); } } catch (Exception e) { throw new ConfigurationException("createBean error", e); } if (pool instanceof MultipleLoadBalanceObjectPool) { MultipleLoadBalanceObjectPool multiPool = (MultipleLoadBalanceObjectPool) pool; multiPool.initAllPools(); } else { PoolableObject object = null; try { object = (PoolableObject) pool.borrowObject(); } catch (Exception e) { logger.error("init pool error!", e); throw new ConfigurationException("init pool error!", e); } finally { if (object != null) { try { pool.returnObject(object); } catch (Exception e) { logger.error("return init pools error", e); throw new ConfigurationException("return init pools error", e); } } } } return pool; } /** * update dbServerConfig * @param sourceConfig * @param tryUpdate * @throws ConfigurationException */ public void updateDBServer(DBServerConfig sourceConfig,boolean tryUpdate) throws ConfigurationException { boolean abstractive = sourceConfig.getAbstractive(); if (sourceConfig == null || StringUtil.isEmpty(sourceConfig.getName())) { throw new ConfigurationException("config or config's name cannot be null"); } if(tryUpdate){ //try to create ObjectPool with this sourceConfig if(!abstractive){ DBServerConfig config = (DBServerConfig)sourceConfig.clone(); if (sourceConfig.getParent() != null) { DBServerConfig parent = this.getConfig().getDbServers().get(config.getParent()); if (parent == null) { throw new ConfigurationException("parent config withe name=" + config.getParent() + " not found"); } this.inheritDBServerConfig(parent, config); } ObjectPool pool = createObjectPool(config); try { pool.close(); } catch (Exception e) { } }else{ Map<String,DBServerConfig> dbServerConfigs = this.getConfig().getDbServers(); for(Map.Entry<String,DBServerConfig> entry : dbServerConfigs.entrySet()){ if(StringUtil.equals(entry.getValue().getParent(),sourceConfig.getName())){ if(!entry.getValue().getAbstractive()){ DBServerConfig child = (DBServerConfig)entry.getValue().clone(); this.inheritDBServerConfig(sourceConfig, child); ObjectPool pool = createObjectPool(child); try { pool.close(); } catch (Exception e) { } break; } } } } }else{ /** * close old objectPool * if this configuration is abstractive then close all children's objectPools * else the old ObjectPool will be closed * */ this.getConfig().addServer(sourceConfig.getName(),sourceConfig); if (!abstractive) { DBServerConfig config = (DBServerConfig)sourceConfig.clone(); if (sourceConfig.getParent() != null) { DBServerConfig parent = this.getConfig().getDbServers().get(sourceConfig.getParent()); if (parent == null) { throw new ConfigurationException("parent config withe name=" + sourceConfig.getParent() + " not found"); } this.inheritDBServerConfig(parent, config); } ObjectPool pool = createObjectPool(config); //close old ObjectPool ObjectPool oldObjectPool = this.getPoolMap().get(config.getName()); this.getPoolMap().put(config.getName(), pool); this.notifyAllContextChangedListener(); if(oldObjectPool != null){ //MultipleLoadBalanceObjectPool not to be closed; if(!(oldObjectPool instanceof MultipleLoadBalanceObjectPool)){ CloseObjectPoolHeartbeatDelayed delay = new CloseObjectPoolHeartbeatDelayed(5,TimeUnit.SECONDS,oldObjectPool); HeartbeatManager.addHeartbeat(delay); } } }else{ //close all children's ObjectPools Map<String,DBServerConfig> dbServerConfigs = this.getConfig().getDbServers(); for(Map.Entry<String,DBServerConfig> entry : dbServerConfigs.entrySet()){ if(StringUtil.equals(entry.getValue().getParent(),sourceConfig.getName())){ if(!entry.getValue().getAbstractive()){ DBServerConfig child = (DBServerConfig)entry.getValue().clone(); this.inheritDBServerConfig(sourceConfig, child); ObjectPool pool = createObjectPool(child); //close old ObjectPool ObjectPool oldObjectPool = this.getPoolMap().get(child.getName()); this.getPoolMap().put(child.getName(), pool); this.notifyAllContextChangedListener(); if(oldObjectPool != null){ //MultipleLoadBalanceObjectPool not to be closed; if(!(oldObjectPool instanceof MultipleLoadBalanceObjectPool)){ CloseObjectPoolHeartbeatDelayed delay = new CloseObjectPoolHeartbeatDelayed(5,TimeUnit.SECONDS,oldObjectPool); HeartbeatManager.addHeartbeat(delay); } } } } } this.getConfig().addServer(sourceConfig.getName(),sourceConfig); } } } }