KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > net > sf > hajdbc > sql > SQLObject


1 /*
2  * HA-JDBC: High-Availability JDBC
3  * Copyright (c) 2004-2006 Paul Ferraro
4  *
5  * This library is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU Lesser General Public License as published by the
7  * Free Software Foundation; either version 2.1 of the License, or (at your
8  * option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful, but WITHOUT
11  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
13  * for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public License
16  * along with this library; if not, write to the Free Software Foundation,
17  * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18  *
19  * Contact: ferraro@users.sourceforge.net
20  */

21 package net.sf.hajdbc.sql;
22
23 import java.util.Collection JavaDoc;
24 import java.util.HashMap JavaDoc;
25 import java.util.Iterator JavaDoc;
26 import java.util.List JavaDoc;
27 import java.util.Map JavaDoc;
28 import java.util.NoSuchElementException JavaDoc;
29 import java.util.SortedMap JavaDoc;
30 import java.util.TreeMap JavaDoc;
31 import java.util.concurrent.Callable JavaDoc;
32 import java.util.concurrent.ExecutionException JavaDoc;
33 import java.util.concurrent.ExecutorService JavaDoc;
34 import java.util.concurrent.Future JavaDoc;
35 import java.util.concurrent.locks.Lock JavaDoc;
36
37 import net.sf.hajdbc.Balancer;
38 import net.sf.hajdbc.Database;
39 import net.sf.hajdbc.DatabaseCluster;
40 import net.sf.hajdbc.Messages;
41 import net.sf.hajdbc.Operation;
42 import net.sf.hajdbc.SQLException;
43
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46
47 /**
48  * Base class for all HA-JDBC proxy objects.
49  * @author Paul Ferraro
50  * @param <E> elements proxied by this object
51  * @param <P> parent object that created the elements proxied by this object
52  * @since 1.0
53  */

54 public abstract class SQLObject<E, P>
55 {
56     private static Logger logger = LoggerFactory.getLogger(SQLObject.class);
57     
58     private DatabaseCluster databaseCluster;
59     protected SQLObject<P, ?> parent;
60     private Operation<P, E> parentOperation;
61     private Map JavaDoc<Database, E> objectMap;
62     private Map JavaDoc<String JavaDoc, Operation<E, ?>> operationMap = new HashMap JavaDoc<String JavaDoc, Operation<E, ?>>();
63     
64     protected SQLObject(SQLObject<P, ?> parent, Operation<P, E> operation, ExecutorService JavaDoc executor, Lock JavaDoc lock) throws java.sql.SQLException JavaDoc
65     {
66         this(parent.getDatabaseCluster(), execute(parent, operation, executor, lock));
67         
68         this.parent = parent;
69         this.parentOperation = operation;
70     }
71     
72     /**
73      * Temporary static method to work around bug in Eclipse compiler
74      * @param parent
75      * @param operation
76      * @param <T>
77      * @param <S>
78      * @return map of Database to SQL object
79      * @throws java.sql.SQLException
80      */

81     private static <T, S> Map JavaDoc<Database, T> execute(SQLObject<S, ?> parent, Operation<S, T> operation, ExecutorService JavaDoc executor, Lock JavaDoc lock) throws java.sql.SQLException JavaDoc
82     {
83         return parent.executeWriteToDatabase(operation, executor, lock);
84     }
85     
86     protected SQLObject(DatabaseCluster databaseCluster, Map JavaDoc<Database, E> objectMap)
87     {
88         this.databaseCluster = databaseCluster;
89         this.objectMap = objectMap;
90     }
91     
92     /**
93      * Returns the underlying SQL object for the specified database.
94      * If the sql object does not exist (this might be the case if the database was newly activated), it will be created from the stored operation.
95      * Any recorded operations are also executed. If the object could not be created, or if any of the executed operations failed, then the specified database is deactivated.
96      * @param database a database descriptor.
97      * @return an underlying SQL object
98      */

99     public synchronized final E getObject(Database database)
100     {
101         E object = this.objectMap.get(database);
102         
103         if (object == null)
104         {
105             try
106             {
107                 if (this.parent == null)
108                 {
109                     throw new java.sql.SQLException JavaDoc();
110                 }
111                 
112                 P parentObject = this.parent.getObject(database);
113                 
114                 if (parentObject == null)
115                 {
116                     throw new java.sql.SQLException JavaDoc();
117                 }
118                 
119                 object = this.parentOperation.execute(database, parentObject);
120                 
121                 for (Operation<E, ?> operation: this.operationMap.values())
122                 {
123                     operation.execute(database, object);
124                 }
125                 
126                 this.objectMap.put(database, object);
127             }
128             catch (java.sql.SQLException JavaDoc e)
129             {
130                 if (this.databaseCluster.deactivate(database))
131                 {
132                     logger.warn(Messages.getMessage(Messages.SQL_OBJECT_INIT_FAILED, this.getClass().getName(), database), e);
133                 }
134             }
135         }
136         
137         return object;
138     }
139     
140     /**
141      * Records an operation.
142      * @param operation a database operation
143      */

144     protected synchronized final void record(Operation<E, ?> operation)
145     {
146         this.operationMap.put(operation.getClass().toString(), operation);
147     }
148     
149     /**
150      * Helper method that extracts the first result from a map of results.
151      * @param <T>
152      * @param valueMap a Map<Database, Object> of operation execution results.
153      * @return a operation execution result
154      */

155     protected final <T> T firstValue(Map JavaDoc<Database, T> valueMap)
156     {
157         return valueMap.values().iterator().next();
158     }
159     
160     private List JavaDoc<Database> getActiveDatabaseList()
161     {
162         List JavaDoc<Database> databaseList = this.databaseCluster.getBalancer().list();
163         
164         this.retain(databaseList);
165         
166         return databaseList;
167     }
168     
169     protected synchronized void retain(Collection JavaDoc<Database> activeDatabases)
170     {
171         if (this.parent == null) return;
172         
173         Iterator JavaDoc<Map.Entry JavaDoc<Database, E>> mapEntries = this.objectMap.entrySet().iterator();
174         
175         while (mapEntries.hasNext())
176         {
177             Map.Entry JavaDoc<Database, E> mapEntry = mapEntries.next();
178             
179             Database database = mapEntry.getKey();
180             
181             if (!activeDatabases.contains(database))
182             {
183                 E object = mapEntry.getValue();
184                 
185                 if (object != null)
186                 {
187                     try
188                     {
189                         this.close(object);
190                     }
191                     catch (java.sql.SQLException JavaDoc e)
192                     {
193                         // Ignore
194
}
195                 }
196                 
197                 mapEntries.remove();
198             }
199         }
200         
201         this.parent.retain(activeDatabases);
202     }
203     
204     protected abstract void close(E object) throws java.sql.SQLException JavaDoc;
205     
206     /**
207      * Executes the specified read operation on a single database in the cluster.
208      * It is assumed that these types of operation will <em>not</em> require access to the database.
209      * @param <T>
210      * @param operation a database operation
211      * @return the result of the operation
212      * @throws java.sql.SQLException if operation execution fails
213      */

214     public final <T> T executeReadFromDriver(Operation<E, T> operation) throws java.sql.SQLException JavaDoc
215     {
216         try
217         {
218             Database database = this.databaseCluster.getBalancer().first();
219             E object = this.getObject(database);
220             
221             return operation.execute(database, object);
222         }
223         catch (NoSuchElementException JavaDoc e)
224         {
225             throw new SQLException(Messages.getMessage(Messages.NO_ACTIVE_DATABASES, this.databaseCluster));
226         }
227     }
228
229     /**
230      * Executes the specified read operation on a single database in the cluster.
231      * It is assumed that these types of operation will require access to the database.
232      * @param <T>
233      * @param operation a database operation
234      * @return the result of the operation
235      * @throws java.sql.SQLException if operation execution fails
236      */

237     public final <T> T executeReadFromDatabase(Operation<E, T> operation) throws java.sql.SQLException JavaDoc
238     {
239         Balancer balancer = this.databaseCluster.getBalancer();
240         
241         try
242         {
243             while (true)
244             {
245                 Database database = balancer.next();
246                 E object = this.getObject(database);
247     
248                 try
249                 {
250                     balancer.beforeOperation(database);
251                     
252                     return operation.execute(database, object);
253                 }
254                 catch (java.sql.SQLException JavaDoc e)
255                 {
256                     this.databaseCluster.handleFailure(database, e);
257                 }
258                 finally
259                 {
260                     balancer.afterOperation(database);
261                 }
262             }
263         }
264         catch (NoSuchElementException JavaDoc e)
265         {
266             throw new SQLException(Messages.getMessage(Messages.NO_ACTIVE_DATABASES, this.databaseCluster));
267         }
268     }
269
270     /**
271      * Executes the specified transactional write operation on every database in the cluster.
272      * It is assumed that these types of operation will require access to the database.
273      * @param <T>
274      * @param operation a database operation
275      * @return the result of the operation
276      * @throws java.sql.SQLException if operation execution fails
277      */

278     public final <T> Map JavaDoc<Database, T> executeTransactionalWriteToDatabase(final Operation<E, T> operation) throws java.sql.SQLException JavaDoc
279     {
280         return this.executeWriteToDatabase(operation, this.databaseCluster.getTransactionalExecutor(), this.databaseCluster.readLock());
281     }
282     
283     /**
284      * Executes the specified non-transactional write operation on every database in the cluster.
285      * It is assumed that these types of operation will require access to the database.
286      * @param <T>
287      * @param operation a database operation
288      * @return the result of the operation
289      * @throws java.sql.SQLException if operation execution fails
290      */

291     public final <T> Map JavaDoc<Database, T> executeNonTransactionalWriteToDatabase(final Operation<E, T> operation) throws java.sql.SQLException JavaDoc
292     {
293         return this.executeWriteToDatabase(operation, this.databaseCluster.getNonTransactionalExecutor(), null);
294     }
295     
296     private <T> Map JavaDoc<Database, T> executeWriteToDatabase(final Operation<E, T> operation, ExecutorService JavaDoc executor, Lock JavaDoc lock) throws java.sql.SQLException JavaDoc
297     {
298         Map JavaDoc<Database, T> resultMap = new TreeMap JavaDoc<Database, T>();
299         SortedMap JavaDoc<Database, java.sql.SQLException JavaDoc> exceptionMap = new TreeMap JavaDoc<Database, java.sql.SQLException JavaDoc>();
300         
301         if (lock != null)
302         {
303             lock.lock();
304         }
305         
306         try
307         {
308             List JavaDoc<Database> databaseList = this.getActiveDatabaseList();
309             
310             if (databaseList.isEmpty())
311             {
312                 throw new SQLException(Messages.getMessage(Messages.NO_ACTIVE_DATABASES, this.databaseCluster));
313             }
314             
315             Map JavaDoc<Database, Future JavaDoc<T>> futureMap = new HashMap JavaDoc<Database, Future JavaDoc<T>>();
316
317             for (final Database database: databaseList)
318             {
319                 final E object = this.getObject(database);
320                 
321                 Callable JavaDoc<T> task = new Callable JavaDoc<T>()
322                 {
323                     public T call() throws java.sql.SQLException JavaDoc
324                     {
325                         return operation.execute(database, object);
326                     }
327                 };
328     
329                 futureMap.put(database, executor.submit(task));
330             }
331
332             for (Database database: databaseList)
333             {
334                 Future JavaDoc<T> future = futureMap.get(database);
335                 
336                 try
337                 {
338                     resultMap.put(database, future.get());
339                 }
340                 catch (ExecutionException JavaDoc e)
341                 {
342                     SQLException cause = new SQLException(e.getCause());
343     
344                     try
345                     {
346                         this.databaseCluster.handleFailure(database, cause);
347                     }
348                     catch (java.sql.SQLException JavaDoc sqle)
349                     {
350                         exceptionMap.put(database, sqle);
351                     }
352                 }
353             }
354         }
355         catch (InterruptedException JavaDoc e)
356         {
357             throw new SQLException(e);
358         }
359         finally
360         {
361             if (lock != null)
362             {
363                 lock.unlock();
364             }
365         }
366         
367         // If no databases returned successfully, return an exception back to the caller
368
if (resultMap.isEmpty())
369         {
370             if (exceptionMap.isEmpty())
371             {
372                 throw new SQLException(Messages.getMessage(Messages.NO_ACTIVE_DATABASES, this.databaseCluster));
373             }
374             
375             throw exceptionMap.get(exceptionMap.firstKey());
376         }
377         
378         // If any databases failed, while others succeeded, deactivate them
379
if (!exceptionMap.isEmpty())
380         {
381             this.handleExceptions(exceptionMap);
382         }
383         
384         // Return results from successful operations
385
return resultMap;
386     }
387     
388     /**
389      * Executes the specified write operation on every database in the cluster.
390      * It is assumed that these types of operation will <em>not</em> require access to the database.
391      * @param <T>
392      * @param operation a database operation
393      * @return the result of the operation
394      * @throws java.sql.SQLException if operation execution fails
395      */

396     public final <T> Map JavaDoc<Database, T> executeWriteToDriver(Operation<E, T> operation) throws java.sql.SQLException JavaDoc
397     {
398         Map JavaDoc<Database, T> resultMap = new TreeMap JavaDoc<Database, T>();
399
400         Lock JavaDoc lock = this.databaseCluster.readLock();
401
402         lock.lock();
403         
404         try
405         {
406             List JavaDoc<Database> databaseList = this.getActiveDatabaseList();
407             
408             if (databaseList.isEmpty())
409             {
410                 throw new SQLException(Messages.getMessage(Messages.NO_ACTIVE_DATABASES, this.databaseCluster));
411             }
412
413             for (Database database: databaseList)
414             {
415                 E object = this.getObject(database);
416                 
417                 resultMap.put(database, operation.execute(database, object));
418             }
419         }
420         finally
421         {
422             lock.unlock();
423         }
424         
425         this.record(operation);
426         
427         return resultMap;
428     }
429     
430     /**
431      * Returns the database cluster to which this proxy is associated.
432      * @return a database cluster
433      */

434     public DatabaseCluster getDatabaseCluster()
435     {
436         return this.databaseCluster;
437     }
438     
439     /**
440      * @param exceptionMap
441      * @throws java.sql.SQLException
442      */

443     @SuppressWarnings JavaDoc("unused")
444     public void handleExceptions(Map JavaDoc<Database, java.sql.SQLException JavaDoc> exceptionMap) throws java.sql.SQLException JavaDoc
445     {
446         for (Map.Entry JavaDoc<Database, java.sql.SQLException JavaDoc> exceptionMapEntry: exceptionMap.entrySet())
447         {
448             Database database = exceptionMapEntry.getKey();
449             java.sql.SQLException JavaDoc exception = exceptionMapEntry.getValue();
450             
451             if (this.databaseCluster.deactivate(database))
452             {
453                 logger.error(Messages.getMessage(Messages.DATABASE_DEACTIVATED, database, this.databaseCluster), exception);
454             }
455         }
456     }
457 }
458
Popular Tags