KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > sape > carbon > services > uniqueid > DefaultUniqueIDServiceImpl


1 /*
2  * The contents of this file are subject to the Sapient Public License
3  * Version 1.0 (the "License"); you may not use this file except in compliance
4  * with the License. You may obtain a copy of the License at
5  * http://carbon.sf.net/License.html.
6  *
7  * Software distributed under the License is distributed on an "AS IS" basis,
8  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
9  * the specific language governing rights and limitations under the License.
10  *
11  * The Original Code is The Carbon Component Framework.
12  *
13  * The Initial Developer of the Original Code is Sapient Corporation
14  *
15  * Copyright (C) 2003 Sapient Corporation. All Rights Reserved.
16  */

17
18 package org.sape.carbon.services.uniqueid;
19
20 import java.sql.Connection JavaDoc;
21 import java.sql.PreparedStatement JavaDoc;
22 import java.sql.ResultSet JavaDoc;
23 import java.sql.SQLException JavaDoc;
24
25 import org.sape.carbon.core.component.ComponentConfiguration;
26 import org.sape.carbon.core.component.lifecycle.Configurable;
27 import org.sape.carbon.core.config.InvalidConfigurationException;
28 import org.sape.carbon.core.exception.ExceptionUtility;
29 import org.sape.carbon.services.sql.StatementFactory;
30 import org.sape.carbon.services.sql.StatementFactoryException;
31 import org.sape.carbon.services.sql.connection.ConnectionFactory;
32
33 import org.apache.commons.logging.Log;
34 import org.apache.commons.logging.LogFactory;
35
36 /**
37  * Default implementation of UniqueIDService interface.
38  * This implementation uses a database to store the next unique id. It uses
39  * transactions at the database level to ensure ID uniqueness across a cluster
40  * of machines and synchronization to ensure that this component does not
41  * return the same ID twice. To decrease database hits, this implementation
42  * stores a block of IDs with each database request instead of querying the
43  * database for each ID.
44  *
45  * Copyright 2002 Sapient
46  * @since carbon 1.0
47  * @author Vivekanand Kirubanandan, June 2002
48  * @version $Revision: 1.12 $($Author: dvoet $ / $Date: 2003/05/05 21:21:38 $)
49  */

50 public class DefaultUniqueIDServiceImpl
51     implements UniqueIDService, Configurable {
52
53     /**
54      * Provides a handle to Apache-commons logger
55      */

56     private Log log =
57         LogFactory.getLog(this.getClass());
58
59     /** UniqueID name used to identify the ID in the database */
60     protected String JavaDoc idName;
61
62     /** The number of IDs reserved with each database request */
63     protected long blockSize;
64
65     /** Starting value of the ID used while creating a new id */
66     protected long initialBlockStart;
67
68     /** Start value of the current block (inclusive)*/
69     protected long currentBlockStart;
70
71     /** End value of the current block (inclusive) */
72     protected long currentBlockEnd;
73
74     /** Current value of the ID, returned by the next call to getNextID */
75     protected long currentValue;
76
77     /** Enable/disable autocreate if the id doesnt exist */
78     protected boolean autoCreate;
79
80     /** Factory used to get the Statements to query the database */
81     protected StatementFactory statementFactory;
82
83     /** Connection Factory used for the Select/Update query */
84     protected ConnectionFactory connectionFactory;
85
86     /** Name of of the retrieve statement within statementFactory */
87     protected String JavaDoc retrieveStatementName;
88
89     /** Name of of the update statement within statementFactory */
90     protected String JavaDoc updateStatementName;
91
92     /** Name of of the create statement within statementFactory */
93     protected String JavaDoc createStatementName;
94
95     /**
96      * Flag that signifies that a new ID block should be retrieved regardless
97      * of currentValue and whether it falls outside of the current block.
98      * Set to true by the configuration method and to false by the getNextID
99      * method after it gets a new block.
100      */

101     protected boolean retrieveNewBlock;
102
103     /**
104      * Method is expected to return then next value, if the value is in
105      * the current block, otherwise resever a new block.
106      *
107      * @return the next unique id
108      * @throws UniqueIDServiceException if unable to generate the value
109      */

110     public synchronized long getNextID()
111         throws UniqueIDServiceException {
112
113         if (log.isTraceEnabled()) {
114             log.trace("Getting next value from ID named ["
115                 + this.idName + "]");
116         }
117
118         if (this.retrieveNewBlock
119             || this.currentValue > this.currentBlockEnd) {
120
121             reserveNewBlock();
122             this.retrieveNewBlock = false;
123         }
124         return this.currentValue++;
125     }
126
127     /**
128      * Configure the component. Uses the UniqueIDServiceConfiguration
129      *
130      * @see UniqueIDServiceConfiguration
131      *
132      * @param configuration A UniqueIDServiceConfiguration
133      */

134     public void configure(ComponentConfiguration configuration) {
135
136         UniqueIDServiceConfiguration config =
137             (UniqueIDServiceConfiguration) configuration;
138
139         // Set instance variables based on configration
140
this.idName = config.getIDName();
141         this.autoCreate = config.isAutoCreate();
142         this.blockSize = config.getBlockSize();
143         this.initialBlockStart = config.getInitialBlockStart();
144         this.statementFactory = config.getStatementFactory();
145         this.connectionFactory = config.getConnectionFactory();
146         this.retrieveStatementName = config.getRetrieveStatementName();
147         this.updateStatementName = config.getUpdateStatementName();
148         this.createStatementName = config.getCreateStatementName();
149
150         // validate configuration
151
if (this.idName == null) {
152             throw new InvalidConfigurationException(
153                 this.getClass(),
154                 config.getConfigurationName(),
155                 "IDName",
156                 "Cannot be null");
157         }
158
159         if (this.statementFactory == null) {
160             throw new InvalidConfigurationException(
161                 this.getClass(),
162                 config.getConfigurationName(),
163                 "StatementFactory",
164                 "Cannot be null");
165         }
166
167         if (this.connectionFactory == null) {
168             throw new InvalidConfigurationException(
169                 this.getClass(),
170                 config.getConfigurationName(),
171                 "ConnectionFactory",
172                 "Cannot be null");
173         }
174
175         // set retrieveNewBlock so that the next call to getNextID will
176
// load a new block
177
this.retrieveNewBlock = true;
178
179     }
180
181     /**
182      * This variable is used to ensure that only one attempt is made to
183      * retreive a new block of unique id in case of synchronization problem
184      * when two threads are trying to create the same unique id name at the
185      * same time.
186      * <p>
187      * true : indicates only one attempt has been made and second is
188      * required<br>
189      * false : indicates, no more attempts are required, continue the
190      * execution<br>
191      * </p>
192      */

193     private boolean isSecondAttemptRequired = true;
194
195     /**
196      * Reserves a new block of IDs, creating it if it does not exists and
197      * this component is configured to do so. Transactions are handled
198      * at this level to ensure only one component gets a new block at a time
199      * for this ID.
200      *
201      * @throws UniqueIDServiceException indicates an generic error
202      */

203     protected void reserveNewBlock() throws UniqueIDServiceException {
204
205         if (log.isInfoEnabled()) {
206             log.info("Reserving [" + this.blockSize + "] on Unique ID ["
207                      + this.idName + "].");
208         }
209
210         Connection JavaDoc connection = null;
211         try {
212
213             connection = this.connectionFactory.getConnection();
214             connection.setAutoCommit(false);
215
216             long start;
217
218             try {
219                 start = retrieveBlockStart(connection);
220             } catch (UniqueIDNotFoundException uidnfe) {
221                 if (this.autoCreate) {
222                     // ID does not exists yet and autoCreate is on, create it
223
try {
224                         start = createUniqueID();
225                     } catch (SQLException JavaDoc sqle) {
226
227                         // this exception indicates that there is a second
228
// attempt required to retreive a new block
229
if (isSecondAttemptRequired) {
230                             isSecondAttemptRequired = false;
231                             start = retrieveBlockStart(connection);
232                         } else {
233                             throw uidnfe;
234                         }
235                     }
236                 } else {
237                     // Not found and auto is off, rethrow exception
238
throw uidnfe;
239                 }
240             }
241
242             long nextBlockStart = start + this.blockSize;
243             reserveBlock(nextBlockStart, connection);
244
245             // Sucess! commit any changes and set internal variables
246
connection.commit();
247             this.currentBlockStart = start;
248             // currentBlockEnd to nextBlockStart - 1 because
249
// currentBlockEnd is inclusive
250
this.currentBlockEnd = nextBlockStart - 1;
251             // set currentValue to the currentBlockStart
252
this.currentValue = this.currentBlockStart;
253
254         } catch (SQLException JavaDoc se) {
255             try {
256                 // rollback the transaction
257
if (connection != null) {
258                     connection.rollback();
259                 }
260             } catch (SQLException JavaDoc sqle) {
261                 if (log.isWarnEnabled()) {
262                     log.warn("Caught exception rolling back unique id update: "
263                         + sqle
264                         + ExceptionUtility.captureStackTrace(sqle));
265                 }
266             }
267
268             // wrap exception in a UniqueIDServiceException
269
throw new UniqueIDServiceException(
270                 this.getClass(),
271                 this.idName,
272                 "Caught SQLException reserving range of ids",
273                 se);
274
275         } catch (UniqueIDServiceException uidse) {
276             try {
277                 // rollback the transaction
278
if (connection != null) {
279                     connection.rollback();
280                 }
281             } catch (SQLException JavaDoc sqle) {
282                 if (log.isWarnEnabled()) {
283                     log.warn("Caught exception rolling back unique id update: "
284                         + sqle
285                         + ExceptionUtility.captureStackTrace(sqle));
286                 }
287             }
288
289             // rethrow exception
290
throw uidse;
291
292         } finally {
293             // close connection
294
try {
295                 if (connection != null) {
296                     connection.close();
297                 }
298             } catch (SQLException JavaDoc se) {
299                 if (log.isWarnEnabled()) {
300                     log.warn("Caught exception closing Connection: "
301                         + se
302                         + ExceptionUtility.captureStackTrace(se));
303                 }
304             }
305         }
306     }
307
308     /**
309      * Retrieves the beginning of the next block of IDs.
310      * @param connection connection used to query the database, explicitly
311      * passed to allow transaction handling
312      *
313      * @return long the current value
314      *
315      * @throws UniqueIDNotFoundException indicates the ID is not found
316      * @throws UniqueIDServiceException indicates a generic error
317      */

318     protected long retrieveBlockStart(Connection JavaDoc connection)
319         throws UniqueIDNotFoundException, UniqueIDServiceException {
320
321         PreparedStatement JavaDoc retrieveStmt = null;
322         ResultSet JavaDoc retrieveRSet = null;
323
324         // Look for Unique ID and lock
325
try {
326             retrieveStmt =
327                 this.statementFactory.createPreparedStatement(
328                     this.retrieveStatementName,
329                     connection);
330
331             retrieveStmt.setString(1, this.idName);
332             retrieveRSet = retrieveStmt.executeQuery();
333
334             if (retrieveRSet.next()) {
335                 return retrieveRSet.getLong(1);
336             } else {
337                 throw new UniqueIDNotFoundException(
338                     this.getClass(), this.idName);
339             }
340
341         } catch (StatementFactoryException sfe) {
342             throw new UniqueIDServiceException(
343                 this.getClass(),
344                 this.idName,
345                 "Caught StatementFactoryException retrieving id",
346                 sfe);
347
348         } catch (SQLException JavaDoc se) {
349             throw new UniqueIDServiceException(
350                 this.getClass(),
351                 this.idName,
352                 "Caught SQLException retrieving id",
353                 se);
354
355         } finally {
356             try {
357                 if (retrieveRSet != null) {
358                     retrieveRSet.close();
359                 }
360             } catch (SQLException JavaDoc se) {
361                 if (log.isWarnEnabled()) {
362                     log.warn("Caught exception closing ResultSet: "
363                         + se
364                         + ExceptionUtility.captureStackTrace(se));
365                 }
366             }
367
368             try {
369                 if (retrieveStmt != null) {
370                     retrieveStmt.close();
371                 }
372             } catch (SQLException JavaDoc se) {
373                 if (log.isWarnEnabled()) {
374                     log.warn("Caught exception closing Statement: "
375                         + se
376                         + ExceptionUtility.captureStackTrace(se));
377                 }
378             }
379         }
380     }
381
382     /**
383      * Reserves a block of IDs by updating the row corresponding to this ID,
384      * increasing it by blockSize (configurable).
385      *
386      * @param nextBlockStart the starting value of the next block of IDs
387      * @param connection the connection used to update the database, explicitly
388      * passed to allow transaction handling
389      *
390      * @throws UniqueIDServiceException indicates an error reserving a block
391      */

392     protected void reserveBlock(long nextBlockStart, Connection JavaDoc connection)
393         throws UniqueIDServiceException {
394
395         PreparedStatement JavaDoc updateStmt = null;
396         // Update value
397
try {
398             updateStmt =
399                 this.statementFactory.createPreparedStatement(
400                     this.updateStatementName,
401                     connection);
402
403             updateStmt.setLong(1, nextBlockStart);
404             updateStmt.setString(2, this.idName);
405
406             // Execute increment
407
int updates = updateStmt.executeUpdate();
408
409             // Only one row should ever be incremented
410
if (updates != 1) {
411                 throw new UniqueIDServiceException(
412                     this.getClass(),
413                     this.idName,
414                     "Database updated [" + updates + "] rows, expected only 1");
415             }
416
417         } catch (StatementFactoryException sfe) {
418             throw new UniqueIDServiceException(
419                 this.getClass(),
420                 this.idName,
421                 "Caught StatementFactoryException reserving range of ids",
422                 sfe);
423
424         } catch (SQLException JavaDoc se) {
425             throw new UniqueIDServiceException(
426                 this.getClass(),
427                 this.idName,
428                 "Caught SQLException reserving range of ids",
429                 se);
430
431         } finally {
432             try {
433                 if (updateStmt != null) {
434                     updateStmt.close();
435                 }
436             } catch (SQLException JavaDoc se) {
437                 if (log.isWarnEnabled()) {
438                     log.warn("Caught exception closing Statement: "
439                         + se
440                         + ExceptionUtility.captureStackTrace(se));
441                 }
442             }
443         }
444     }
445
446     /**
447      * <p>Inserts a new row into the Unique ID holding table with an initial
448      * value defined by initialBlockStart which is set from configuration.
449      * This Unique ID can then be accessed through the normal
450      * <code>reserveBlock</code> method.</p>
451      *
452      * @return long the current value of the ID in the table
453      * @throws UniqueIDCreationException when unable to create the Unique ID
454      * in the table
455      * @throws SQLException when unable to insert the row in table
456      */

457     protected long createUniqueID()
458         throws UniqueIDCreationException, SQLException JavaDoc {
459
460         if (log.isInfoEnabled()) {
461             log.info("Creating createUniqueID [" + this.idName + "]");
462         }
463
464         PreparedStatement JavaDoc insertStmt = null;
465
466         try {
467             insertStmt =
468                 this.statementFactory.createPreparedStatement(
469                     this.createStatementName);
470
471             insertStmt.setString(1, this.idName);
472             insertStmt.setDouble(2, this.initialBlockStart);
473             int updateCount = insertStmt.executeUpdate();
474
475             if (updateCount != 1) {
476                 throw new UniqueIDCreationException(
477                     this.getClass(),
478                     this.idName,
479                     "The update count was [" + updateCount + "], expected 1");
480             }
481
482         } catch (SQLException JavaDoc se) {
483             // this exception is propogated above to ensure the logic of
484
// second attempt in case of synchronization problem of
485
// two threads trying to create same unique ID block at the same
486
// time
487
if (log.isInfoEnabled()) {
488                 log.info("Exception encountered while inserting row "
489                 + "in unique id table " + se);
490             }
491             throw se;
492
493         } catch (StatementFactoryException sfe) {
494             throw new UniqueIDCreationException(
495                 this.getClass(),
496                 this.idName,
497                 "Caught StatementFactoryException creating id",
498                 sfe);
499
500         } finally {
501             try {
502                 if (insertStmt != null) {
503                     insertStmt.close();
504                 }
505             } catch (SQLException JavaDoc se) {
506                 if (log.isWarnEnabled()) {
507                     log.warn("Caught exception closing Statement: "
508                         + se
509                         + ExceptionUtility.captureStackTrace(se));
510                 }
511             }
512         }
513
514         return this.initialBlockStart;
515     }
516 }
Popular Tags