/* * This software is licensed under the Apache License, Version 2.0 * (the "License") agreement; 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.moneta.config; import java.io.FileInputStream; import java.io.InputStream; import java.sql.Connection; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TreeSet; import org.apache.commons.configuration.ConfigurationException; import org.apache.commons.configuration.XMLConfiguration; import org.apache.commons.dbcp2.PoolableConnection; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.Validate; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.pool2.ObjectPool; import org.apache.commons.pool2.impl.GenericObjectPool; import org.moneta.error.MonetaException; import org.moneta.healthcheck.DbcpConnectionPoolHealthCheck; import org.moneta.types.topic.Dialect; import org.moneta.types.topic.MonetaDataSource; import org.moneta.types.topic.Topic; import org.moneta.types.topic.TopicKeyField; import com.codahale.metrics.health.HealthCheck; import com.codahale.metrics.health.jvm.ThreadDeadlockHealthCheck; /** * Utility class to find and establish Moneta application configuration * @author D. Ashmore * */ public class MonetaConfiguration { public static final String MONETA_CONFIGURATION_PROPERTY = "moneta.configuration"; public static final String DEFAULT_CONFIGURATION_FILE_NAME="moneta.xml"; private final Map<String,MonetaDataSource> dataSourceMap = new HashMap<String,MonetaDataSource>(); private final Map<String,ObjectPool<PoolableConnection>> connectionPoolMap = new HashMap<String,ObjectPool<PoolableConnection>>(); private final Map<String,Topic> topicMap = new HashMap<String,Topic>(); private final Map<String,Topic> pluralNameMap = new HashMap<String,Topic>(); private boolean initRun = false; private String[] ignoredContextPathNodes=null; private final Map<String,HealthCheck> healthChecks = new HashMap<String,HealthCheck>(); public MonetaConfiguration() { init(findConfiguration()); } public MonetaConfiguration(InputStream configurationStream) { init(loadConfigurationFromStream(configurationStream)); } protected static final XMLConfiguration findConfiguration() { String configFileName = System.getProperty(MONETA_CONFIGURATION_PROPERTY); XMLConfiguration config = null; if ( !StringUtils.isEmpty(configFileName)) { return loadConfigurationFromFile(configFileName); } InputStream configurationStream = MonetaConfiguration.class.getClassLoader().getResourceAsStream(DEFAULT_CONFIGURATION_FILE_NAME); if (configurationStream == null) { throw new MonetaException("Moneta configuration not found"); } config = loadConfigurationFromStream(configurationStream); return config; } protected static final XMLConfiguration loadConfigurationFromFile(String configFileName) { try { return loadConfigurationFromStream(new FileInputStream(configFileName)); } catch (Exception e) { throw new MonetaException("Moneta configuration file not loaded", e) .addContextValue("configFileName", configFileName); } } protected static final XMLConfiguration loadConfigurationFromStream( InputStream configurationStream) { XMLConfiguration config = new XMLConfiguration(); try { config.load(configurationStream); } catch (ConfigurationException e) { throw new MonetaException("Moneta configuration file not loaded from classpath", e) .addContextValue("configFileName", DEFAULT_CONFIGURATION_FILE_NAME); } finally { IOUtils.closeQuietly(configurationStream); } return config; } /** * Will initialize Moneta given a configuration. This <b>must</b> be executed before use. * @param config XML configuration */ protected final void init(XMLConfiguration config) { initDataSources(config); initTopics(config); healthChecks.put("Deadlock", new ThreadDeadlockHealthCheck()); initRun = true; } protected void initDataSources(XMLConfiguration config) { int nbrDataSources = 0; Object temp = config.getList("DataSources.DataSource[@name]"); if (temp instanceof Collection) { nbrDataSources = ((Collection)temp).size(); } MonetaDataSource dataSourceType; String driverClassName; String dialectName; Class driverClass; for (int i = 0; i < nbrDataSources; i++) { dataSourceType = new MonetaDataSource(); dataSourceType.setDataSourceName(config.getString("DataSources.DataSource(" + i + ")[@name]")); dataSourceType.setConnectionUrl(config.getString("DataSources.DataSource(" + i + ")[@url]")); driverClassName = config.getString("DataSources.DataSource(" + i + ")[@driver]"); dialectName = config.getString("DataSources.DataSource(" + i + ")[@dialect]"); Validate.notEmpty(dataSourceType.getDataSourceName(), "Null or blank DataSources.DataSource.name not allowed"); Validate.notEmpty(dataSourceType.getConnectionUrl(), "Null or blank DataSources.DataSource.url not allowed"); Validate.notEmpty(driverClassName, "Null or blank DataSources.DataSource.driver not allowed"); try { driverClass = Class.forName(driverClassName); } catch (ClassNotFoundException e) { throw new MonetaException("Data source JDBC driver not found in classpath", e) .addContextValue("DataSources.DataSource.driver", driverClassName) .addContextValue("Data Source offset", i); } dataSourceType.setDriver( driverClass); Dialect dialect; String localDialectName; if (dialectName != null) { localDialectName = dialectName.toUpperCase(); try {dialect = Dialect.valueOf(localDialectName);} catch (Exception e) { throw new MonetaException("Dialect not supported", e).addContextValue("dialectName", dialectName); } dataSourceType.setDialect(dialect); } GenericObjectPool<PoolableConnection> connectionPool = (GenericObjectPool<PoolableConnection>) ConnectionPoolFactory.createConnectionPool(dataSourceType); healthChecks.put("Data source "+dataSourceType.getDataSourceName(), new DbcpConnectionPoolHealthCheck(connectionPool, dataSourceType.getDataSourceName())); connectionPoolMap.put(dataSourceType.getDataSourceName(), connectionPool); dataSourceMap.put(dataSourceType.getDataSourceName(), dataSourceType); } } protected void initTopics(XMLConfiguration config) { int nbrTopics = 0; Object temp = config.getList("Topics.Topic[@name]"); if (temp instanceof Collection) { nbrTopics = ((Collection)temp).size(); } Topic topic; String readOnlyStr; for (int i = 0; i < nbrTopics; i++) { topic = new Topic(); gatherTopicAttributes(config, topic, i); gatherAliasAttributes(config, topic); gatherKeyFields(config, topic); validateTopic(topic); topicMap.put(topic.getTopicName(), topic); pluralNameMap.put(topic.getPluralName(), topic); } Validate.isTrue(topicMap.size() > 0, "No Topics configured."); } protected void gatherAliasAttributes(XMLConfiguration config, Topic topic) { int nbrAliases = 0; Object temp = config.getList("Topics.Topic.Alias[@name]"); if (temp instanceof Collection) { nbrAliases = ((Collection)temp).size(); } String name, column; for (int i = 0; i < nbrAliases; i++) { name=config.getString("Topics.Topic.Alias(" + i + ")[@name]"); column=config.getString("Topics.Topic.Alias(" + i + ")[@column]"); if (StringUtils.isEmpty(name) || StringUtils.isEmpty(column)) { throw new MonetaException("Topic Alias fields must have both name and column specified") .addContextValue("topic", topic.getTopicName()) .addContextValue("name", name) .addContextValue("column", column); } topic.getAliasMap().put(column, name); } } protected void gatherKeyFields(XMLConfiguration config, Topic topic) { int nbrKeyFields = 0; Object temp = config.getList("Topics.Topic.PrimaryKey.Field[@name]"); if (temp instanceof Collection) { nbrKeyFields = ((Collection)temp).size(); } String name, typeStr; TopicKeyField.DataType dataType; TopicKeyField keyField; for (int i = 0; i < nbrKeyFields; i++) { name=config.getString("Topics.Topic.PrimaryKey.Field(" + i + ")[@name]"); typeStr=config.getString("Topics.Topic.PrimaryKey.Field(" + i + ")[@type]"); if (StringUtils.isEmpty(name) || StringUtils.isEmpty(typeStr)) { throw new MonetaException("Topic Primary Key Fields fields must have both name and type specified") .addContextValue("topic", topic.getTopicName()) .addContextValue("name", name) .addContextValue("type", typeStr); } try {dataType = TopicKeyField.DataType.valueOf(typeStr.toUpperCase());} catch (Exception e) { throw new MonetaException("Datatype not supported", e) .addContextValue("topic", topic.getTopicName()) .addContextValue("key field", name) .addContextValue("dataType", typeStr); } keyField = new TopicKeyField(); topic.getKeyFieldList().add(keyField); keyField.setColumnName(name); keyField.setDataType(dataType); } } protected void gatherTopicAttributes(XMLConfiguration config, Topic topic, int i) { String readOnlyStr; topic.setTopicName(config.getString("Topics.Topic(" + i + ")[@name]")); topic.setPluralName(config.getString("Topics.Topic(" + i + ")[@pluralName]")); topic.setDataSourceName(config.getString("Topics.Topic(" + i + ")[@dataSource]")); topic.setSchemaName(config.getString("Topics.Topic(" + i + ")[@schema]")); topic.setCatalogName(config.getString("Topics.Topic(" + i + ")[@catalog]")); topic.setTableName(config.getString("Topics.Topic(" + i + ")[@table]")); readOnlyStr = config.getString("Topics.Topic(" + i + ")[@readOnly]"); Boolean bValue = BooleanUtils.toBooleanObject(readOnlyStr); if (bValue != null) { topic.setReadOnly(bValue); } } protected void validateTopic(Topic topic) { Validate.notEmpty(topic.getTopicName(), "Null or blank Topics.Topic.name not allowed"); Validate.notEmpty(topic.getPluralName(), "Null or blank Topics.Topic.pluralName not allowed"); Validate.notEmpty(topic.getDataSourceName(), "Null or blank Topics.Topic.dataSource not allowed. topic="+topic.getTopicName()); Validate.notEmpty(topic.getTableName(), "Null or blank Topics.Topic.table not allowed. topic="+topic.getTopicName()); Validate.notNull(topic.getReadOnly(), "Null or blank Topics.Topic.readOnly not allowed. topic="+topic.getTopicName()); if (StringUtils.isEmpty(topic.getSchemaName())) { Validate.isTrue(topic.getCatalogName()==null, "Null or blank Topics.Topic.catalog not allowed when schema is provided. topic="+topic.getTopicName()); } if ( !connectionPoolMap.containsKey(topic.getDataSourceName())) { throw new MonetaException("Topic references non-existent data source") .addContextValue("topic", topic.getTopicName()) .addContextValue("dataSource", topic.getDataSourceName()); } } /** * Will return a dbConnection for a given information topic. * @param sourceName Data source name * @return topicDbConnection connection topic */ public Connection getConnection(String sourceName) { Validate.notEmpty(sourceName, "Null or blank sourceName not allowed"); Validate.isTrue(this.initRun, "Moneta not properly initialized."); ObjectPool connectionPool = connectionPoolMap.get(sourceName); if (connectionPool == null) { throw new MonetaException("Data Source Not Found") .addContextValue("sourceName", sourceName); } try { return (Connection)connectionPool.borrowObject(); } catch (Exception e) { throw new MonetaException("Error creating JDBC connection") .addContextValue("sourceName", sourceName); } } public Topic getTopic(String topicName) { Validate.notEmpty(topicName, "Null or blank topicName not allowed"); Validate.isTrue(this.initRun, "Moneta not properly initialized."); return topicMap.get(topicName); } public Topic findByPlural(String pluralTopicName) { Validate.notEmpty(pluralTopicName, "Null or blank pluralTopicName not allowed"); Validate.isTrue(this.initRun, "Moneta not properly initialized."); return pluralNameMap.get(pluralTopicName); } public List<Topic> getTopicList() { return new ArrayList<Topic>(new TreeSet<Topic>(topicMap.values())); } public MonetaDataSource getMonetaDataSource(String sourceName) { Validate.notEmpty(sourceName, "Null or blank sourceName not allowed"); Validate.isTrue(this.initRun, "Moneta not properly initialized."); return dataSourceMap.get(sourceName); } public String[] getIgnoredContextPathNodes() { return ignoredContextPathNodes; } public void setIgnoredContextPathNodes(String[] ignoredContextPathNodes) { this.ignoredContextPathNodes = ignoredContextPathNodes; } public Map<String, HealthCheck> getHealthChecks() { return healthChecks; } }