View Javadoc

1   /* (c) Copyright 2003 Caleigo AB, All rights reserved. 
2    * 
3    * This library is free software; you can redistribute it and/or
4    * modify it under the terms of the GNU Lesser General Public
5    * License as published by the Free Software Foundation; either
6    * version 2.1 of the License, or (at your option) any later version.
7    * 
8    * This library is distributed in the hope that it will be useful,
9    * but WITHOUT ANY WARRANTY; without even the implied warranty of
10   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11   * Lesser General Public License for more details.
12   * 
13   * You should have received a copy of the GNU Lesser General Public
14   * License along with this library; if not, write to the Free Software
15   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16   *  
17   */
18  
19  package org.caleigo.core.service;
20  
21  
22  import java.util.*;
23  import java.sql.*;
24  
25  import org.caleigo.core.*;
26  import org.caleigo.core.exception.*;
27  import org.caleigo.toolkit.log.*;
28  
29  /*** <Description of JDBCDataService>
30   *
31   * @author  Dennis Zikovic
32   * @version 1.00
33   * 
34   *//* 
35   *
36   * WHEN        WHO               WHY & WHAT
37   * ------------------------------------------------------------------------------
38   * 2001-07-20  Dennis Zikovic    Creation
39   */
40  public class JDBCPreparedStatementDataService extends AbstractDataService
41  {
42      // Constants ---------------------------------------------------------------
43      public static final int DEFAULT_MAX_CONNECTION_POOL_SIZE = 10;
44      
45      private static final int UNDEFINED_AUTO_INDEX = 0;
46      private static final int JDBC_AUTO_INDEX = 1;
47      private static final int SQLSERVER_AUTO_INDEX = 2;
48      private static final int POSTGRE_AUTO_INDEX = 3;
49      private static final int ORACLE_AUTO_INDEX = 4;
50      private static final int HSQLDB_AUTO_INDEX = 5;
51      private static final int UNSUPORTED_AUTO_INDEX = -1;
52      
53      // Class members -----------------------------------------------------------
54      private static Driver sDriver;
55      
56      // Data members ------------------------------------------------------------
57      private String mConnectionURL;
58      private Properties mConnectionInfo;
59      private SQLToolKitPS mSQLToolKit;
60      private ConnectionPool mConnectionPool;
61      
62      private int mAutoIndexMethod = UNDEFINED_AUTO_INDEX;
63      
64      // Class methods -----------------------------------------------------------
65      
66      public static void setDriver(Driver driver)
67      {
68          sDriver = driver;
69      }
70      
71      // Constructors ------------------------------------------------------------
72      
73      /*** Default constructor for JDBCDataService.
74       */
75      public JDBCPreparedStatementDataService(IDataSourceDescriptor descriptor)
76      {
77          this(descriptor, descriptor.getSourceName(), "jdbc:odbc:"+descriptor.getSourceName(), new Properties(), DEFAULT_MAX_CONNECTION_POOL_SIZE);
78      }
79      
80      public JDBCPreparedStatementDataService(IDataSourceDescriptor descriptor, Object serviceIdentity, String url)
81      {
82          this(descriptor, serviceIdentity, url, new Properties(), DEFAULT_MAX_CONNECTION_POOL_SIZE);
83      }
84      
85      public JDBCPreparedStatementDataService(IDataSourceDescriptor descriptor, Object serviceIdentity, String url, String user, String password)
86      {
87          this(descriptor, serviceIdentity, url, new Properties(), DEFAULT_MAX_CONNECTION_POOL_SIZE);
88          mConnectionInfo.put("user", user);
89          mConnectionInfo.put("password", password);
90      }
91      
92      public JDBCPreparedStatementDataService(IDataSourceDescriptor descriptor, Object serviceIdentity, String url, String user, String password, String catalog)
93      {
94          this(descriptor, serviceIdentity, url, user, password);
95          if (catalog != null)
96              mConnectionInfo.put("catalog", catalog);
97      }
98      
99      public JDBCPreparedStatementDataService(IDataSourceDescriptor descriptor, Object serviceIdentity, String url, java.util.Properties info, int maxConnectionPoolSize)
100     {
101         super(descriptor.getCodeName(), serviceIdentity, descriptor);
102         mConnectionURL = url;
103         mConnectionInfo = info;
104         mSQLToolKit = new SQLToolKitPS();
105         mConnectionPool = new ConnectionPool(maxConnectionPoolSize);
106         
107         // Perform data access verifaction on the data source.
108         if(DataAccessManager.getManager().getAccessLevel(descriptor)==DataAccessManager.NONE)
109             throw new SecurityException("No read access for "+descriptor+" data sources!");
110     }
111     
112     // IDataService implementation ---------------------------------------------
113     public IDataTransaction newTransaction()
114     {
115         return new JDBCDataTransaction();
116     }
117         
118     /*** Should return true if the service is online and reponding to calls.
119      */ 
120     public boolean ping()
121     {
122         boolean responding = false;
123         try
124         {
125             Connection connection = this.openConnection();
126             if(connection!=null)
127             {
128                 this.closeConnection(connection);
129                 responding = true;
130             }
131         }
132         catch(Exception e)
133         { 
134         } 
135         return responding;
136     }
137     
138     // Action methods ----------------------------------------------------------
139     
140     protected void executeLoad(Connection connection, IEntity entity, Qualifier qualifier) throws DataServiceException
141     {
142         try
143         {
144             // Perform security access check.
145             if(DataAccessManager.getManager().getAccessLevel(entity.getEntityDescriptor())==DataAccessManager.NONE)
146                 throw new SecurityException("No read access for "+entity.getEntityDescriptor()+" entities!");
147                 
148             // Perform query.
149             PreparedStatement preparedStatement = mSQLToolKit.buildSelectStatement(entity.getEntityDescriptor(), qualifier, connection);
150             
151             ResultSet set = preparedStatement.executeQuery();
152             
153             // Read the result and update the entity status.
154             if(set.next())
155             {
156                 this.readResultSetRow(set, entity, false);
157                 
158                 // Reset entity if data READ access is not granted.
159                 if(!DataAccessManager.getManager().hasReadAccess(entity))
160                     entity.clear();
161                 else
162                 {
163                     entity.setStatusFlag(IEntity.PERSISTENT);
164                     entity.clearStatusFlag(IEntity.DIRTY | IEntity.EMPTY);
165                 }
166             }
167             else
168             {
169                 entity.setStatusFlag(IEntity.EMPTY);
170                 entity.clearStatusFlag(IEntity.DIRTY | IEntity.PERSISTENT);
171             }
172             set.close();
173         }
174         catch(SQLException e) 
175         { 
176             throw new DataServiceException("Select command failed", e);
177         } 
178     }
179     
180     protected void executeQuery(Connection connection, DataQuery query, ISelection selection) throws DataServiceException
181     {
182         try
183         {
184             // Perform security access check.
185             if(DataAccessManager.getManager().getAccessLevel(query.getEntityDescriptor())==DataAccessManager.NONE)
186                 throw new SecurityException("No read access for "+query.getEntityDescriptor()+" entities!");
187                 
188             // Perform query.
189             PreparedStatement statement = mSQLToolKit.buildSelectStatement(query.getEntityDescriptor(), query.getQualifier(), connection);
190             
191             ResultSet set = statement.executeQuery();
192             
193             // Read the result.
194             IEntity entity;
195             while(set.next())
196             {
197                 // Create the entity
198                 entity = query.getEntityDescriptor().createEntity();
199                 this.readResultSetRow(set, entity, true);
200             
201                 // Update the entity status.
202                 entity.setStatusFlag(IEntity.PERSISTENT);
203                 entity.clearStatusFlag(IEntity.DIRTY | IEntity.EMPTY);
204                 
205                 // Store the entity if data READ access access is granted.
206                 if(DataAccessManager.getManager().hasReadAccess(entity))
207                     selection.addEntity(entity);
208             }
209             set.close();
210         }
211         catch(SQLException e) 
212         { 
213             throw new DataServiceException("Select command failed", e);
214         } 
215     }
216     
217     protected void executeInsert(Connection connection, IEntity entity) throws DataServiceException
218     {
219         String sql = null;
220         try
221         {
222             // Perform security access check.
223             this.checkEntityAsStorable(entity);
224 //            if(!DataAccessManager.getManager().hasWriteAccess(entity))
225 //                throw new SecurityException("No write access for "+entity+"!");
226                 
227             // Prepare update command.
228             PreparedStatement statement = mSQLToolKit.buildInsertStatement(entity, connection, true);
229             
230             Log.print(this, "Performing Insert: "+sql);
231             
232             // Perform update command.
233             int count = -1;
234             if ((mAutoIndexMethod==JDBC_AUTO_INDEX || mAutoIndexMethod==UNDEFINED_AUTO_INDEX) && (mConnectionURL.indexOf("hsqldb")==-1))
235             {
236                 try
237                 {
238                     count = statement.executeUpdate();
239                 }
240                 catch(Exception e)
241                 {
242                 }
243             }
244             if(count<0)
245             {
246                 statement = mSQLToolKit.buildInsertStatement(entity, connection, false);
247                 count = statement.executeUpdate();
248             }
249             
250             // Cast exception and cause roleback if single insert failed.
251             if(count!=1)
252                 throw new DataServiceException("Multiple insert commands are dissabled, affect count "+count+" caused rejection.");
253             
254             // Update autogenerated key indexes.
255             if(this.hasAutoIndexField(entity.getEntityDescriptor()))
256                 this.updateAutoIndex(statement, entity);
257         }
258         catch(SQLException e) 
259         { 
260             throw new DataServiceException("Insert command failed", e, sql);
261         } 
262     }
263     
264     protected void executeUpdate(Connection connection, IEntity entity, Qualifier qualifier) throws DataServiceException
265     {
266         try
267         {
268             // Perform security access check.
269             this.checkEntityAsStorable(entity);
270 //            if(!DataAccessManager.getManager().hasWriteAccess(entity))
271 //                throw new SecurityException("No write access for "+entity+"!");
272                 
273             // Cancel update if entity is not dirty.
274             if(!entity.isDirty())
275             {
276                 Log.printWarning(this, "Ignored update of non-dirty entity: "+entity);
277                 return;
278             }
279             
280             // Perform update command.
281             PreparedStatement statement = mSQLToolKit.buildUpdateStatement(entity, qualifier, connection);
282              
283             int count = statement.executeUpdate();
284             
285             // Cast exception and cause roleback if single update failed.
286             if(count!=1)
287                 throw new DataServiceException("Multiple update commands are dissabled, affect count "+count+" caused rejection.");
288         }
289         catch(SQLException e) 
290         { 
291             throw new DataServiceException("Update command failed", e);
292         } 
293     }
294     
295     protected void executeDelete(Connection connection, IEntity entity) throws DataServiceException
296     {
297         try
298         {
299             // Perform security access check.
300             this.checkEntityAsDeletable(entity);
301 //            if(!DataAccessManager.getManager().hasWriteAccess(entity))
302 //                throw new SecurityException("No write access for "+entity+"!");
303                 
304             // Perform delete command.
305             PreparedStatement statement = mSQLToolKit.buildDeleteStatement(entity.getEntityDescriptor(), entity.getOriginQualifier(), connection);
306             
307             int count = statement.executeUpdate();
308             if(count>1)
309                 throw new DataServiceException("Multiple delete commands are dissabled, affect count "+count+" caused rejection.");
310         }
311         catch(SQLException e) 
312         { 
313             throw new DataServiceException("Delete command failed", e);
314         } 
315     }
316     
317     // Help methods ------------------------------------------------------------
318     
319     protected Connection openConnection() throws DataServiceException
320     {
321         return mConnectionPool.getConnection();
322     }
323     
324     protected void closeConnection(Connection connection) throws DataServiceException
325     {
326         mConnectionPool.releaseConnection(connection);
327     }
328     
329     protected void readResultSetRow(ResultSet set, IEntity entity, boolean useFastSetData) throws DataServiceException
330     {
331         try
332         {
333             IEntityDescriptor descriptor = entity.getEntityDescriptor();
334             SQLToolKitPS.IDataTypeConverter converter =null;
335             DataType dataType = null;
336             Object data = null;
337             
338             for(int j=0; j<set.getMetaData().getColumnCount(); j++)
339             {
340             	// Get field data.
341                 dataType = descriptor.getFieldDescriptor(j).getDataType();
342                 
343                 // Convert field data.
344                 converter = mSQLToolKit.getDataTypeConverter(dataType);
345                 data = this.getSetData(set, j+1, dataType);
346                 if(converter!=null)
347                     data = converter.convertFromDB(data);
348                 else if(data!=null && data.getClass()!=dataType.getDataClass())
349                     data = dataType.convertFrom(data);
350                     
351                 // Set data to entity. Note that if the useFastSetData flag is
352                 // set we can avoid the more resource demanding set data method.
353                 if(useFastSetData)
354                     this.setEntityData(entity, j, data);
355                 else
356                     entity.setData(descriptor.getFieldDescriptor(j), data);
357             }
358         }
359         catch(Exception e) 
360         { 
361             throw new DataServiceException("Select command failed", e);
362         } 
363     }
364     
365     protected Object getSetData(ResultSet set, int index, DataType dataType)
366     {
367         try
368         {
369             Object data = set.getObject(index);
370             
371             // Fix to handle hadle JDBC drivers that are inaware data types.
372             if(data==null && !set.wasNull())
373             {
374                 if(dataType==DataType.STRING)
375                     data = set.getString(index);
376                 else if(dataType==DataType.BYTE)
377                     data = new Byte(set.getByte(index));
378                 else if(dataType==DataType.SHORT)
379                     data = new Short(set.getShort(index));
380                 else if(dataType==DataType.INTEGER)
381                     data = new Integer(set.getInt(index));
382                 else if(dataType==DataType.LONG)
383                     data = new Long(set.getLong(index));
384                 else if(dataType==DataType.FLOAT)
385                     data = new Float(set.getFloat(index));
386                 else if(dataType==DataType.DOUBLE)
387                     data = new Double(set.getDouble(index));
388                 else if(dataType==DataType.BOOLEAN)
389                     data = new Boolean(set.getBoolean(index));
390                 else if(dataType instanceof DataType.BinaryType)
391                     data = dataType.convertFrom(set.getBinaryStream(index)); // (set.getBytes(index));
392             }
393             return data;
394         }
395         catch(Exception e) 
396         { 
397             throw new DataServiceException("Select command failed", e);
398         } 
399     }
400         
401     /*** This method updates autogenerated primary key field values.
402      * Note that this method can only be used directly after a insert and
403      * should still not be considered safe in an environment with frequent 
404      * inserts to the entity's table.
405      */
406     protected void updateAutoIndex(Statement statement, IEntity entity) throws DataServiceException
407     {
408         String com = null;        
409         IFieldDescriptor field = null;
410         
411         if(mAutoIndexMethod==UNDEFINED_AUTO_INDEX)
412             Log.print(this, "Evaluating usable auto index method.");
413                 
414         // Standard java.sql method to access auto indexes. Requires 1.4 driver.
415         if((mAutoIndexMethod==JDBC_AUTO_INDEX || mAutoIndexMethod==UNDEFINED_AUTO_INDEX) && (mConnectionURL.indexOf("hsqldb")==-1))
416         {
417             try
418             {
419                 ResultSet set = statement.getGeneratedKeys();
420                 for(int j=0; j<entity.getEntityDescriptor().getFieldCount(); j++)
421                 {
422                     field = entity.getEntityDescriptor().getFieldDescriptor(j);
423                     if(field.isAutoGenerated() && field.isIdentityField())
424                     {
425                         // Read the result.
426                         if(set.next())
427                         {
428                             Object data = field.getDataType().convertFrom(set.getObject(1));
429                             entity.setData(field, data);
430                 
431                             mAutoIndexMethod = JDBC_AUTO_INDEX;
432                         }
433                     }
434                 }
435                 while(set.next());
436             }
437             catch(Exception e)
438             {
439                 if(mAutoIndexMethod==JDBC_AUTO_INDEX)
440                     throw new DataServiceException("Update of auto generated index failed!", e, com);
441             }
442             if(mAutoIndexMethod==UNDEFINED_AUTO_INDEX)
443                 Log.print(this, "JDBC method not applicable.");
444         }
445         
446         // PostgreSQL specific solution.
447         if(mAutoIndexMethod==POSTGRE_AUTO_INDEX || mAutoIndexMethod==UNDEFINED_AUTO_INDEX)
448         {
449             try
450             {
451                 for(int j=0; j<entity.getEntityDescriptor().getFieldCount(); j++)
452                 {
453                     field = entity.getEntityDescriptor().getFieldDescriptor(j);
454                     if(field.isAutoGenerated() && field.isIdentityField())
455                     {
456                         com = "SELECT currval('"+field.getEntityDescriptor().getSourceName()+"_"+field.getSourceName()+"_seq') ";
457                         Statement identityStatement = statement.getConnection().createStatement();
458                         Log.print(this, "Key retrieval: "+com);
459                         ResultSet set = identityStatement.executeQuery(com);
460 
461                         // Read the result.
462                         if(set.next())
463                         {
464                             Object data = field.getDataType().convertFrom(set.getObject(1));
465                             entity.setData(field, data);
466                 
467                             mAutoIndexMethod = POSTGRE_AUTO_INDEX;
468                         }
469                         while(set.next());
470                     }
471                 }
472             }
473             catch(Exception e) 
474             { 
475                 Log.printWarning(this, "Postgre method failed!");
476                 if(mAutoIndexMethod==POSTGRE_AUTO_INDEX)
477                     throw new DataServiceException("Update of auto generated index failed!", e, com);
478             } 
479             if(mAutoIndexMethod==UNDEFINED_AUTO_INDEX)
480                Log.print(this, "Postgre method not applicable.");
481         }
482         
483         // Microsoft SQL-Server specific solution.
484         if(mAutoIndexMethod==SQLSERVER_AUTO_INDEX || mAutoIndexMethod==UNDEFINED_AUTO_INDEX)
485         {
486             try
487             {
488                 for(int j=0; j<entity.getEntityDescriptor().getFieldCount(); j++)
489                 {
490                     field = entity.getEntityDescriptor().getFieldDescriptor(j);
491                     if(field.isAutoGenerated() && field.isIdentityField())
492                     {
493                         com = "SELECT @@IDENTITY FROM " + field.getEntityDescriptor().getSourceName();
494                         Statement identityStatement = statement.getConnection().createStatement();
495                         Log.print(this, "Key retrieval: "+com);
496                         ResultSet set = identityStatement.executeQuery(com);
497 
498                         // Read the result.
499                         if(set.next())
500                         {
501                             Object data = field.getDataType().convertFrom(set.getObject(1));
502                             entity.setData(field, data);
503                 
504                             mAutoIndexMethod = SQLSERVER_AUTO_INDEX;
505                         }
506                         while(set.next());
507                     }
508                 }
509             }
510             catch(Exception e) 
511             { 
512                 if(mAutoIndexMethod==SQLSERVER_AUTO_INDEX)
513                    throw new DataServiceException("Update of auto generated index failed!", e, com);
514             } 
515             if(mAutoIndexMethod==UNDEFINED_AUTO_INDEX)
516                 Log.print(this, "SQL-Server method not applicable.");
517         }
518         
519          // Oracle specific solution.
520         if(mAutoIndexMethod==ORACLE_AUTO_INDEX || mAutoIndexMethod==UNDEFINED_AUTO_INDEX)
521         {
522             try
523             {
524                 for(int j=0; j<entity.getEntityDescriptor().getFieldCount(); j++)
525                 {
526                     field = entity.getEntityDescriptor().getFieldDescriptor(j);
527                     if(field.isAutoGenerated() && field.isIdentityField())
528                     {
529                         com = "SELECT " + field.getEntityDescriptor().getSourceName() + "_" + field.getSourceName() + "_seq.currval FROM dual " + field.getEntityDescriptor().getSourceName();
530                         Statement identityStatement = statement.getConnection().createStatement();
531                         Log.print(this, "Key retrieval: "+com);
532                         ResultSet set = identityStatement.executeQuery(com);
533 
534                         // Read the result.
535                         if(set.next())
536                         {
537                             Object data = field.getDataType().convertFrom(set.getObject(1));
538                             entity.setData(field, data);
539                 
540                             mAutoIndexMethod = ORACLE_AUTO_INDEX;
541                         }
542                         while(set.next());
543                     }
544                 }
545             }
546             catch(Exception e) 
547             { 
548                 if(mAutoIndexMethod==ORACLE_AUTO_INDEX)
549                    throw new DataServiceException("Update of auto generated index failed!", e, com);
550             } 
551             if(mAutoIndexMethod==UNDEFINED_AUTO_INDEX)
552                 Log.print(this, "Oracle method not applicable.");
553         }
554         
555         // Oracle specific solution.
556        if(mAutoIndexMethod==HSQLDB_AUTO_INDEX || mAutoIndexMethod==UNDEFINED_AUTO_INDEX)
557        {
558            try
559            {
560                for(int j=0; j<entity.getEntityDescriptor().getFieldCount(); j++)
561                {
562                    field = entity.getEntityDescriptor().getFieldDescriptor(j);
563                    if(field.isAutoGenerated() && field.isIdentityField())
564                    {
565                        com = "call identity()";
566                        Statement identityStatement = statement.getConnection().createStatement();
567                        Log.print(this, "Key retrieval: "+com);
568                        ResultSet set = identityStatement.executeQuery(com);
569 
570                        // Read the result.
571                        if(set.next())
572                        {
573                            Object data = field.getDataType().convertFrom(set.getObject(1));
574                            entity.setData(field, data);
575         
576                            mAutoIndexMethod = HSQLDB_AUTO_INDEX;
577                        }
578                        while(set.next());
579                    }
580                }
581            }
582            catch(Exception e) 
583            { 
584                if(mAutoIndexMethod==HSQLDB_AUTO_INDEX)
585                   throw new DataServiceException("Update of auto generated index failed!", e, com);
586            } 
587            if(mAutoIndexMethod==UNDEFINED_AUTO_INDEX)
588                Log.print(this, "hsqldb method not applicable.");
589        }
590         
591         
592         
593         if(mAutoIndexMethod==UNSUPORTED_AUTO_INDEX || mAutoIndexMethod==UNDEFINED_AUTO_INDEX)
594         {
595             mAutoIndexMethod=UNSUPORTED_AUTO_INDEX;
596             throw new DataServiceException("Used JDBC Driver does not support extraction of Generated Keys!");    
597         }
598     }
599     
600     protected boolean hasAutoIndexField(IEntityDescriptor entityDescriptor)
601     {
602         IFieldDescriptor autoIndexField = null;
603         for(int j=0; autoIndexField==null && j<entityDescriptor.getFieldCount(); j++)
604             if(entityDescriptor.getFieldDescriptor(j).isAutoGenerated() && entityDescriptor.getFieldDescriptor(j).isIdentityField())
605                 autoIndexField = entityDescriptor.getFieldDescriptor(j);
606         return autoIndexField!=null;
607     }
608     
609     // Access methods ----------------------------------------------------------
610     public String getURL() 
611     {
612         return mConnectionURL;
613     }
614     
615     public String getUser() 
616     {
617         return mConnectionInfo.getProperty("user", null);
618     }
619     
620     public String getPassword() 
621     {
622         return mConnectionInfo.getProperty("password", null);
623     }
624     
625     public SQLToolKitPS getSQLToolKit()
626     {
627         return mSQLToolKit;
628     }
629     
630     public void setSQLToolKit(SQLToolKitPS kit)
631     {
632         mSQLToolKit = kit;
633     }
634     
635     // Nested classes ----------------------------------------------------------
636     protected class JDBCDataTransaction extends AbstractDataTransaction
637     {
638         // Data members --------------------------------------------------------
639         private Connection mConnection;
640         
641         // Constructors --------------------------------------------------------
642         public JDBCDataTransaction()
643         {
644             super(getTimeout());
645         }
646         
647         // Superclass overrides ------------------------------------------------
648         
649         /*** Commit performs all the stored operations in the transaction. 
650          * If any of the operations fail a rollback on all operations will be
651          * automatically performed and a TransactionFailedException will be thrown.
652          */
653         public void commit() throws DataServiceException
654         {
655             long commitStartTime = System.currentTimeMillis();
656             
657             // Get a connection.
658             mConnection = openConnection();
659             
660             // Log warning for long connection access time.
661             if(System.currentTimeMillis()-commitStartTime>50)
662             Log.printWarning(this, "Long connection open time: " + (System.currentTimeMillis() - commitStartTime) + " ms");
663             
664             // Execute all operations
665             try
666             {
667                 try
668                 {
669                     if(mConnectionInfo.containsKey("catalog"))
670                         mConnection.setCatalog((String) mConnectionInfo.get("catalog"));
671                 }
672                 catch (SQLException e) 
673                 {
674                 }
675                 
676                 // Perform operations
677                 mConnection.setAutoCommit(false);
678                 Enumeration dataOperations = this.getOperations();
679                 while(dataOperations.hasMoreElements())
680                 {
681                     DataOperation operation = (DataOperation)dataOperations.nextElement();
682                     switch(operation.getOperationType())
683                     {
684                         case DataOperation.LOAD:
685                             executeLoad(mConnection, operation.getEntity(), operation.getQualifier());
686                         break;
687                         case DataOperation.QUERY:
688                             executeQuery(mConnection, operation.getDataQuery(), operation.getEntitySelection());
689                         break;
690                         case DataOperation.STORE:
691                             if(operation.getEntity().isPersistent())
692                                 executeUpdate(mConnection, operation.getEntity(), operation.getQualifier());
693                             else
694                                 executeInsert(mConnection, operation.getEntity());
695                         break;
696                         case DataOperation.DELETE:
697                             executeDelete(mConnection, operation.getEntity());
698                         break;
699                         case DataOperation.REFRESH:
700                             executeLoad(mConnection, operation.getEntity(), operation.getQualifier());
701                         break;			
702                     }
703                 }
704                 
705                 // Commit transaction.
706                 mConnection.commit();
707                 Log.print(this, "Commit time: " + (System.currentTimeMillis() - commitStartTime) + " ms");
708                 
709                 // Update flags.
710                 dataOperations = this.getOperations();	
711                 while(dataOperations.hasMoreElements())
712                 {		
713                     DataOperation operation = (DataOperation)dataOperations.nextElement();
714                     switch(operation.getOperationType())
715                     {	
716                         case DataOperation.DELETE:
717                             operation.getEntity().clearStatusFlag(IEntity.DIRTY | IEntity.EMPTY | IEntity.PERSISTENT);
718                         break;
719                         case DataOperation.STORE:
720                             operation.getEntity().setStatusFlag(IEntity.PERSISTENT);
721                             operation.getEntity().clearStatusFlag(IEntity.DIRTY | IEntity.EMPTY);
722                         break;	
723                         case DataOperation.REFRESH: // Sets flags independant of transaction sucess!!
724                         case DataOperation.LOAD:
725                         case DataOperation.QUERY:
726                         break;
727                     }
728                 }                
729             }
730             catch(Exception e)
731             {
732                 String rollbackMsg;
733                 
734                 // Perform rollback on connection.
735                 try
736                 {
737                     mConnection.rollback();
738                     rollbackMsg = "rollback succesfull";
739                 }
740                 catch(SQLException eSQL)
741                 {   
742                     rollbackMsg = "rollback failed";
743                 }
744                 
745                 // Throw proper API exception. 
746                 if(e instanceof DataServiceException)
747                     throw new DataServiceException("Transaction failed, "+rollbackMsg, e, ((DataServiceException)e).getDescription());
748                 else
749                     throw new DataServiceException("Transaction failed, "+rollbackMsg+": "+e.getClass().getName()+" - "+e.getMessage(), e);
750             }
751             finally
752             {
753                 // Return the connection for closuere or pooling.
754                 closeConnection(mConnection);
755                 mConnection = null;
756             }
757 
758             // Adjust entity flags.
759         }
760         
761         public void abortTransaction() throws DataServiceException
762         {
763             try
764             {
765                 if (mConnection != null)
766                 {
767                     if (mConnection.getAutoCommit() == false)
768                         mConnection.rollback();
769                         
770                     mConnection.close();
771                     mConnection = null;
772                 }
773             } catch (SQLException e)
774             {
775                 throw new DataServiceException("Failed to abort transaction", e);
776             }
777         }
778     }
779     
780     protected class ConnectionPool
781     {
782         // Data members --------------------------------------------------------
783         private int mMaxSize;
784         private Connection[] mConnectionPool;
785         private boolean[] mFreeConnectionFlags;
786         private List mWaitingThreads;
787         
788         // Constructors --------------------------------------------------------
789         
790         /*** Creates a connection pool with the maximum size <code>maxSize</code>.
791          * Setting the maximum size to zero means that every call to <code>getConnection</code>
792          * will create a new connection.
793          */
794         public ConnectionPool(int maxSize)
795         {
796             mMaxSize = maxSize;
797             if (maxSize > 0)
798             {
799                 mConnectionPool = new Connection[maxSize];
800                 mFreeConnectionFlags = new boolean[maxSize];
801                 mWaitingThreads = new ArrayList();
802             }
803         }
804         
805         // Access methods ------------------------------------------------------
806         public Connection getConnection() throws DataServiceException
807         {
808             // If the pool size is zero just create a new connection
809             if(mMaxSize == 0)
810             {
811                 try
812                 {
813                     if (sDriver != null)
814                         return sDriver.connect(mConnectionURL, mConnectionInfo);
815 
816                     if (mConnectionInfo.get("user") != null)
817                         return DriverManager.getConnection(mConnectionURL, mConnectionInfo);
818                     else
819                         return DriverManager.getConnection(mConnectionURL);
820                 }
821                 catch(SQLException e)
822                 { 
823                     throw new DataServiceException("Failed to open database connection: " + e.getMessage());
824                 }
825             }
826             
827             // Get a free connection from the connection pool
828             //Connection conn = null;
829             try
830             {
831                 return this.getConnectionFromPool();
832                 /*while ((conn = this.getNextFreeConnection()) == null)
833                     this.wait();*/
834             }
835             catch (Exception e)
836             {
837                 throw new DataServiceException("Failed to open database connection: " + e.getMessage());
838             }
839             //return conn;
840         }
841         
842         public int getMaxSize()
843         {
844             return mMaxSize;
845         }
846         
847         // Action methods ------------------------------------------------------
848         public synchronized void releaseConnection(Connection connection)
849             throws DataServiceException
850         {
851             if(connection == null)
852                 return;
853             
854             // If the pool size is zero just create a new connection
855             if(mMaxSize == 0)
856             {
857                 try
858                 {
859                     connection.close();
860                     return;
861                 }
862                 catch(SQLException e)
863                 { 
864                     throw new DataServiceException("Failed to open database connection: " + e.getMessage());
865                 }
866             }
867             
868             // Find the index of the connection
869             int index = 0;
870             for(; index < mConnectionPool.length && mConnectionPool[index] != connection; index++);
871             
872             // Mark the connection as free
873             if(index < mConnectionPool.length)
874             {
875                 mFreeConnectionFlags[index] = true;
876                 this.notifyAll();
877             }
878         }
879         
880         // Help methods --------------------------------------------------------
881         private synchronized Connection getConnectionFromPool()
882             throws SQLException, InterruptedException
883         {
884             Connection conn = null;
885             // If there are no threads waiting for a connection try to get a free connection
886             if(mWaitingThreads.size() == 0)
887                 conn = this.getNextFreeConnectionFromPool();
888             
889             // If there was no free connection add this thread to the queue of waiting threads
890             if(conn == null)
891                 mWaitingThreads.add(Thread.currentThread());
892             
893             while(conn == null)
894             {
895                 Log.print(this, "Waiting for free connection.");
896                 this.wait();
897                 // Only try to get a free connection if the current thread is first in the queue
898                 if (mWaitingThreads.get(0) == Thread.currentThread())
899                     conn = this.getNextFreeConnectionFromPool();
900             }
901             
902             // Remove the current thread from the queue if necessary
903             if(mWaitingThreads.contains(Thread.currentThread()));
904                 mWaitingThreads.remove(Thread.currentThread());
905                 
906             return conn;
907         }
908         
909         private synchronized Connection getNextFreeConnectionFromPool()
910             throws SQLException
911         {
912             int index = 0;
913             
914             // Find the index of the first free connection in the connection pool
915             for(; index < mConnectionPool.length && mConnectionPool[index] != null && !mFreeConnectionFlags[index]; index++);
916             
917             if (index >= mConnectionPool.length)
918                 // No free connections were found and the pool contains the maxinum number of
919                 // allowed connections
920                 return null;
921             
922             if(mConnectionPool[index] != null)
923             {
924                 // Found a free connection, mark it as not free and return it
925                 mFreeConnectionFlags[index] = false;
926 //                Log.print(this, "Returned connection with index " + index);
927                 
928                 if (mConnectionPool[index].isClosed())
929                     mConnectionPool[index] = this.openNewConnection();
930 
931                 return mConnectionPool[index];
932             }
933             
934             // Increase the number of connections in the pool by one and return the new connection
935             mConnectionPool[index] = this.openNewConnection();
936                 
937             Log.print(this, "Extending connection pool for "+getServiceIdentity()+" to " + (index+1) + " connections.");
938             return mConnectionPool[index];
939         }
940         
941         private Connection openNewConnection() throws SQLException
942         {
943             if (sDriver != null)
944                 return sDriver.connect(mConnectionURL, mConnectionInfo);
945                 
946             if (mConnectionInfo.get("user") != null)
947                 return DriverManager.getConnection(mConnectionURL, mConnectionInfo);
948             else
949                 return DriverManager.getConnection(mConnectionURL);
950         }
951     }
952 }