KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > bsf > remoteIterator > server > RemoteIteratorBean


1 /* ******************************************************************************** *
2  * Copyright (c) 2002 - 2004 Bright Side Factory. All rights reserved. *
3  * *
4  * Redistribution and use in source and binary forms, with or without modification, *
5  * are permitted provided that the following conditions are met: *
6  * *
7  * 1. Redistributions of source code must retain the above copyright notice, this *
8  * list of conditions and the following disclaimer. *
9  * *
10  * 2. Redistributions in binary form must reproduce the above copyright notice, *
11  * this list of conditions and the following disclaimer in the documentation and/or *
12  * other materials provided with the distribution. *
13  * *
14  * 3. The end-user documentation included with the redistribution, if any, must *
15  * include the following acknowledgment: "This product includes software developed *
16  * by the Bright Side Factory (http://www.bs-factory.org/)." Alternately, this *
17  * acknowledgment may appear in the software itself, if and wherever such *
18  * third-party acknowledgments normally appear. *
19  * *
20  * 4. The names "Bright Side", "BS Framework" and "Bright Side Factory" must not be *
21  * used to endorse or promote products derived from this software without prior *
22  * written permission. For written permission, please contact info@bs-factory.org. *
23  * *
24  * 5. Products derived from this software may not be called "Bright Side", nor may *
25  * "Bright Side" appear in their name, without prior written permission of the *
26  * Bright Side Factory. *
27  * *
28  * THIS SOFTWARE IS PROVIDED ''AS IS'' BY THE COPYRIGHT OWNER AND ANY EXPRESSED OR *
29  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF *
30  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT *
31  * SHALL THE COPYRIGHT OWNER OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, *
32  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, *
33  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, *
34  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF *
35  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE *
36  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS OFTWARE, EVEN IF ADVISED *
37  * OF THE POSSIBILITY OF SUCH DAMAGE. *
38  * *
39  * ================================================================================ *
40  * *
41  * This software consists of voluntary contributions made by many individuals on *
42  * behalf of the Bright Side Factory. For more information on the Bright Side *
43  * Factory, please see http://www.bs-factory.org. *
44  * *
45  * ******************************************************************************** */

46 package org.bsf.remoteIterator.server;
47
48 import org.bsf.commons.ejb.SessionAdapterBean;
49 import org.bsf.remoteIterator.common.ColumnMetadata;
50 import org.bsf.remoteIterator.common.RemoteIteratorResultTable;
51
52 import javax.ejb.CreateException JavaDoc;
53 import java.sql.*;
54 import java.util.ArrayList JavaDoc;
55 import java.util.List JavaDoc;
56
57 /**
58  * Executes and provides an iterator on a SQL statement using the Bean container
59  * to handle passivation and so on... Usually used to populate a Table on the
60  * client without transfering all the rows at once.
61  *
62  * @ejb:bean type="Stateful"
63  * name="RemoteIterator"
64  * jndi-name="ejb/RemoteIterator"
65  * generate="true"
66  * view-type="remote"
67  *
68  * @ejb:home extends="javax.ejb.EJBHome"
69  *
70  * @ejb:interface extends="javax.ejb.EJBObject"
71  *
72  * @ejb:transaction type="Supports"
73  *
74  * @ejb:resource-ref res-name="jdbc/DBPool"
75  * res-type="javax.sql.DataSource"
76  * res-auth="Container"
77  *
78  * @jboss:resource-ref res-ref-name="jdbc/DBPool"
79  * resource-name="FwkDataSourceManager"
80  *
81  * @jonas.bean ejb-name="RemoteIterator"
82  * jndi-name="ejb/RemoteIterator"
83  *
84  * @jonas.resource res-ref-name="jdbc/DBPool"
85  * jndi-name="jdbc_1"
86  */

87 public class RemoteIteratorBean extends SessionAdapterBean {
88     // *************************************************************************
89
// *************************** Static variables ****************************
90
// *************************************************************************
91

92     // Various constants (self explanatory)
93
private static final int RI_BUFFER_SIZE = 2000;
94     private static final int RI_DEFAULT_BLOCK_SIZE = 150;
95
96     private static final String JavaDoc EMPTY_STRING = "";
97     private static final String JavaDoc JNDI_DATASOURCE_NAME = "DBPool";
98
99     // *************************************************************************
100
// *************************** Instance variables **************************
101
// *************************************************************************
102

103     /**
104      * Keeps the size of the last read block.
105      */

106     private int _lastReadBlockSize = 0;
107
108     /**
109      * The curent position into the resultset (needed when passivated or
110      * activated).
111      */

112     private int _position = 0;
113
114     private String JavaDoc _SQLQuery;
115     private List JavaDoc _columnMetadatas;
116     private int _defaultBlockSize = RI_DEFAULT_BLOCK_SIZE;
117
118     // TRANSIENT FIELDS (Cnx informations, we need them... :o)
119
private transient Statement _statement;
120     private transient ResultSet _resultSet;
121     private transient Connection _connection;
122
123     // *************************************************************************
124
// *************************** Utility Methods *****************************
125
// *************************************************************************
126

127     /**
128      * Creates the meta data from the actual resultsets.
129      */

130     private void createMetadatas() {
131         _columnMetadatas = new ArrayList JavaDoc();
132
133         try {
134             ResultSetMetaData resultSetMetaData = _resultSet.getMetaData();
135
136             int nbColums = resultSetMetaData.getColumnCount();
137
138             for ( int col = 1 ; col <= nbColums ; col++ ) {
139                 ColumnMetadata temp = new ColumnMetadata();
140
141                 temp.setColumnClassName( resultSetMetaData.getColumnClassName( col ) );
142                 temp.setColumnName( resultSetMetaData.getColumnName( col ) );
143                 temp.setSchemaName( resultSetMetaData.getSchemaName( col ) );
144                 temp.setTableName( resultSetMetaData.getTableName( col ) );
145                 temp.setColumnType( resultSetMetaData.getColumnType( col ) );
146                 temp.setColumnTypeName( resultSetMetaData.getColumnTypeName( col ) );
147                 temp.setPrecision( resultSetMetaData.getPrecision( col ) );
148                 temp.setScale( resultSetMetaData.getScale( col ) );
149
150                 _columnMetadatas.add( temp );
151             }
152         } catch( SQLException exception ) {
153             handleSQLException( exception );
154         }
155     }
156
157     /**
158      * @param p_query The SQL statement to execute. It should be a valid SQL statement
159      * otherwise you'll get an SQL exception later :o) !!!
160      *
161      * @throws java.lang.RuntimeException if the given query is null or blank.
162      *
163      * @ejb:create-method
164      */

165     public void ejbCreate( String JavaDoc p_query ) throws CreateException JavaDoc {
166         logGraphBegin( "create" );
167
168         // We check if the given p_query is valid...
169
if ( p_query == null || EMPTY_STRING.equals( p_query ) ) {
170             handleExceptionAsSystemException( new RuntimeException JavaDoc( "The p_query shouldn't be null !!!" ) );
171         }
172
173         // We whall keep the query
174
_SQLQuery = p_query;
175
176         // Let's go for a first execution of the SQL statement
177
firstQueryExecution();
178
179         // We create the MetaData (could be requested by the client)
180
createMetadatas();
181
182         logGraphEnd( "create" );
183     }
184
185     /**
186      * Ensures that everything is closed correctly (mainly the DB ressources).
187      */

188     public void ejbRemove() {
189         logGraphBegin( "ejbRemove " );
190
191         // close connection
192
closeDataSource();
193
194         logGraphEnd( "ejbRemove" );
195     }
196
197     // *************************************************************************
198
// ******************************* Accessors *******************************
199
// *************************************************************************
200

201     /**
202      * @return A List of ColumnMetaData corresponding to the executed SQL statement.
203      *
204      * @see org.bsf.remoteIterator.common.ColumnMetadata
205      *
206      * @ejb:interface-method
207      */

208     public List JavaDoc getColumnMetaData() {
209         return _columnMetadatas;
210     }
211
212     /**
213      * @return the block of rows starting at the given position and of the default
214      * block size.
215      *
216      * @see #absolute(int p_position, int p_blockSize)
217      * @see org.bsf.remoteIterator.common.RemoteIteratorResultTable
218      *
219      * @ejb:interface-method
220      */

221     public RemoteIteratorResultTable absolute( int p_position ) {
222         return absolute( p_position, RI_DEFAULT_BLOCK_SIZE );
223     }
224
225     /**
226      * @return the block of rows starting at the given position and of the given
227      * size.
228      *
229      * @see #absolute(int p_position)
230      * @see org.bsf.remoteIterator.common.RemoteIteratorResultTable
231      *
232      * @ejb:interface-method
233      */

234     public RemoteIteratorResultTable absolute( int p_position, int p_blockSize ) {
235         try {
236             _resultSet.absolute( p_position );
237         } catch( SQLException sqlException ) {
238             // Unable to reach the desired position...
239
handleSQLException( sqlException );
240         }
241
242         // We managed to set the cursor at the desired position, let's return the rows...
243
return next( p_blockSize );
244     }
245
246     /**
247      * @return the previous "block" of rows (starting from before the first row
248      * of the actual block) as a RemoteIteratorResultTable. The default block
249      * size is used. Returns the default block size rows from the begining
250      * if the default block size to use is bigger than the available rows.
251      *
252      * @see #previous(int p_blockSize)
253      * @see org.bsf.remoteIterator.common.RemoteIteratorResultTable
254      *
255      * @ejb:interface-method
256      */

257     public RemoteIteratorResultTable previous() {
258         return previous( _defaultBlockSize );
259     }
260
261     /**
262      * @param p_blockSize the size of the block to return. Should be greater than
263      * zero (not checked).
264      *
265      * @return the previous p_blockSize rows (starting from the actual position) as a
266      * RemoteIteratorResultTable. Return to the beginning of the actual block before
267      * returning the previous rows. Returns the p_blockSize rows from the begining
268      * if the given block size is bigger than the available rows.
269      *
270      * @see org.bsf.remoteIterator.common.RemoteIteratorResultTable
271      *
272      * @ejb:interface-method
273      */

274     public RemoteIteratorResultTable previous( int p_blockSize ) {
275         String JavaDoc logMessage = "previous(" + p_blockSize + ")";
276         logGraphBegin( logMessage );
277
278         // Creating a RemoteIteratorResultTable
279
RemoteIteratorResultTable remoteIteratorResultTable = new RemoteIteratorResultTable( 0 );
280
281         try {
282             // We have to move to (begining of the actual block - block size)
283
int positionToReach = _resultSet.getRow() - _lastReadBlockSize - p_blockSize;
284
285             if ( positionToReach <= 0 ) {
286                 _resultSet.beforeFirst();
287             } else {
288                 _resultSet.absolute( positionToReach );
289             }
290             remoteIteratorResultTable = next( p_blockSize );
291         } catch( SQLException e ) {
292             // That might happen...
293
handleSQLException( e );
294         }
295
296         logGraphEnd( logMessage );
297
298         return remoteIteratorResultTable;
299     }
300
301     /**
302      * @return the next "block" of rows (starting from the actual position) as a
303      * RemoteIteratorResultTable. The default block size is used.
304      *
305      * @see #next(int p_blockSize)
306      * @see org.bsf.remoteIterator.common.RemoteIteratorResultTable
307      *
308      * @ejb:interface-method
309      */

310     public RemoteIteratorResultTable next() {
311         return next( _defaultBlockSize );
312     }
313
314     /**
315      * @param p_blockSize the size of the block to return. Should be greater than
316      * zero (not checked).
317      *
318      * @return the next p_blockSize rows (starting from the actual position) as a
319      * RemoteIteratorResultTable.
320      *
321      * @see org.bsf.remoteIterator.common.RemoteIteratorResultTable
322      *
323      * @ejb:interface-method
324      */

325     public RemoteIteratorResultTable next( int p_blockSize ) {
326         String JavaDoc logMessage = "next(" + p_blockSize + ")";
327         logGraphBegin( logMessage );
328
329         int readBlockPosition = 0;
330         boolean willBeFirst = false;
331
332         // Creating a RemoteIteratorResultTable
333
RemoteIteratorResultTable remoteIteratorResultTable = new RemoteIteratorResultTable( p_blockSize );
334
335         try {
336             if ( isBeforeFirst() ) {
337                 // We know that after the move we will be at the end of the first block
338
willBeFirst = true;
339             }
340
341             if ( isAfterLast() ) {
342                 // We have already reached the end of data
343
remoteIteratorResultTable.setAlreadyLast( true );
344                 remoteIteratorResultTable.setLast( true );
345
346                 // No need to go on
347
return remoteIteratorResultTable;
348             }
349
350             // We can read the rows requested rows
351

352             // The lastNotReached test is needed because the isAfterLast returns
353
// false if there is no data...
354
boolean lastNotReached = true;
355
356             while ( ( readBlockPosition < p_blockSize ) && ( lastNotReached = _resultSet.next() ) ) {
357                 // Found one... Add it to the RemoteIteratorResultTable...
358
addCurrentResultSetRow( remoteIteratorResultTable );
359
360                 // We increment the position in the current block (ie. Next please !!!)
361
readBlockPosition++;
362
363                 // if last record was reached, we set thtotal size
364
if ( isLast() ) {
365                     logDebug( "end position found :" + getPosition() );
366                 }
367             }
368
369             // We keep the size of the last read block
370
_lastReadBlockSize = readBlockPosition;
371
372             // If needed, we notify that we have read the first record (ie. the block is containing the first record)
373
if ( willBeFirst ) {
374                 remoteIteratorResultTable.setFirst( ( true ) );
375             }
376
377             // If needed, we notify that we have read the last record
378
if ( !lastNotReached || isAfterLast() || isLast() ) {
379                 remoteIteratorResultTable.setLast( ( true ) );
380             }
381         } catch( SQLException e ) {
382             // That might happen on the next...
383
handleSQLException( e );
384         }
385
386         logGraphEnd( logMessage );
387
388         return remoteIteratorResultTable;
389     }
390
391     /**
392      * @return the total number of records (the cursor remains on its current
393      * position).
394      *
395      * @ejb:interface-method
396      */

397     public Long JavaDoc getRowCount() {
398         logGraphBegin( "getRowCount" );
399
400         long totalSize = 0;
401
402         try {
403             // We retrieve the current position (to restore it, cf. below)
404
int currentPosition = getPosition();
405
406             // We move to the last record
407
_resultSet.last();
408
409             // We get the last record position;
410
totalSize = getPosition();
411
412             // We restore the cursor previous position
413
if ( currentPosition == 0 ) {
414                 _resultSet.beforeFirst();
415             } else {
416                 _resultSet.absolute( currentPosition );
417             }
418         } catch( SQLException eSQL ) {
419             handleSQLException( eSQL );
420         }
421
422         logGraphEnd( "getRowCount: found " + totalSize );
423
424         return new Long JavaDoc( totalSize );
425     }
426
427     /**
428      * @return true if the current block is the last block.
429      */

430     private boolean isLast() throws SQLException {
431         return _resultSet.isLast();
432     }
433
434     /**
435      * @return true if we are after the last record
436      */

437     private boolean isAfterLast() throws SQLException {
438         return _resultSet.isAfterLast();
439     }
440
441     /**
442      * @return true if the cursor is before the first record, false otherwise.
443      */

444     private boolean isBeforeFirst() throws SQLException {
445         return _resultSet.isBeforeFirst();
446     }
447
448     /**
449      * @return The current position of the cursor in the ResultSet, 0 if the
450      * resultset is null.
451      */

452     private int getPosition() throws SQLException {
453         if ( _resultSet == null )
454             return 0;
455         else
456             return _resultSet.getRow();
457     }
458
459     /**
460      * Sets the cursor to the specified position and update the lastReadBlockSize.
461      */

462     private void restorePosition( int p_position, int p_lastReadBlockSize ) throws SQLException {
463         if ( p_position > 0 ) {
464             _resultSet.absolute( p_position );
465         }
466
467         _lastReadBlockSize = p_lastReadBlockSize;
468     }
469
470     /**
471      * Adds the current record to the given RemoteIteratorResultTable.
472      *
473      * @throws java.sql.SQLException if there's a problem in the retrieval of the record...
474      * @throws java.lang.NullPointerException if the given ResultTable is null.
475      *
476      * @see org.bsf.remoteIterator.common.RemoteIteratorResultTable
477      */

478     private void addCurrentResultSetRow( RemoteIteratorResultTable p_resultTable ) throws SQLException {
479         int columnCount = _resultSet.getMetaData().getColumnCount();
480         ArrayList JavaDoc row = new ArrayList JavaDoc( columnCount );
481
482         for ( int columnIndex = 0 ; columnIndex < columnCount ; columnIndex++ ) {
483             // We add the default object returned by the driver
484
row.add( _resultSet.getObject( columnIndex + 1 ) );
485         }
486
487         // And we add the created row to the ResultTable
488
p_resultTable.addRow( row, new Integer JavaDoc( _resultSet.getRow() ) );
489     }
490
491     /**
492      * Must be called before executeQuery as it creates the statement and configures
493      * it.
494      */

495     private void prepareQuery() throws SQLException {
496         logGraphBegin( "prepareQuery" );
497
498         // We create a new statement (scroll insensible)
499
_statement = _connection.createStatement( ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY );
500
501         // To optimize the fetch (could be tuned)
502
_statement.setFetchSize( RemoteIteratorBean.RI_BUFFER_SIZE );
503
504         logGraphEnd( "prepareQuery" );
505     }
506
507     /**
508      * Prepares and executes the SQL query.
509      *
510      * @throws java.sql.SQLException if there's a problem while preparing or executing the SQL.
511      */

512     private void executeQuery() throws SQLException {
513         logGraphBegin( "executeQuery \nQuery : " + _SQLQuery );
514
515         try {
516             // We prepare the query (cf. above)
517
prepareQuery();
518
519             // We reset the logical position information
520
_lastReadBlockSize = 0;
521
522             if ( _statement == null )
523                 throw new SQLException( "Statement null ! You must call prepareQuery() before any executeQuery() ! : " );
524
525             // We execute query
526
_resultSet = _statement.executeQuery( _SQLQuery );
527         } catch( SQLException sqlException ) {
528             logFatal( "executeQuery : error while executing query " + _SQLQuery, sqlException );
529
530             throw sqlException;
531         }
532     }
533
534     /**
535      * Should be called for the first execution of the Query (opens the DataSource
536      * and executes the query).
537      */

538     private void firstQueryExecution() {
539         // Opens the database and executes the query
540
try {
541             openDataSource();
542
543             executeQuery();
544         } catch( SQLException Se ) {
545             // We cleanup the situation before throwing the exception
546
closeDataSource();
547             handleSQLException( Se );
548         }
549     }
550
551     /**
552      * Retrieves a connection to the Database.
553      */

554     private void openDataSource() throws SQLException {
555         logGraphBegin( "openDataSource" );
556
557         _connection = getConnection( JNDI_DATASOURCE_NAME );
558     }
559
560     /**
561      * Closes the DB ressources (ResultSet, Statement, Connection).
562      */

563     private void closeDataSource() {
564         logGraphBegin( "closeDataSource" );
565
566         try {
567             if ( _resultSet != null ) _resultSet.close();
568         } catch( SQLException Se ) {
569             logError( Se.getLocalizedMessage() );
570         }
571
572         try {
573             if ( _statement != null ) _statement.close();
574         } catch( SQLException e ) {
575             logError( e.getLocalizedMessage() );
576         }
577
578         try {
579             if ( _connection != null ) _connection.close();
580         } catch( SQLException e ) {
581             logError( e.getLocalizedMessage() );
582         }
583
584         logGraphEnd( "closeDataSource" );
585     }
586
587     /**
588      * Sets the default block size to be used for this RemoteIteratorBean.
589      *
590      * @ejb:interface-method
591      */

592     public void setDefaultBlockSize( Integer JavaDoc p_defaultBlockSize ) {
593         if ( p_defaultBlockSize == null )
594             _defaultBlockSize = RI_DEFAULT_BLOCK_SIZE;
595         else
596             _defaultBlockSize = p_defaultBlockSize.intValue();
597     }
598
599     /////////////////////////////////////////////////////////////////////////
600
// TECHNICAL CONTRACT
601
/////////////////////////////////////////////////////////////////////////
602

603     public void ejbActivate() {
604         logGraphBegin( "ejbActivate" );
605
606         try {
607             // Save postion before being reinitialized by prepareQuery;
608
int oldPosition = _position;
609             int oldLastReadBlockSize = _lastReadBlockSize;
610
611             // Restoring the connection
612
firstQueryExecution();
613
614             // We restore the "old" position
615
restorePosition( oldPosition, oldLastReadBlockSize );
616         } catch( SQLException Se ) {
617             handleSQLException( Se );
618         }
619
620         logDebug( " State after reactivation : lastReadBlockSize = " + _lastReadBlockSize + ", position = " + _position );
621
622         logGraphEnd( "ejbActivate " );
623     }
624
625     public void ejbPassivate() {
626         logGraphBegin( "ejbPassivate " );
627
628         try {
629             if ( _resultSet != null ) {
630                 // We keep the current position (for activation...)
631
_position = getPosition();
632             } else {
633                 _position = 0;
634             }
635
636             // We release the connection
637
closeDataSource();
638         } catch( SQLException e ) {
639             handleSQLException( e );
640         }
641
642         logDebug( " State for passivation: lastReadBlockSize = " + _lastReadBlockSize + ", position = " + _position );
643         logGraphEnd( "ejbPassivate" );
644     }
645 }
Popular Tags