/* Copyright 2013 Stephen Stacha
*
* 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 com.innavace.ds.config;
import com.innavace.ds.wrapper.OrderedParameterWrapper;
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;
import javax.naming.NamingException;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.*;
/**
* User: sstacha
* Date: Mar 5, 2013
* Encapsulates all functions for getting and writing configurations
*/
public class ConfigurationHandler
{
public static Logger log = Logger.getLogger(ConfigurationHandler.class);
private static Map<String, Configuration> configurationsMap = new LinkedHashMap<String, Configuration>();
private static Map<String, String> registryMap = new LinkedHashMap<String, String>();
public static synchronized void init() throws NamingException, SQLException {
initConfigurations();
log.info("data configurations initialized");
if (!hasConfigurations())
log.warn("no configurations crated: This shouldn't happen.");
initSystemRegistry();
if (registryMap.size() == 0)
log.warn("no registry entries loaded.");
log.info("system registry initialized");
}
public static boolean hasConfigurations() { return configurationsMap.size() > 0; }
public static synchronized Collection<Configuration> getConfigurations() { return configurationsMap.values(); }
public static Configuration getConfiguration(String path) { return configurationsMap.get(path); }
public static synchronized Set<Map.Entry<String, String>> getRegistryEntries() { return registryMap.entrySet(); }
private static synchronized void initConfigurations() throws NamingException, SQLException {
Connection con = null;
Statement stmt = null;
ResultSet rs = null;
// attempt to read in configurations from the datasource;
if (configurationsMap.size() > 0)
configurationsMap.clear();
try
{
con = ConnectionHandler.getConnection("default");
if (con == null)
throw new SQLException("Unable to obtain default connection; aborting configuration initialization.");
stmt = con.createStatement();
Configuration configuration;
try {rs = stmt.executeQuery("SELECT * FROM CONFIGURATIONS");}
catch (SQLException sqlex)
{
// if we have an exception reading the configurations table lets try to create it and the default data
// and read it again
createDatabaseSchema(stmt);
try {con.commit();}
catch (Exception ex) {log.debug("attempted commit but failed: " + ex);}
rs = stmt.executeQuery("SELECT * FROM CONFIGURATIONS");
}
while(rs.next())
{
configuration = new Configuration();
//configuration.id = rs.getLong("CONFIGURATION_ID");
configuration.connectionName = rs.getString("CONNECTION_NAME");
if (configuration.connectionName == null || configuration.connectionName.length() == 0)
configuration.connectionName = "default";
configuration.path = rs.getString("PATH");
if (configuration.path != null && configuration.path.length() > 0 && (!configuration.path.startsWith("/")))
configuration.path = "/" + configuration.path;
configuration.queryStatement = rs.getString("QUERY_STATEMENT");
configuration.updateStatement = rs.getString("UPDATE_STATEMENT");
configuration.insertStatement = rs.getString("INSERT_STATEMENT");
configuration.deleteStatement = rs.getString("DELETE_STATEMENT");
// configuration.cached = (rs.getString("CACHED") != null && rs.getString("CACHED").equalsIgnoreCase("true"));
configuration.keywords = rs.getString("KEYWORDS");
// todo - determine if we want to try to connect to a table to determine if it exists
// NOTE: if not exists try to create it (will error if we don't have read permissions; test
configurationsMap.put(configuration.path, configuration);
}
}
finally
{
if (rs != null) {
try {rs.close();}
catch (Exception ex) {log.warn("Exception attempting to close non null result set in configuration handler.init(). May have a memory leak!: ", ex);}
}
if (stmt != null) {
try {stmt.close();}
catch (Exception ex) {log.warn("Exception attempting to close non null statement in configuration handler.init(). May have a memory leak!: ", ex);}
}
if (con != null) {
try {con.close();}
catch (Exception ex) {log.warn("Exception attempting to close non null connection in configuration handler.init(). May have a memory leak!: ", ex);}
}
}
}
public static synchronized void createDatabaseSchema(Statement stmt)
{
String sql = "CREATE TABLE CONFIGURATIONS (" + // CONFIGURATION_ID BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY, " +
"CONNECTION_NAME VARCHAR(50), PATH VARCHAR(1000) PRIMARY KEY, " +
"QUERY_STATEMENT VARCHAR(2000) NOT NULL, INSERT_STATEMENT VARCHAR(1000), UPDATE_STATEMENT VARCHAR(1000), DELETE_STATEMENT VARCHAR(1000), KEYWORDS VARCHAR(2000))";
try {stmt.execute(sql);log.info("SYSTEM TABLE [CONFIGURATIONS] CREATED");}
catch (SQLException sqlex) {log.fatal("Exception attempting to create configurations table: ", sqlex);}
log.info("created configuration table.");
// create the default data for this table
// keep it simple for now and run some DDL to create the tables and such. Later we may look at running a file
// that can be streamed so we can do automatic updates and such
sql = "INSERT INTO CONFIGURATIONS (CONNECTION_NAME, PATH, " +
"QUERY_STATEMENT, INSERT_STATEMENT, UPDATE_STATEMENT, DELETE_STATEMENT, KEYWORDS) VALUES (" +
"'default', '/configurations', 'SELECT * FROM CONFIGURATIONS', " +
"'INSERT INTO CONFIGURATIONS (CONNECTION_NAME, PATH, QUERY_STATEMENT, INSERT_STATEMENT, UPDATE_STATEMENT, DELETE_STATEMENT, KEYWORDS) " +
"VALUES (?, ?, ?, ?, ?, ?, ?)', " +
"'UPDATE CONFIGURATIONS SET CONNECTION_NAME=?, PATH=?, QUERY_STATEMENT=?, " +
"INSERT_STATEMENT=?, UPDATE_STATEMENT=?, DELETE_STATEMENT=?, KEYWORDS=? WHERE PATH=?', " +
"'DELETE FROM CONFIGURATIONS WHERE PATH=?', 'system, product:console')";
log.debug(sql);
try {stmt.execute(sql);log.info("SYSTEM [CONFIGURATIONS] DEFAULT DATA CREATED");}
catch (SQLException sqlex) {log.fatal("Exception attempting to create default configurations data: ", sqlex);}
sql = "INSERT INTO CONFIGURATIONS (CONNECTION_NAME, PATH, " +
"QUERY_STATEMENT, INSERT_STATEMENT, UPDATE_STATEMENT, DELETE_STATEMENT, KEYWORDS) VALUES (" +
"'default', '/connections', 'SELECT * FROM CONNECTIONS', " +
"'INSERT INTO CONNECTIONS (NAME, TYPE, JDBC_DRIVER, JDBC_URL, JDBC_USERNAME, JDBC_PASSWORD, JNDI_NAME, JNDI_CONTEXT, DESCRIPTION) " +
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)', " +
"'UPDATE CONNECTIONS SET NAME=?, TYPE=?, " +
"JDBC_DRIVER=?, JDBC_URL=?, JDBC_USERNAME=?, JDBC_PASSWORD=?, " +
"JNDI_NAME=?, JNDI_CONTEXT=?, DESCRIPTION=? WHERE NAME=?', " +
"'DELETE FROM CONNECTIONS WHERE NAME=?', 'system, product:console')";
log.info(sql);
try {stmt.execute(sql);log.info("SYSTEM [CONNECTIONS] DEFAULT DATA CREATED");}
catch (SQLException sqlex) {log.fatal("Exception attempting to create default configurations data for connections: ", sqlex);}
sql = "CREATE TABLE SYSREG (SYSREG_CODE VARCHAR(50) PRIMARY KEY, SYSREG_VALUE VARCHAR(255) NOT NULL)";
try {stmt.execute(sql);log.info("SYSTEM TABLE [SYSREG] CREATED");}
catch (SQLException sqlex) {log.fatal("Exception attempting to create sysreg table: ", sqlex);}
log.info("created sysreg table.");
// create the default data for this table
sql = "INSERT INTO SYSREG VALUES ('DB_VERSION', '1.0.0')";
try {stmt.execute(sql);log.info("SYSTEM [SYSREG] DEFAULT DATA CREATED");}
catch (SQLException sqlex) {log.fatal("Exception attempting to create default sysreg data: ", sqlex);}
log.info("created sysreg data.");
}
public static synchronized void clearCache() {
Collection<Configuration> configurations = configurationsMap.values();
for (Configuration configuration : configurations)
configuration.cachedResult = null;
}
private static void initSystemRegistry() throws NamingException, SQLException
{
// keep it simple; read the system registry values from the sysreg table and put them in the map
// assume we have already run the getConfigurations() which creates the default tables if not there and populates
// todo : move the default creation to method and call from here too
Connection con = ConnectionHandler.getConnection("default");
Statement stmt = null;
ResultSet rs = null;
try
{
stmt = con.createStatement();
try {rs = stmt.executeQuery("SELECT * FROM SYSREG");}
catch (SQLException sqlex)
{
// if we have an exception reading the configurations table lets try to create it and the default data
// and read it again
createDatabaseSchema(stmt);
rs = stmt.executeQuery("SELECT * FROM SYSREG");
}
String key;
String value;
while(rs.next())
{
key = rs.getString("SYSREG_CODE");
value = rs.getString("SYSREG_VALUE");
if (key == null || key.trim().equalsIgnoreCase(""))
{
log.warn("missing registry key: skipping entry");
continue;
}
registryMap.put(key, value);
}
}
finally
{
if (rs != null) {
try {rs.close();}
catch (Exception ex) {log.warn("Exception attempting to close non null result set in configuration handler.initRegistry(). May have a memory leak!: " + ex);}
}
if (stmt != null) {
try {stmt.close();}
catch (Exception ex) {log.warn("Exception attempting to close non null statement in configuration handler.initRegistry(). May have a memory leak!: " + ex);}
}
if (con != null) {
try {con.close();}
catch (Exception ex) {log.warn("Exception attempting to close non null connection in configuration handler.initRegistry(). May have a memory leak!: " + ex);}
}
}
}
public static synchronized Configuration get(String path) {
if (path == null)
return null;
return configurationsMap.get(path);
}
public static synchronized boolean hasConfiguration(String path) {
return path != null && configurationsMap.get(path) != null;
}
public static synchronized boolean updateConfiguration(Configuration configuration) throws NamingException, SQLException, IOException {
if (configuration == null || configuration.path == null || configuration.path.length() == 0)
throw new SQLException("Unable to update connection because the connection was null or the name was empty.");
log.debug("updating connection: " + configuration.path);
Configuration connectionConfiguration = ConfigurationHandler.getConfiguration("/configurations");
if (connectionConfiguration == null)
throw new SQLException("/configurations configuration not found!");
OrderedParameterWrapper parameterWrapper = new OrderedParameterWrapper(null, configuration.toQueryString(), null);
Configuration existingConfiguration = ConfigurationHandler.get(configuration.path);
connectionConfiguration.execute(parameterWrapper.getParameterMap(), "text/json", existingConfiguration != null ? "update" : "insert");
return true;
}
public static synchronized String toJSON() {
StringBuilder buffer = new StringBuilder(400);
Collection<Configuration> configurations = configurationsMap.values();
buffer.append("[");
for (Configuration configuration : configurations) {
if (buffer.length() > 1)
buffer.append(", ");
buffer.append(configuration.toJSON());
}
buffer.append("]");
return buffer.toString();
}
public static synchronized String toJSON(String key) {
Configuration configuration = configurationsMap.get(key);
if (configuration == null)
return "{}";
return configuration.toJSON();
}
public static synchronized String toXML() {return toXML(null);}
public static synchronized String toXML(String filter) {
StringBuilder buffer = new StringBuilder(400);
Collection<Configuration> configurations = configurationsMap.values();
buffer.append("<configurations>");
String fragment;
for (Configuration configuration : configurations) {
if (filter == null || filter.length() == 0 || filter.equalsIgnoreCase("all")) {
buffer.append(configuration.toXML());
}
else if (filter.startsWith("!")) {
fragment = filter.substring(1);
if (!(configuration.hasKeyword(fragment)))
buffer.append(configuration.toXML());
}
else
if (configuration.hasKeyword(filter))
buffer.append(configuration.toXML());
}
buffer.append("</configurations>");
return buffer.toString();
}
public static void main(String[] args)
{
BasicConfigurator.configure();
Connection connection = null;
Statement statement = null;
ResultSet rs = null;
try
{
ConfigurationHandler.init();
Collection<Configuration> configurations = ConfigurationHandler.getConfigurations();
for (Configuration configuration : configurations)
System.out.println(configuration);
String sql = "SELECT CONFIGURATIONS.*, DS_CONFIGURATION_CATEGORY_LIST (CONFIGURATION_ID) AS CATEGORIES FROM CONFIGURATIONS";
connection = ConnectionHandler.getConnection("default");
statement = connection.createStatement();
rs = statement.executeQuery(sql);
while (rs != null && rs.next()) {
System.out.println("configuration [" + rs.getString("PATH") + "]: " + rs.getString("CATEGORIES"));
}
if (rs != null)
rs.close();
}
catch (Exception ex) {System.out.println("Exception in main: " + ex);}
finally {
if (statement != null) {
try {
statement.close();
}
catch(SQLException sqlex) {System.out.println("Exception closing statement: " + sqlex);}
}
if (connection != null) {
try {
connection.close();
}
catch(SQLException sqlex) {System.out.println("Exception closing connection: " + sqlex);}
}
ConnectionHandler.destroy();
}
}
}