1 /***
2 * The contents of this file are subject to the Mozilla Public
3 * License Version 1.1 (the "License"); you may not use this file
4 * except in compliance with the License. You may obtain a copy of
5 * the License at http://www.mozilla.org/MPL/
6 *
7 * Software distributed under the License is distributed on an "AS
8 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
9 * implied. See the License for the specific language governing
10 * rights and limitations under the License.
11 *
12 * The Original Code is pow2toolkit library.
13 *
14 * The Initial Owner of the Original Code is
15 * Power Of Two S.R.L. (www.pow2.com)
16 *
17 * Portions created by Power Of Two S.R.L. are
18 * Copyright (C) Power Of Two S.R.L.
19 * All Rights Reserved.
20 *
21 * Contributor(s):
22 */
23
24 package com.pow2.dao;
25
26
27 import java.sql.*;
28
29
30 /***
31 * IdGenerator class.
32 * <br>
33 * Use the HIGH/LOW approach to generate an
34 * uinque Id.
35 * <br>
36 * This method uses the <code>app_identifier</code> database table
37 * <br>
38 * <table border="1" bgcolor="#f0f0f0">
39 * <tr>
40 * <td>field name</td>
41 * <td>field type</td>
42 * <td>is key ?</td>
43 * </tr>
44 * <tr>
45 * <td>id</td>
46 * <td>integer not null</td>
47 * <td>y</td>
48 * </tr>
49 * <tr>
50 * <td>high</td>
51 * <td>integer</td>
52 * <td>n</td>
53 * </tr>
54 * </table>
55 * <br><br>
56 *
57 * Initialize the unique table record with this SQL statement:
58 * <br>
59 * <code>INSERT INTO "app_identifier" VALUES (0,1);</code>
60 *
61 * @author Luca Fossato
62 */
63 public class IdGenerator extends AbstractDAO
64 {
65 /*** low max value */
66 public final static int LOW_MAXVALUE = 10;
67
68 /*** an handle to the unique IdGenerator instance */
69 private static IdGenerator instance = null;
70
71 /*** query used to retrieve the high value from the APP_IDENTIFIER table */
72 private String qryGetHighValue;
73
74 /*** query used to update the high value from the APP_IDENTIFIER table */
75 private String qryUpdateHighValue;
76
77 /*** id high value */
78 private long high;
79
80 /*** id low value */
81 private int low;
82
83
84 /***
85 * Protected default constructor.
86 * <br>
87 * Initialize the <code>high</code> and <code>low</code> values.
88 */
89 protected IdGenerator()
90 {
91 qryGetHighValue = "select HIGH from APP_IDENTIFIER where id = 0";
92 qryUpdateHighValue = "update APP_IDENTIFIER set HIGH = ? WHERE id = 0";
93
94 try
95 {
96 high = getNextHighValue();
97 cat.info("::IdGenerator - new high value: [" + high + "]");
98 }
99 catch (Exception e)
100 {
101 cat.error("::qryGetHighValue - cannot initialize the IdGenerator class: ", e);
102
103 }
104
105 low = 0;
106 }
107
108
109 /***
110 * Get the instance of the IdGenerator class.
111 *
112 * @return the instance of the IdGenerator class
113 */
114 public static synchronized IdGenerator instance()
115 {
116 if (instance == null)
117 instance = new IdGenerator();
118
119 return instance;
120 }
121
122
123 /***
124 * Sets the low attribute of the IdGenerator object
125 *
126 * @param low The new low value
127 */
128 public void setLow(int low)
129 {
130 this.low = low;
131 }
132
133
134 /***
135 * Gets the low attribute of the IdGenerator object
136 *
137 * @return The low value
138 */
139 public int getLow()
140 {
141 return low;
142 }
143
144
145 /***
146 * Sets the high attribute of the IdGenerator object
147 *
148 * @param high The new high value
149 */
150 public void setHigh(long high)
151 {
152 this.high = high;
153 }
154
155
156 /***
157 * Gets the high attribute of the IdGenerator object
158 *
159 * @return The high value
160 */
161 public long getHigh()
162 {
163 return high;
164 }
165
166
167 /***
168 * Get a new unique identifier number.
169 *
170 * @return a new unique identifier number
171 * @exception Exception Description of the Exception
172 */
173 public synchronized long getId() throws Exception
174 {
175 long id = -1;
176
177
178
179
180
181 low++;
182
183 if (low >= LOW_MAXVALUE)
184 {
185
186
187 low = 0;
188
189
190 high = getNextHighValue();
191 }
192
193
194 id = ((LOW_MAXVALUE * high) + low);
195
196
197
198
199
200
201
202
203 return id;
204 }
205
206
207 /***
208 * PRIVATE METHODS here
209 */
210
211
212 /***
213 * Get the next <code>HIGH</code> value from
214 * the <code>APP_IDENTIFIER</code> table.
215 * <br>
216 * Then update the table unique record increasing by 1
217 * the <code>HIGH</code> field value.
218 * <br>
219 * The queries are execute within a transaction with the
220 * isolation level set to <code>Connection.TRANSACTION_SERIALIZABLE</code>.
221 * <br>
222 * During the transaction, dirty reads, non-repeatable reads
223 * and phantom reads are prevented.
224 *
225 * @return the next <code>HIGH</code> value from
226 * the <code>APP_IDENTIFIER</code> table
227 * @exception Exception if any error occurs
228 */
229 private long getNextHighValue() throws Exception
230 {
231 Connection con = null;
232 Statement stGetHighValue = null;
233 PreparedStatement psUpdateHighValue = null;
234 ResultSet rs = null;
235 long high = -1;
236
237 try
238 {
239 con = DAO.instance().getConnection();
240
241
242
243 con.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
244 con.setAutoCommit(false);
245
246 stGetHighValue = con.createStatement();
247 rs = stGetHighValue.executeQuery(qryGetHighValue);
248
249
250 if (rs.next())
251 high = rs.getLong("HIGH");
252
253
254 psUpdateHighValue = con.prepareStatement(qryUpdateHighValue);
255 psUpdateHighValue.setLong(1, (high + 1));
256 psUpdateHighValue.executeUpdate();
257
258
259
260
261
262 }
263 catch (SQLException e)
264 {
265
266
267 cat.error("::getNextHighValue - rollback the current transaction", e);
268 closeResources(con, false);
269 throw e;
270 }
271 finally
272 {
273 closeResources(rs, stGetHighValue, psUpdateHighValue, con, true);
274 }
275
276 return high;
277 }
278
279
280 /***
281 * Close the JDBC resultSet, statement and preparedStatement objects,
282 * commit or rollback any uncommitted transaction and
283 * close the input connection.
284 * <br>
285 * Call this method into a <code>finally()</code> clause.
286 *
287 * @param rs the recordSet object to close
288 * @param con the Connection object to close
289 * @param st the statement object to close
290 * @exception SQLException if any error occurs
291 */
292 private void closeResources(ResultSet rs,
293 Statement st,
294 PreparedStatement ps,
295 Connection con,
296 boolean commit)
297 throws SQLException
298 {
299 if (ps != null) ps.close();
300 closeResources(rs, st, con, true);
301 }
302 }