/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* 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 Lesser General Public License for more details.
*
* Copyright (c) 2007 - 2009 Pentaho Corporation.. All rights reserved.
*/
package org.pentaho.reporting.ui.datasources.jdbc.connection;
import java.util.Properties;
import java.util.TreeMap;
import java.util.Map;
import java.util.Iterator;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.reporting.libraries.base.util.StringUtils;
/**
* Manages the list of local database connections for this application.
* <p/>
* The list of local connections is contained in the user preferences for this user. The structure of the preferences is
* as follows: <ul> <li>One node per connection - the name of the node is the JNDI Connection name <li>Under each node,
* the following set of string keys and values which define each connection <ul> <li>driver - the JDBC driver class
* <li>url - the URL of the database <li>username - the username used to connect to the database <li>password - the
* password used to connect to the database </ul> </ul>
*/
public class JdbcConnectionDefinitionManager
{
// Logger
private static final Log log = LogFactory.getLog(JdbcConnectionDefinitionManager.class);
// The default connection (used if no other connections can be loaded)
private static final JdbcConnectionDefinition SAMPLE_DATA_JNDI_SOURCE =
new JndiConnectionDefinition("SampleData", "SampleData", "Hypersonic", null, null);
private static final JdbcConnectionDefinition SAMPLE_DATA_DRIVER_SOURCE =
new DriverConnectionDefinition("SampleData (Hypersonic)", "org.hsqldb.jdbcDriver",
"jdbc:hsqldb:hsql://localhost:9001/sampledata", "pentaho_user", "password");
private static final JdbcConnectionDefinition SAMPLE_DATA_MEMORY_SOURCE =
new DriverConnectionDefinition("SampleData (Memory)", "org.hsqldb.jdbcDriver",
"jdbc:hsqldb:mem:SampleData", "pentaho_user", "password");
private static final JdbcConnectionDefinition LOCAL_SAMPLE_DATA_DRIVER_SOURCE =
new DriverConnectionDefinition("SampleData (Local)", "org.hsqldb.jdbcDriver",
"jdbc:hsqldb:./resources/sampledata/sampledata", "pentaho_user", "password");
private static final JdbcConnectionDefinition MYSQL_SAMPLE_DATA_DRIVER_SOURCE =
new DriverConnectionDefinition("SampleData (MySQL)", "com.mysql.jdbc.Driver",
"jdbc:mysql://localhost:3306/sampledata", "pentaho_user", "password");
// The location in the user preferences tree for the JNDI datasource settings
private static final String DATASOURCE_PREFERENCES_NODE = "org/pentaho/reporting/ui/datasources/jdbc/Settings";
// The current set of Sources
private TreeMap<String,JdbcConnectionDefinition> connectionDefinitions = new TreeMap<String,JdbcConnectionDefinition>();
/**
* The reference to the preferences object for the datasource settings
*/
private Preferences userPreferences;
private static final String TYPE_KEY = "type";
// Constants use in the storage of the field information
private static final String DRIVER_KEY = "driver";
private static final String URL_KEY = "url";
private static final String USERNAME_KEY = "username";
private static final String PASSWORD_KEY = "password";
private static final String HOSTNAME_KEY = "hostname";
private static final String PORT_KEY = "port";
private static final String DATABASE_TYPE_KEY = "database_type";
private static final String DATABASE_NAME_KEY = "database_name";
private static final String JNDI_LOCATION = "jndi-location";
/**
* Initializes the instance by loading the database connection information from the configuration storage.
*/
public JdbcConnectionDefinitionManager()
{
this(DATASOURCE_PREFERENCES_NODE);
}
public JdbcConnectionDefinitionManager(final String node)
{
// Retrieve the system preferences for the datasource
userPreferences = Preferences.userRoot().node(node);
// Load the list of JNDI Sources
try
{
final String[] childNodeNames = userPreferences.childrenNames();
for (int i = 0; i < childNodeNames.length; i++)
{
final String name = childNodeNames[i];
final Preferences p = userPreferences.node(name);
final String type = p.get("type", null);
if (type == null)
{
p.removeNode();
}
else if (type.equals("local"))
{
final Properties props = new Properties();
if (p.nodeExists("properties"))
{
final Preferences preferences = p.node("properties");
final String[] strings = preferences.keys();
for (int j = 0; j < strings.length; j++)
{
final String string = strings[j];
final String value = preferences.get(string, null);
if (value != null)
{
props.setProperty(string, value);
}
else
{
props.remove(string);
}
}
}
final DriverConnectionDefinition driverConnection = new DriverConnectionDefinition(
name,
p.get(DRIVER_KEY, null),
p.get(URL_KEY, null),
p.get(USERNAME_KEY, null),
p.get(PASSWORD_KEY, null),
p.get(HOSTNAME_KEY, null),
p.get(DATABASE_NAME_KEY, null),
p.get(DATABASE_TYPE_KEY, null),
p.get(PORT_KEY, null),
props
);
connectionDefinitions.put(name, driverConnection);
}
else if (type.equals("jndi"))
{
final JndiConnectionDefinition connectionDefinition = new JndiConnectionDefinition
(name, p.get(JNDI_LOCATION, null), p.get(DATABASE_TYPE_KEY, null),
p.get(USERNAME_KEY, null), p.get(PASSWORD_KEY, null));
connectionDefinitions.put(name, connectionDefinition);
}
else
{
p.removeNode();
}
}
}
catch (BackingStoreException e)
{
// The system preferences system is not working - log this as a message and use defaults
log.warn("Could not access the user prefererences while loading the " +
"JNDI connection information - using default JNDI connection entries", e);
}
catch (final Exception e)
{
log.warn("Configuration information was invalid.", e);
}
// If the connectionDefinitions is empty, add any default entries
if (connectionDefinitions.isEmpty() && DATASOURCE_PREFERENCES_NODE.equals(node))
{
if (userPreferences.getBoolean("sample-data-created", false) == true)
{
// only create the sample connections once, if we work on a totally fresh config.
return;
}
updateSourceList(SAMPLE_DATA_JNDI_SOURCE);
updateSourceList(SAMPLE_DATA_DRIVER_SOURCE);
updateSourceList(SAMPLE_DATA_MEMORY_SOURCE);
updateSourceList(LOCAL_SAMPLE_DATA_DRIVER_SOURCE);
updateSourceList(MYSQL_SAMPLE_DATA_DRIVER_SOURCE);
userPreferences.putBoolean("sample-data-created", true);
try
{
userPreferences.flush();
}
catch (BackingStoreException e)
{
// ignored ..
}
}
}
/**
* Returns a copy of the current list of JNDI sources
*/
public JdbcConnectionDefinition[] getSources()
{
return connectionDefinitions.values().toArray
(new JdbcConnectionDefinition[connectionDefinitions.size()]);
}
/**
* Removes the specified connection-definition from the list of connections.
*
* @param name the name of the JNDI source to remove from the list
*/
public void removeSource(final String name)
{
// Make sure the name provided is not null
if (StringUtils.isEmpty(name))
{
throw new IllegalArgumentException("The provided name is invalid");
}
// If the source is in our list, remove it
connectionDefinitions.remove(name);
// Remove the entry from the user preferences
try
{
final Preferences node = userPreferences.node(name);
if (node != null)
{
node.removeNode();
userPreferences.flush();
}
}
catch (BackingStoreException e)
{
log.error("Could not remove JNDI connection entry [" + name + ']', e);
}
}
/**
* Adds or updates the source list with the specified source entry. Delagates to JNDI or JDBC method
*
* @param source the entry to add/update in the list
* @throws IllegalArgumentException indicates the source is <code>null</code>
*/
public boolean updateSourceList(final JdbcConnectionDefinition source)
{
if (source == null)
{
throw new IllegalArgumentException("The provided source is null");
}
if (source instanceof DriverConnectionDefinition)
{
return updateSourceList((DriverConnectionDefinition) source);
}
if (source instanceof JndiConnectionDefinition)
{
return updateSourceList((JndiConnectionDefinition) source);
}
else
{
throw new IllegalArgumentException("The provided source is not a supported type");
}
}
/**
* Adds or updates the source list with the specified source entry. If the entry exists (has the same name), the new
* entry will replace the old entry. If the enrty does not already exist, the new entry will be added to the list.
* <br> Since the definition of the ConnectionDefintion ensures that it will be valid, no testing will be performed on
* the contents of the ConnectionDefintion.
*
* @param source the entry to add/update in the list
* @throws IllegalArgumentException indicates the source is <code>null</code>
*/
private boolean updateSourceList(final DriverConnectionDefinition source) throws IllegalArgumentException
{
if (source == null)
{
throw new IllegalArgumentException("The provided source is null");
}
// Update the node in the list
final boolean updateExisting =
(connectionDefinitions.put(source.getName(), source) != null);
// Update the information in the preferences
try
{
final Preferences node = userPreferences.node(source.getName());
put(node, TYPE_KEY, "local");
put(node, DRIVER_KEY, source.getDriverClass());
put(node, URL_KEY, source.getConnectionString());
put(node, USERNAME_KEY, source.getUsername());
put(node, PASSWORD_KEY, source.getPassword());
put(node, HOSTNAME_KEY, source.getHostName());
put(node, PORT_KEY, source.getPort());
put(node, DATABASE_TYPE_KEY, source.getDatabaseType());
put(node, DATABASE_NAME_KEY, source.getDatabaseName());
final Preferences preferences = node.node("properties");
final Properties properties = source.getProperties();
final Iterator entryIterator = properties.entrySet().iterator();
while (entryIterator.hasNext())
{
final Map.Entry entry = (Map.Entry) entryIterator.next();
put(preferences, String.valueOf(entry.getKey()), String.valueOf(entry.getValue()));
}
node.flush();
}
catch (BackingStoreException e)
{
log.error("Could not add/update connection entry [" + source.getName() + ']', e);
}
return updateExisting;
}
private static void put(final Preferences node, final String key, final String value)
{
if (value == null)
{
node.remove(key);
}
else
{
node.put(key, value);
}
}
/**
* Adds or updates the source list with the specified source entry. If the entry exists (has the same name), the new
* entry will replace the old entry. If the enrty does not already exist, the new entry will be added to the list.
* <br> Since the definition of the ConnectionDefintion ensures that it will be valid, no testing will be performed on
* the contents of the ConnectionDefintion.
*
* @param source the entry to add/update in the list
* @throws IllegalArgumentException indicates the source is <code>null</code>
*/
private boolean updateSourceList(final JndiConnectionDefinition source) throws IllegalArgumentException
{
if (source == null)
{
throw new IllegalArgumentException("The provided source is null");
}
// Update the node in the list
final boolean updateExisting =
(connectionDefinitions.put(source.getName(), source) != null);
// Update the information in the preferences
try
{
final Preferences node = userPreferences.node(source.getName());
put(node, TYPE_KEY, "jndi");
put(node, JNDI_LOCATION, source.getJndiName());
put(node, USERNAME_KEY, source.getUsername());
put(node, PASSWORD_KEY, source.getPassword());
put(node, DATABASE_TYPE_KEY, source.getDatabaseType());
node.flush();
}
catch (BackingStoreException e)
{
log.error("Could not add/update connection entry [" + source.getName() + ']', e);
}
return updateExisting;
}
}