/**
* <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);
}
}
}
}