001    package com.sptci.rwt;
002    
003    import java.io.FileNotFoundException;
004    import java.io.Serializable;
005    
006    import java.sql.Connection;
007    
008    import java.util.Collection;
009    import java.util.Collections;
010    import java.util.TreeMap;
011    
012    import com.thoughtworks.xstream.XStream;
013    import com.sptci.util.StringUtilities;
014    
015    /**
016     * A serializable wrapper used to represent saved JDBC connections for
017     * the application.  This class will be serialised to the following file
018     * and initialised from the same file during application load:
019     *
020     * <pre>&lt;sptrwt.data.directory&gt;/connections.xml</pre>
021     *
022     * <p>&copy; Copyright 2007 <a href='http://sptci.com/' target='_new'>Sans Pareil Technologies, Inc.</a></p>
023     * @author Rakesh Vidyadharan 2007-09-24
024     * @version $Id: Connections.java 4123 2008-05-25 21:49:01Z rakesh $
025     */
026    public class Connections implements Serializable
027    {
028      /**
029       * The encoding to use to serialise and deserialise instances of this
030       * class.
031       *
032       * {@value}
033       */
034      public static final String ENCODING = "UTF-8";
035    
036      /**
037       * The name of the file to which this class will be serialised.
038       */
039      public static final String FILE_NAME = "connections.xml";
040    
041      /**
042       * The system property used to configure the location of the root directory
043       * under which persistent state information for the application is stored.
044       *
045       * {@value}
046       */
047      public static final String DIRECTORY = "sptrwt.data.directory";
048    
049      /**
050       * The {@link com.thoughtworks.xstream.XStream} instance used to serialise
051       * and deserialise instances of this class.
052       */
053      protected static final XStream xstream;
054    
055      /**
056       * Static initialiser for {@link #xstream}.
057       */
058      static
059      {
060        xstream = new XStream();
061        xstream.alias( "connections", Connections.class );
062    
063        xstream.alias( "databaseType", DatabaseType.class );
064        xstream.useAttributeFor( DatabaseType.class, "name" );
065        xstream.useAttributeFor( DatabaseType.class, "driver" );
066        xstream.useAttributeFor( DatabaseType.class, "urlPattern" );
067    
068        xstream.alias( "connectionData", ConnectionData.class );
069      }
070    
071      /**
072       * A map used to quickly look up {@link DatabaseType} instances by their
073       * {@link DatabaseType#name}.
074       */
075      private final TreeMap<String,DatabaseType> databases;
076    
077      /**
078       * The fully qualified file name to use to serialise this instance into.
079       */
080      private transient String fileName;
081    
082      /**
083       * Default constructor.  Cannot be instantiated.
084       */
085      protected Connections()
086      {
087        databases = new TreeMap<String,DatabaseType>();
088      }
089    
090      /**
091       * Create a new instance of the class for the specified user.  Saved
092       * files are stored under a directory named after the <code>user</code>
093       * under {@link #DIRECTORY}.
094       * 
095       * @see #deserialise
096       * @return The newly initialised instance of the class.
097       * @throws RuntimeException If errors are encountered while deserialising
098       *   the persistent state of this instance.
099       */
100      public static Connections getInstance( final String user )
101      {
102        final Connections connections = new Connections();
103        connections.deserialise( user );
104        return connections;
105      }
106    
107      /**
108       * Return a {@link java.sql.Connection} for the specified database type
109       * and saved with the specified unique name.
110       *
111       * @see #getConnectionParameters
112       * @see ConnectionFactory#open( ConnectionParameters )
113       * @param databaseType The name of the database engine.
114       * @param name The unique name used to identify the saved connection.
115       * @return The connection object or <code>null</code> if no saved
116       *   information can be found for the specified parameters.
117       * @throws ConnectionException If errors are encountered while initiating
118       *   the connection.
119       */
120      public Connection getConnection( final String databaseType,
121          final String name ) throws ConnectionException
122      {
123        Connection connection = null;
124        final ConnectionParameters parameters =
125          getConnectionParameters( databaseType, name );
126        if ( parameters != null )
127        {
128          connection = ConnectionFactory.open( parameters );
129        }
130    
131        return connection;
132      }
133    
134      /**
135       * Return a value object that represents all the connection information
136       * for the specified database type and saved with the specified unique
137       * name.
138       *
139       * @param databaseType The name of the database engine.
140       * @param name The unique name used to identify the saved connection.
141       * @return The appropriate value object or <code>null</code> if no
142       *   matching saved connection is found.
143       */
144      public ConnectionParameters getConnectionParameters(
145          final String databaseType, final String name )
146      {
147        ConnectionParameters parameters = null;
148        final DatabaseType type = databases.get( databaseType );
149    
150        if ( type != null )
151        {
152          final ConnectionData data = type.getConnectionData( name );
153          if ( data != null )
154          {
155            parameters = new ConnectionParameters(
156                data.getUserName(), data.getPassword(), data.getHost(),
157                data.getPort(), data.getDatabase(), type.getName(),
158                type.getUrlPattern(), type.getDriver() );
159          }
160        }
161    
162        return parameters;
163      }
164    
165      /**
166       * Add the specified connection parameters value object to the application
167       * persistent state.
168       *
169       * @see #serialise
170       * @param name The unique name to use to identify the saved connection.
171       * @param parameters The parameters to be saved to {@link #FILE_NAME}.
172       */
173      public void add( final String name, final ConnectionParameters parameters )
174      {
175        DatabaseType type = databases.get( parameters.databaseType );
176        if ( type == null )
177        {
178          type = new DatabaseType();
179          type.setName( parameters.databaseType );
180          type.setUrlPattern( parameters.urlPattern );
181          type.setDriver( parameters.driver );
182          databases.put( parameters.databaseType, type );
183        }
184    
185        final ConnectionData data = new ConnectionData();
186        data.setHost( parameters.host );
187        data.setPort( parameters.port );
188        data.setDatabase( parameters.database );
189        data.setUserName( parameters.userName );
190        data.setPassword( parameters.password );
191    
192        type.add( name, data );
193        serialise();
194      }
195    
196      /**
197       * Remove the specified connection parameters from the application
198       * persistent state.
199       *
200       * @see #serialise
201       * @param databaseType The database type under which the connection was
202       *   saved.
203       * @param name The unique name to use to identify the saved connection.
204       */
205      public void delete( final String databaseType, final String name )
206      {
207        final DatabaseType type = databases.get( databaseType );
208        if ( type != null ) type.delete( name );
209        serialise();
210      }
211    
212      /**
213       * Remove the specified database from persistent state.  This removes
214       * all saved connections for that database engine.
215       *
216       * @see #serialise
217       * @param name The name of the database engine to remove from saved state.
218       */
219      public void delete( final String name )
220      {
221        databases.remove( name );
222        serialise();
223      }
224    
225      /**
226       * Deserialise the contents of {@link #FILE_NAME} into this instance.
227       *
228       * @param user The name of the user to use to construct the full filename.
229       * @throws RuntimeException If errors are encountered while deserialising
230       *   the persistent state.  No exceptions are thrown if the file does
231       *   not exist.
232       */
233      protected void deserialise( final String user ) throws RuntimeException
234      {
235        final String separator =
236          System.getProperties().getProperty( "file.separator" );
237    
238        final StringBuilder builder = new StringBuilder( 64 );
239        builder.append( System.getProperty( DIRECTORY ) );
240        builder.append( separator );
241        builder.append( user );
242        builder.append( separator );
243        builder.append( FILE_NAME );
244        fileName = builder.toString();
245    
246        try
247        {
248          final String xml = StringUtilities.fromFile( fileName, ENCODING );
249          xstream.fromXML( xml, this );
250        }
251        catch ( FileNotFoundException fnfe ) {}
252        catch ( Throwable t )
253        {
254          throw new RuntimeException( "Error deserialising saved instance", t );
255        }
256      }
257    
258      /**
259       * Serialise this instance to the {@link #FILE_NAME}.
260       *
261       * @throws RuntimeException If errors are encountered while serialising
262       *   the instance.
263       */
264      protected void serialise() throws RuntimeException
265      {
266        try
267        {
268          final String xml = xstream.toXML( this );
269          StringUtilities.toFile( xml, fileName, ENCODING );
270        }
271        catch ( Throwable t )
272        {
273          throw new RuntimeException( t );
274        }
275      }
276      
277      /**
278       * Returns the {@link DatabaseType} identified by the {@link DatabaseType#name}
279       * parameter specified.
280       * 
281       * @param name The name to use to fetch the database type.
282       * @return The database type associated with the <code>name</code> or
283       *   <code>null</code> if no such database type exists.
284       */
285      public DatabaseType getDatabaseType( final String name )
286      {
287        return databases.get( name );
288      }
289      
290      /**
291       * Returns {@link #databases}.
292       *
293       * @return A read only view of the collection.
294       */
295      public Collection<DatabaseType> getDatabaseTypes()
296      {
297        return Collections.unmodifiableCollection( databases.values() );
298      }
299      
300      /**
301       * Returns the collection of {@link DatabaseType#name} values stored as
302       * <code>key</code> in {@link #databases}.
303       * 
304       * @return The collection of names.
305       */
306      public Collection<String> getDatabaseTypeNames()
307      {
308        return Collections.unmodifiableCollection( databases.keySet() );
309      }
310    }