KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > avalon > excalibur > datasource > ids > TableIdGenerator


1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements. See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14  * implied.
15  *
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */

19
20 package org.apache.avalon.excalibur.datasource.ids;
21
22 import java.math.BigDecimal JavaDoc;
23 import java.sql.Connection JavaDoc;
24 import java.sql.ResultSet JavaDoc;
25 import java.sql.SQLException JavaDoc;
26 import java.sql.Statement JavaDoc;
27
28 import org.apache.avalon.framework.configuration.Configuration;
29 import org.apache.avalon.framework.configuration.ConfigurationException;
30
31 /**
32  * The TableIdGenerator requests blocks of ids from a Database table. The table consists of two
33  * columns one called <code>table_name</code> of type CHAR or VARCHAR, and the second called
34  * <code>next_id</code> of an integer type large enough to hold your largest ids.
35  * <p>
36  * The Configuration to use a TableIdGenerator looks like the following:
37  * <pre>
38  * &lt;id-generators&gt;
39  * &lt;table name="user-ids" big-decimals="true" block-size="1" table="ids"
40  * key-table="event-type" logger="cm.ids"&gt;
41  * &lt;dbpool&gt;user-db&lt;/dbpool&gt;
42  * &lt;/table&gt;
43  * &lt;/id-generators&gt;
44  * </pre>
45  * Where user-db is the name of a DataSource configured in a datasources element, block-size is
46  * the number if ids that are allocated with each query to the databse (defaults to "10"),
47  * table is the name of the table which contains the ids (defaults to "ids"), and key-table is
48  * the table_name of the row from which the block of ids are allocated (defaults to "id").
49  * <p>
50  *
51  * With the following roles declaration:
52  * <pre>
53  * &lt;role name="org.apache.avalon.excalibur.datasource.ids.IdGeneratorSelector"
54  * shorthand="id-generators"
55  * default-class="org.apache.avalon.excalibur.component.ExcaliburComponentSelector"&gt;
56  * &lt;hint shorthand="table"
57  * class="org.apache.avalon.excalibur.datasource.ids.TableIdGenerator"/&gt;
58  * &lt;/role&gt;
59  * </pre>
60  *
61  * To configure your component to use the IdGenerator declared above, its configuration should look
62  * something like the following:
63  * <pre>
64  * &lt;user-service logger="cm"&gt;
65  * &lt;dbpool&gt;user-db&lt;/dbpool&gt;
66  * &lt;id-generator&gt;user-ids&lt;/id-generator&gt;
67  * &lt;/user-service&gt;
68  * </pre>
69  *
70  * Your component obtains a reference to an IdGenerator using the same method as it obtains a
71  * DataSource, by making use of a ComponentSelector.
72  * <p>
73  * Depending on your database, the ids table should look something like the following:
74  * <pre>
75  * CREATE TABLE ids (
76  * table_name varchar(16) NOT NULL,
77  * next_id INTEGER NOT NULL,
78  * PRIMARY KEY (table_name)
79  * );
80  * </pre>
81  *
82  * @avalon.component
83  * @avalon.service type=org.apache.avalon.excalibur.datasource.ids.IdGenerator
84  * @x-avalon.info name=table-id-generator
85  * @x-avalon.lifestyle type=singleton
86  *
87  * @author <a HREF="mailto:dev@avalon.apache.org">Avalon Development Team</a>
88  * @version CVS $Revision: 1.4 $ $Date: 2004/02/28 11:47:17 $
89  * @since 4.1
90  */

91 public class TableIdGenerator
92     extends AbstractDataSourceBlockIdGenerator
93 {
94     /**
95      * The name of the table containing the ids.
96      */

97     private String JavaDoc m_table;
98
99     /**
100      * TableName used to reference which ids to allocate.
101      */

102     private String JavaDoc m_tableName;
103
104     /*---------------------------------------------------------------
105      * Constructors
106      *-------------------------------------------------------------*/

107     public TableIdGenerator()
108     {
109     }
110
111     /*---------------------------------------------------------------
112      * Methods
113      *-------------------------------------------------------------*/

114     /**
115      * Allocates a block of ids of the given size and returns the first id.
116      *
117      * @param blockSize number of ids to allocate.
118      * @param useBigDecimals returns the first id as a BigDecimal if true, otherwise as a Long.
119      *
120      * @return either a Long or a BigDecimal depending on the value of useBigDecimals
121      *
122      * @throws IdException if a block of ids can not be allocated.
123      */

124     private Object JavaDoc allocateIdBlock( int blockSize, boolean useBigDecimals )
125         throws IdException
126     {
127         if( getLogger().isDebugEnabled() )
128         {
129             getLogger().debug( "Allocating a new block of " + blockSize +
130                                " ids for key_table " + m_tableName + "." );
131         }
132
133         try
134         {
135             Connection JavaDoc conn = getConnection();
136             try
137             {
138                 boolean autoCommit = conn.getAutoCommit();
139                 
140                 Statement JavaDoc stmt = conn.createStatement();
141                 try
142                 {
143                     // Try to get a block without using transactions. This makes this code
144
// portable, but works on the assumption that requesting blocks of ids
145
// is a fairly rare thing.
146
int tries = 0;
147                     while( tries < 50 )
148                     {
149                         // Find out what the next available id is.
150
String JavaDoc query = "SELECT next_id FROM " + m_table + " WHERE table_name = '"
151                             + m_tableName + "'";
152                         ResultSet JavaDoc rs = stmt.executeQuery( query );
153                         if ( !rs.next() )
154                         {
155                             // The row does not exist.
156
String JavaDoc msg =
157                                 "Unable to allocate a block of Ids, no row with table_name='"
158                                 + m_tableName + "' exists in the " + m_table + " table.";
159                             getLogger().error( msg );
160                             if ( !autoCommit )
161                             {
162                                 conn.rollback();
163                             }
164
165                             throw new IdException( msg );
166                         }
167                         
168                         // Get the next_id using the appropriate data type.
169
Object JavaDoc nextId;
170                         Object JavaDoc newNextId;
171                         if( useBigDecimals )
172                         {
173                             BigDecimal JavaDoc oldNextId = rs.getBigDecimal( 1 );
174                             newNextId = oldNextId.add( new BigDecimal JavaDoc( blockSize ) );
175                             nextId = oldNextId;
176                         }
177                         else
178                         {
179                             long oldNextId = rs.getLong( 1 );
180                             newNextId = new Long JavaDoc( oldNextId + blockSize );
181                             nextId = new Long JavaDoc( oldNextId );
182                         }
183                         
184                         // Update the value of next_id in the database so it reflects the full block
185
// being allocated. If another process has done the same thing, then this
186
// will either throw an exception due to transaction isolation or return
187
// an update count of 0. In either case, we will need to try again.
188
try
189                         {
190                             // Need to quote next_id values so that MySQL handles large BigDecimals
191
// correctly.
192
query = "UPDATE " + m_table
193                                 + " SET next_id = '" + newNextId + "' "
194                                 + " WHERE table_name = '" + m_tableName + "' "
195                                 + " AND next_id = '" + nextId + "'";
196                             int updated = stmt.executeUpdate( query );
197                             if( updated >= 1 )
198                             {
199                                 // Update was successful.
200
if ( !autoCommit )
201                                 {
202                                     conn.commit();
203                                 }
204
205                                 // Return the next id obtained above.
206
return nextId;
207                             }
208                             else
209                             {
210                                 // May have been a transaction confict. Try again.
211
if( getLogger().isDebugEnabled() )
212                                 {
213                                     getLogger().debug(
214                                         "Update resulted in no rows being changed." );
215                                 }
216                             }
217                         }
218                         catch ( SQLException JavaDoc e )
219                         {
220                             // Assume that this was caused by a transaction conflict. Try again.
221
if( getLogger().isDebugEnabled() )
222                             {
223                                 // Just show the exception message to keep the output small.
224
getLogger().debug(
225                                     "Encountered an exception attempting to update the "
226                                     + m_table + " table. May be a transaction confict. "
227                                     + "Trying again: " + e.getMessage() );
228                             }
229                         }
230                         
231                         // If we got here, then we failed, roll back the connection so we can
232
// try again.
233
if ( !autoCommit )
234                         {
235                             conn.rollback();
236                         }
237
238                         tries++;
239                     }
240                     
241                     // If we got here then we ran out of tries.
242
getLogger().error( "Unable to allocate a block of Ids. Too many retries." );
243                     return null;
244                 }
245                 finally
246                 {
247                     stmt.close();
248                 }
249             }
250             finally
251             {
252                 conn.close();
253             }
254         }
255         catch ( SQLException JavaDoc e )
256         {
257             String JavaDoc msg = "Unable to allocate a block of Ids.";
258             getLogger().error( msg, e );
259             throw new IdException( msg, e );
260         }
261     }
262
263     /*---------------------------------------------------------------
264      * AbstractDataSourceBlockIdGenerator Methods
265      *-------------------------------------------------------------*/

266     /**
267      * Allocates a block, of the given size, of ids from the database.
268      *
269      * @param blockSize number of Ids which are to be allocated.
270      *
271      * @return The first id in the allocated block.
272      *
273      * @throws IdException if there it was not possible to allocate a block of ids.
274      */

275     protected BigDecimal JavaDoc allocateBigDecimalIdBlock( int blockSize )
276         throws IdException
277     {
278         return (BigDecimal JavaDoc)allocateIdBlock( blockSize, true );
279     }
280
281     /**
282      * Allocates a block, of the given size, of ids from the database.
283      *
284      * @param blockSize number of Ids which are to be allocated.
285      *
286      * @return The first id in the allocated block.
287      *
288      * @throws IdException if there it was not possible to allocate a block of ids.
289      */

290     protected long allocateLongIdBlock( int blockSize )
291         throws IdException
292     {
293         Long JavaDoc id = (Long JavaDoc)allocateIdBlock( blockSize, false );
294
295         return id.longValue();
296     }
297
298     /*---------------------------------------------------------------
299      * Configurable Methods
300      *-------------------------------------------------------------*/

301     /**
302      * Called by the Container to configure the component.
303      *
304      * @param configuration configuration info used to setup the component.
305      *
306      * @throws ConfigurationException if there are any problems with the configuration.
307      */

308     public void configure( Configuration configuration )
309         throws ConfigurationException
310     {
311         super.configure( configuration );
312
313         // Obtain the table name.
314
m_table = configuration.getAttribute( "table", "ids" );
315
316         // Obtain the key-table.
317
m_tableName = configuration.getAttribute( "key-table", "id" );
318     }
319 }
320
321
Popular Tags