001    package com.sptci.rwt;
002    
003    import java.io.FileNotFoundException;
004    import java.io.Serializable;
005    
006    import java.util.ArrayList;
007    import java.util.Collection;
008    import java.util.Collections;
009    import java.util.TreeMap;
010    
011    import com.thoughtworks.xstream.XStream;
012    
013    import com.sptci.KeyValue;
014    import com.sptci.util.StringUtilities;
015    
016    /**
017     * A serializable wrapper used to represent saved SQL queries for
018     * the application.  This class will be serialised to the following file
019     * and initialised from the same file during application load:
020     *
021     * <pre>&lt;sptrwt.data.directory&gt;/queries.xml</pre>
022     *
023     * <p>&copy; Copyright 2007 <a href='http://sptci.com/' target='_new'>Sans Pareil Technologies, Inc.</a></p>
024     * @author Rakesh Vidyadharan 2007-10-10
025     * @version $Id: Queries.java 4123 2008-05-25 21:49:01Z rakesh $
026     */
027    public class Queries implements Serializable
028    {
029      /**
030       * The encoding to use to serialise and deserialise instances of this
031       * class.
032       *
033       * {@value}
034       */
035      public static final String ENCODING = "UTF-8";
036    
037      /**
038       * The name of the file to which this class will be serialised.
039       */
040      public static final String FILE_NAME = "queries.xml";
041    
042      /**
043       * The system property used to configure the location of the root directory
044       * under which persistent state information for the application is stored.
045       *
046       * {@value}
047       */
048      public static final String DIRECTORY = "sptrwt.data.directory";
049    
050      /**
051       * The {@link com.thoughtworks.xstream.XStream} instance used to serialise
052       * and deserialise instances of this class.
053       */
054      protected static final XStream xstream;
055    
056      /**
057       * Static initialiser for {@link #xstream}.
058       */
059      static
060      {
061        xstream = new XStream();
062        xstream.alias( "queries", Queries.class );
063        xstream.alias( "query", Query.class );
064        xstream.alias( "keyValue", KeyValue.class );
065        xstream.alias( "category", Category.class );
066    
067        xstream.useAttributeFor( KeyValue.class, "key" );
068        xstream.useAttributeFor( KeyValue.class, "value" );
069      }
070    
071      /**
072       * A map used to quickly look up {@link Query} instances by their
073       * {@link Category#name}.
074       */
075      private final TreeMap<String,Category> categories;
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 Queries()
086      {
087        categories = new TreeMap<String,Category>();
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 Queries getInstance( final String user )
101      {
102        final Queries queries = new Queries();
103        queries.deserialise( user );
104        return queries;
105      }
106    
107      /**
108       * Return a {@link java.sql.Query} for the specified category and saved
109       * with the specified unique name.
110       *
111       * @param category The category under which the query is saved.
112       * @param name The unique name used to identify the saved query.
113       * @return The SQL statement associated with the named query, or
114       *   <code>null</code> if no such mapping exists.
115       */
116      public Query getQuery( final String category, final String name )
117      {
118        Query query = null;
119        Category cat = categories.get( category );
120        if ( cat != null )
121        {
122          query = cat.getQuery( name );
123        }
124    
125        return query;
126      }
127    
128      /**
129       * Add the specified query parameters value object to the application
130       * persistent state.
131       *
132       * @see #serialise
133       * @param category The category to associate the query with.
134       * @param query The query to be saved.
135       */
136      public void add( final String category, final Query query )
137      {
138        Category cat = categories.get( category );
139    
140        if ( cat == null )
141        {
142          cat = new Category( category );
143          categories.put( category, cat );
144        }
145    
146        cat.addQuery( query );
147        serialise();
148      }
149    
150      /**
151       * Remove the specified query parameters from the application
152       * persistent state.
153       *
154       * @see #serialise
155       * @param category The category under which the query was saved.
156       * @param name The unique name to use to identify the saved query.
157       */
158      public void delete( final String category, final String name )
159      {
160        final Category cat = categories.get( category );
161        if ( cat != null )
162        {
163          cat.deleteQuery( name );
164          serialise();
165        }
166      }
167    
168      /**
169       * Remove the specified category from persistent state.  This removes
170       * all saved queries for that category.
171       *
172       * @see #serialise
173       * @param name The name of the category to remove from saved state.
174       */
175      public void delete( final String name )
176      {
177        categories.remove( name );
178        serialise();
179      }
180    
181      /**
182       * Deserialise the contents of {@link #FILE_NAME} into this instance.
183       *
184       * @param user The name of the user to use to construct the full filename.
185       * @throws RuntimeException If errors are encountered while deserialising
186       *   the persistent state.  No exceptions are thrown if the file does
187       *   not exist.
188       */
189      protected void deserialise( final String user ) throws RuntimeException
190      {
191        final String separator =
192          System.getProperties().getProperty( "file.separator" );
193    
194        final StringBuilder builder = new StringBuilder( 64 );
195        builder.append( System.getProperty( DIRECTORY ) );
196        builder.append( separator );
197        builder.append( user );
198        builder.append( separator );
199        builder.append( FILE_NAME );
200        fileName = builder.toString();
201    
202        try
203        {
204          final String xml = StringUtilities.fromFile( fileName, ENCODING );
205          xstream.fromXML( xml, this );
206        }
207        catch ( FileNotFoundException fnfe ) {}
208        catch ( Throwable t )
209        {
210          throw new RuntimeException( "Error deserialising saved instance", t );
211        }
212      }
213    
214      /**
215       * Serialise this instance to the {@link #FILE_NAME}.
216       *
217       * @throws RuntimeException If errors are encountered while serialising
218       *   the instance.
219       */
220      protected void serialise() throws RuntimeException
221      {
222        try
223        {
224          final String xml = xstream.toXML( this );
225          StringUtilities.toFile( xml, fileName, ENCODING );
226        }
227        catch ( Throwable t )
228        {
229          throw new RuntimeException( t );
230        }
231      }
232    
233      /**
234       * Returns the category identified by the name specified.
235       *
236       * @param name The name of the category.
237       * @return The category identified by the name specified, or
238       *   <code>null</code> if no such category exists.
239       */
240      public Category getCategory( final String name )
241      {
242        return categories.get( name );
243      }
244      
245      /**
246       * Returns the names of the categories under which queries have been
247       * saved.
248       *
249       * @return A read only collection of category names.
250       */
251      public Collection<Category> getCategories()
252      {
253        return Collections.unmodifiableCollection( categories.values() );
254      }
255    }