KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > mckoi > jfccontrols > QueryAgent


1 /**
2  * com.mckoi.jfccontrols.QueryAgent 23 Aug 2000
3  *
4  * Mckoi SQL Database ( http://www.mckoi.com/database )
5  * Copyright (C) 2000, 2001, 2002 Diehl and Associates, Inc.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * Version 2 as published by the Free Software Foundation.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License Version 2 for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * Version 2 along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19  *
20  * Change Log:
21  *
22  *
23  */

24
25 package com.mckoi.jfccontrols;
26
27 import java.util.ArrayList JavaDoc;
28 import java.awt.*;
29 import java.sql.*;
30 import javax.swing.*;
31
32 /**
33  * A class that is an agent for queries from the client environment to the
34  * server. All locked swing event dispatched JDBC queries should go through
35  * this class. These are queries where the user does something which results
36  * in a query being executed on the server, and must then wait for the result
37  * to be received.
38  * <p>
39  * This class provides a mechanism which allows the client not to block
40  * indeffinately on the event dispatcher thread. This means we can give
41  * feedback to the user if a query is taking a long time (progress bar,
42  * hour-glass, etc) and components will repaint correctly. It also allows us
43  * to cancel any query in progress (because the event dispatcher isn't locked
44  * we can handle UI events and the interface won't be frozen).
45  * <p>
46  * We acheive this behaviour with a hack of the system EventQueue (the same way
47  * modality works in swing.JInternalFrame). The low down is, we emulate the
48  * Java event dispatcher inner loop so that all AWT events (repaints/
49  * component events/etc) are dispatched in our own controlled loop that is
50  * blocked with respect to the callee. (Find this blocking behaviour in
51  * SwingBlockUtil)
52  * <p>
53  * I consider this a mild hack. This class may be incompatible with future
54  * versions of Java if the AWT event mechanism is altered. It may also not
55  * work happily with non-Sun based implementations of Java.
56  * <p>
57  * NOTE: Other ways of acheiving non-AWT locking queries is with a delegate
58  * implementation. The method that executes the query returns immediately
59  * and the result is sent to a delegate. While this system sounds nice in
60  * theory, it's not pretty in practice. Especially if you need to execute
61  * many queries in a specific sequence. Also, handling exceptions is a
62  * nightmare with this idea.
63  *
64  * @author Tobias Downer
65  */

66
67 public class QueryAgent {
68
69   /**
70    * The JDBC Connection object for the connection to the database.
71    */

72   private Connection connection = null;
73
74   /**
75    * The utility for blocking the swing event dispatch thread.
76    */

77   private SwingBlockUtil block_util;
78
79   /**
80    * The thread we use to send commands to the JDBC connection.
81    */

82   private QueryThread query_thread;
83
84   /**
85    * This represents the state of the result of the query. Either 'n' (none),
86    * 'e' (exception), 'r' (result), 'f' (finished), or 'c' (cancelled).
87    */

88   private char query_finished = 'f';
89
90   /**
91    * If an exception was thrown, the SQLException.
92    */

93   private SQLException sql_exception;
94
95   /**
96    * If a ResultSet was obtained from the query, the ResultSet.
97    */

98   private ResultSet result_set;
99
100   /**
101    * Constructs the query agent.
102    */

103   public QueryAgent(Connection connection) {
104     this.connection = connection;
105     block_util = new SwingBlockUtil();
106     query_thread = new QueryThread();
107     query_thread.start();
108   }
109
110   /**
111    * Returns the connection for the JDBC interface.
112    */

113   public Connection connection() {
114     return connection;
115   }
116
117   /**
118    * Executes a query, blocking until a response from the server has been
119    * received. This will send the command to the server on the QueryThread
120    * and emulates the event dispatch thread so AWT events can still be
121    * processed.
122    * <p>
123    * This is based on the 'setModal' method found in JInternalFrame. It
124    * is up to the developer to block the user interface elements from being
125    * used while we are waiting for a query result.
126    * <p>
127    * Throws an InterruptedException if the query is cancelled via the
128    * 'cancelQuery' method.
129    */

130   public ResultSet executeQuery(Query query)
131                                    throws SQLException, InterruptedException JavaDoc {
132
133     if (!SwingUtilities.isEventDispatchThread()) {
134       throw new Error JavaDoc("Not on the event dispatcher.");
135     }
136     else if (query_finished != 'f') {
137       // This situation would occur when a component generates an event (such
138
// as the user pressing a button) that performs a query. Therefore
139
// there are multi-levels of queries being executed.
140
throw new Error JavaDoc("Can't nest queries.");
141     }
142
143     // Set the new statement to be executed,
144
PreparedStatement statement =
145                              connection.prepareStatement(query.getString());
146     for (int i = 0; i < query.parameterCount(); ++i) {
147       statement.setObject(i + 1, query.getParameter(i));
148     }
149
150     query_thread.addStatement(statement);
151     query_finished = 'n';
152     // Block until statement finished or cancelled.
153
block_util.block();
154
155     if (query_finished == 'e') {
156       SQLException e = sql_exception;
157       sql_exception = null;
158       query_finished = 'f';
159       throw e;
160     }
161     else if (query_finished == 'r') {
162       ResultSet rs = result_set;
163       result_set = null;
164       query_finished = 'f';
165       return rs;
166     }
167     else if (query_finished == 'c') {
168       query_finished = 'f';
169       throw new InterruptedException JavaDoc("Query Cancelled");
170     }
171     else {
172       char old_state = query_finished;
173       query_finished = 'f';
174       throw new Error JavaDoc("Unknown query state: " + old_state);
175     }
176
177   }
178
179   /**
180    * Cancels any query that is currently being executed by this agent.
181    * This will cause the 'executeQuery' method to throw an
182    * InterruptedException.
183    */

184   public void cancelQuery() {
185     SwingUtilities.invokeLater(new Runnable JavaDoc() {
186       public void run() {
187         if (query_finished != 'f') {
188           query_finished = 'c';
189           block_util.unblock();
190         }
191       }
192     });
193   }
194
195   /**
196    * Returns the current system EventQueue.
197    */

198   private EventQueue eventQueue() {
199     return Toolkit.getDefaultToolkit().getSystemEventQueue();
200   }
201
202   /**
203    * Returns true if the query is done or not yet.
204    */

205   private boolean isQueryDone() {
206     return query_finished != 'n';
207   }
208
209   /**
210    * This is called when a query has finished and an SQLException was
211    * thrown.
212    * <p>
213    * Called from the QueryThread.
214    */

215   private void notifyException(
216                     final PreparedStatement statement, final SQLException e) {
217     SwingUtilities.invokeLater(new Runnable JavaDoc() {
218       public void run() {
219         if (query_finished != 'f') {
220           sql_exception = e;
221           query_finished = 'e';
222           block_util.unblock();
223         }
224       }
225     });
226   }
227
228   /**
229    * This is called when a query has finished and a valid ResultSet was
230    * returned as the result.
231    * <p>
232    * Called from the QueryThread.
233    */

234   private void notifyComplete(
235               final PreparedStatement statement, final ResultSet result_set) {
236     SwingUtilities.invokeLater(new Runnable JavaDoc() {
237       public void run() {
238         if (query_finished != 'f') {
239           QueryAgent.this.result_set = result_set;
240           query_finished = 'r';
241           block_util.unblock();
242         }
243       }
244     });
245   }
246
247   // ---------- Inner classes ----------
248

249   /**
250    * The query thread.
251    */

252   private class QueryThread extends Thread JavaDoc {
253
254     // The list of statements pending to be executed,
255
private ArrayList JavaDoc statements = new ArrayList JavaDoc();
256
257     private QueryThread() {
258       super();
259       setDaemon(true);
260       setName("Mckoi - Query Agent");
261     }
262
263     /**
264      * Sets the PreparedStatement that we want to execute.
265      */

266     private synchronized void addStatement(PreparedStatement statement) {
267       statements.add(statement);
268       notifyAll();
269     }
270
271     public void run() {
272       while (true) {
273         try {
274           PreparedStatement to_exec = null;
275           synchronized (this) {
276             while (statements.size() == 0) {
277               wait();
278             }
279             to_exec = (PreparedStatement) statements.remove(0);
280           }
281           try {
282             // 'to_exec' is the statement to execute next,
283
ResultSet result_set = to_exec.executeQuery();
284             notifyComplete(to_exec, result_set);
285           }
286           catch (SQLException e) {
287             notifyException(to_exec, e);
288           }
289         }
290         catch (Throwable JavaDoc e) {
291           System.err.println("Exception during QueryThread: " +
292                              e.getMessage());
293           e.printStackTrace(System.err);
294         }
295       }
296     }
297
298   }
299
300   // ---------- Static methods ----------
301

302   private static QueryAgent query_agent = null;
303
304   /**
305    * Initialises the QueryAgent with the given JDBC Connection.
306    */

307   public static void initAgent(Connection connection) {
308     if (query_agent == null) {
309       query_agent = new QueryAgent(connection);
310     }
311   }
312
313   /**
314    * Returns the QueryAgent for this VM.
315    */

316   public static QueryAgent getDefaultAgent() {
317     return query_agent;
318   }
319
320   /**
321    * Executes a query on the default query agent for this VM. This must be
322    * called on the swing event dispatcher thread.
323    * <p>
324    * This will block until the server has responded to the query and a result
325    * has been obtained. While this method does block, it still will service
326    * events on the event dispatcher queue. This means, UI elements will still
327    * work (buttons/text fields/etc) at the same time the server is fetching
328    * the result. And, we still have blocking behaviour for the callee.
329    * <p>
330    * What this ultimately means, is that components can be repainted and
331    * we can have animations indicating the progress (feedback from the UI to
332    * the user that it hasn't frozen up) and buttons to cancel the query if
333    * it's taking too long.
334    */

335   public static ResultSet execute(Query query)
336                                    throws SQLException, InterruptedException JavaDoc {
337     return getDefaultAgent().executeQuery(query);
338   }
339
340   /**
341    * Cancels the query that is currently being executed (if any). This must
342    * be called on the swing event dispatcher thread.
343    * <p>
344    * This will throw an InterruptedException from the 'execute' method if it
345    * is waiting.
346    */

347   public static void cancel() {
348     getDefaultAgent().cancelQuery();
349   }
350
351 }
352
Popular Tags