/*
* 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.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
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 final 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 ) {
this( Preferences.userRoot().node( node ), node );
}
/**
* package-local visibility for testing purposes
*/
JdbcConnectionDefinitionManager( final Preferences externalPreferences, final String node ) {
userPreferences = externalPreferences;
// 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, null );
put( node, PASSWORD_KEY, null );
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;
}
}