001    package com.sptci.rwt.webui;
002    
003    import java.util.Collections;
004    import java.util.Map;
005    import java.util.LinkedHashMap;
006    
007    import nextapp.echo2.app.Column;
008    import nextapp.echo2.app.Component;
009    import nextapp.echo2.app.Row;
010    import nextapp.echo2.app.SplitPane;
011    import nextapp.echo2.app.TextArea;
012    import nextapp.echo2.app.TextField;
013    import nextapp.echo2.app.event.ActionListener;
014    
015    import consultas.echo2consultas.LiveTextField;
016    
017    import echopointng.PopUp;
018    
019    import com.sptci.ReflectionUtility;
020    import com.sptci.echo2.Configuration;
021    import com.sptci.echo2.Dimensions;
022    import com.sptci.echo2.Utilities;
023    import com.sptci.echo2.WindowPane;
024    import com.sptci.echo2.style.Extent;
025    import com.sptci.rwt.ConnectionManager;
026    
027    /**
028     * An abstract base class for query executor view components used to
029     * interact with the {@link com.sptci.rwt.AbstractQueryExecutor} class.
030     *
031     * <p>&copy; Copyright 2007 <a href='http://sptci.com/' target='_new'>Sans Pareil Technologies, Inc.</a></p>
032     * @author Rakesh Vidyadharan 2007-10-12
033     * @version $Id: ExecutorView.java 4123 2008-05-25 21:49:01Z rakesh $
034     */
035    public abstract class ExecutorView extends WindowPane
036    {
037      /** The component used to enter the SQL statement to execute. */
038      protected TextArea query;
039      
040      /**
041       * The component used to specify the maximum number of rows of data
042       * to retrieve for <code>select</code> statements.
043       */
044      protected LiveTextField maxResults;
045      
046      /**
047       * The component used to specify the maximum number of characters to
048       * display in a table column.
049       * 
050       * @since Version 1.3
051       */
052      protected LiveTextField maxColumnLength;
053    
054      /**
055       * The component used to display the results after executing a statement.
056       */
057      protected Column results;
058      
059      /** Parent Controller. */
060      protected final MainController controller;
061    
062      /** The connection manager to use to interact with the database. */
063      protected final ConnectionManager connectionManager;
064    
065      /** The cache of previously executed statements in this view. */
066      protected final Map<String,String> history =
067        new LinkedHashMap<String,String>();
068    
069      /**
070       * Create instance of the pane using the specified controller.
071       *
072       * @param controller The controller to use to interact with the rest of the
073       *   application.
074       */
075      protected ExecutorView( final MainController controller )
076      {
077        this.controller = controller;
078        this.connectionManager = controller.getConnectionManager();
079        setTitle( getTitle() + " : " + connectionManager.getTitle() );
080      }
081    
082      /**
083       * Create the UI components that are necessary for the pane.
084       *
085       * @see #createQuery
086       * @see #createControls
087       */
088      @Override
089      public void init()
090      {
091        removeAll();
092        SplitPane pane = new SplitPane();
093        pane.setStyleName( getClass().getName() + ".splitPane" );
094    
095        Column column = new Column();
096    
097        column.add( createQuery() );
098        column.add( createControls() );
099        pane.add( column );
100    
101        results = new Column();
102        pane.add( results );
103    
104        add( pane );
105      }
106    
107      /**
108       * Initialise the {@link #query} component.
109       *
110       * @return The properly initialised component.
111       */
112      protected Component createQuery()
113      {
114        query = new TextArea();
115        query.setHeight( Extent.getInstance(
116              Dimensions.getInt( this, "query.height" ) ) );
117        query.setStyleName( "Query.TextComponent" );
118        controller.setFocused( query );
119        return query;
120      }
121    
122      /**
123       * Initialise the {@link #maxResults} component.
124       *
125       * @see #createLiveTextField
126       * @param row The component to which the {@link #maxResults} component
127       *   and its associated label is to be added.
128       */
129      protected void createMaxResults( final Component row )
130      {
131        maxResults = createLiveTextField( "maxResults", row );
132      }
133      
134      /**
135       * Initialise the {@link #maxColumnLength} component.
136       *
137       * @since Version 1.3
138       * @see #createLiveTextField
139       * @param row The component to which the {@link #maxColumnLength} component
140       *   and its associated label is to be added.
141       */
142      protected void createMaxColumnLength( final Component row )
143      {
144        maxColumnLength = createLiveTextField( "maxColumnLength", row );
145        maxColumnLength.setText( String.valueOf(
146            Dimensions.getInt( this, "maxColumnLength" ) ) );
147      }
148      
149      /**
150       * Create a numeric text field with the specified name.
151       * 
152       * @since Version 1.3
153       * @param name The name of the text field.  Used to look up localised
154       *   information.
155       * @param row The parent component to which the text field is to be added.
156       * @return The properly initialised text field.
157       */
158      protected LiveTextField createLiveTextField( final String name,
159          final Component row )
160      {
161        row.add( Utilities.createLabel(
162              getClass().getName(), name, "General.Label" ) );
163    
164        LiveTextField field = new LiveTextField();
165        field.setRegexp( "^\\d*$" );
166        field.setWidth( Extent.getInstance(
167            Dimensions.getInt( this, name + ".width" ) ) );
168        field.setMaximumLength(
169            Dimensions.getInt( this, name + ".maxlength" ) );
170    
171        row.add( field );
172        return field;
173      }
174    
175      /**
176       * Create the {@link nextapp.echo2.app.Button} used to trigger execution
177       * of the user entered SQL statement.
178       *
179       * @see #getListenerName
180       * @return The button component.
181       */
182      protected Component createExecute()
183      {
184        final String className = getListenerName();
185        ActionListener listener = null;
186    
187        try
188        {
189          listener = (ActionListener)
190            ReflectionUtility.newInstance( className, controller );
191        }
192        catch ( Throwable t )
193        {
194          String message = Configuration.getString( ExecutorView.class, "error" );
195          message = message.replaceAll( "\\$class\\$", className );
196          controller.processFatalException( message, t );
197        }
198    
199        return Utilities.createButton( getClass().getName(), "execute",
200              "General.Button", listener );
201      }
202    
203      /**
204       * Create the {@link nextapp.echo2.app.Button} used to trigger export
205       * of the results of the query to Excel.
206       *
207       * @return The component used to trigger the export action.
208       */
209      protected Component createExport()
210      {
211        return Utilities.createButton( getClass().getName(), "export",
212              "General.Button", new ExportListener( controller ) );
213      }
214    
215      /**
216       * Create the {@link echopointng.PopUp} component used to display the
217       * component used to capture user input on the category and name to
218       * assign to a saved SQL statement.
219       *
220       * @return The component used to capture user input.
221       */
222      protected Component createSave()
223      {
224        PopUp popup = new PopUp();
225        popup.setTarget( Utilities.createLabel( getClass().getName(),
226              "saveQuery", "Save.Label" ) );
227        popup.setPopUp( new SaveQueryComponent( controller ) );
228    
229        return popup;
230      }
231    
232      /**
233       * Create the {@link nextapp.echo2.app.Button} used to display the
234       * history of statements executed in the current view.
235       *
236       * @return The button component.
237       */
238      protected Component createHistory()
239      {
240        return Utilities.createButton( getClass().getName(), "history",
241            "General.Button", new HistoryListener( controller ) );
242      }
243    
244      /**
245       * Get the fully qualified class name of the action listener used to
246       * execute the user entered statement.
247       *
248       * @return The fully qualified class name of the listener.
249       */
250      protected String getListenerName()
251      {
252        final String name = getClass().getName();
253        String cls = name.substring( 0, name.lastIndexOf( "." ) );
254        cls += ".Execute";
255        cls += name.substring( name.lastIndexOf( "." ) + 1 );
256        cls = cls.substring( 0, cls.lastIndexOf( "ExecutorView" ) );
257        cls += "Listener";
258    
259        return cls;
260      }
261    
262      /**
263       * Return the SQL statement that was entered into {@link #query}.
264       *
265       * @return The sql statement that was entered.
266       */
267      public String getQuery()
268      {
269        return query.getText();
270      }
271    
272      /**
273       * Sets the text displayed in {@link #query} to the specified value.
274       * Re-initialises {@link #query} to get around the client not getting 
275       * updated if client state has been modified.
276       *
277       * @param text The query value to set.
278       */
279      public void setQuery( final String text )
280      {
281        final Component parent = query.getParent();
282        final int index = parent.indexOf( query );
283        parent.remove( index );
284        parent.add( createQuery(), index );
285        query.setText( text );
286      }
287    
288      /**
289       * Return the value entered in {@link #maxResults}.
290       */
291      public int getMaxResults()
292      {
293        return ( ( maxResults.getText().length() == 0 ) ? 0 :
294            Integer.parseInt( maxResults.getText() ) );
295      }
296      
297      /**
298       * Return the value entered in {@link #maxColumnLength}.
299       */
300      public int getMaxColumnLength()
301      {
302        return ( ( maxColumnLength.getText().length() == 0 ) ? 0 :
303            Integer.parseInt( maxColumnLength.getText() ) );
304      }
305    
306      /**
307       * Removes the results of a previous query execution.  Removes all the
308       * child components of {@link #results}.
309       */
310      public void reset()
311      {
312        results.removeAll();
313      }
314    
315      /**
316       * Create the layout component to use to display the {@link #maxResults}
317       * component and the other controls used to execute the statement.
318       *
319       * @return The layout component.
320       */
321      protected abstract Component createControls();
322      
323      /**
324       * Returns {@link #history}.
325       *
326       * @return The value/reference of/to history.
327       */
328      public Map<String,String> getHistory()
329      {
330        return Collections.unmodifiableMap( history );
331      }
332    
333      /**
334       * Add the specified statement to {@link #history}.  Removes and re-adds
335       * duplicate statements to maintain execution order.
336       *
337       * @param statement The statement that is to be added.
338       */
339      public void addToHistory( final String statement )
340      {
341        final String text = ( ( statement.length() > 100 ) ?
342            statement.substring( 0, 100 ) + "..." : statement );
343    
344        if ( history.containsKey( text ) )
345        {
346          history.remove( text );
347        }
348    
349        history.put( text, statement );
350      }
351    
352      /**
353       * Sets the text displayed in {@link #query} to the specified value
354       * from {@link #history}.  The value specified is the <code>key</code>
355       * in {@link #history}.  The value set will be <code>value</code>.
356       *
357       * @see #setQuery
358       * @param key The key to use to set the statement.
359       */
360      public void setQueryFromHistory( final String key )
361      {
362        final String value = history.get( key );
363        if ( value != null ) setQuery( value );
364      }
365      
366      /**
367       * Returns {@link #connectionManager}.
368       *
369       * @return The value/reference of/to connectionManager.
370       */
371      public ConnectionManager getConnectionManager()
372      {
373        return connectionManager;
374      }
375    }