View Javadoc

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;   // was: 10000
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       // exit here...
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     // every time a client requests a new id, the system increase low by 1 unit.
178     // If low reaches LOW_MAXVALUE, it is set to 0, and the system increases
179     // the high value by 1 unit.
180     // The unique key is given by the combination of the high value and low value.
181     low++;
182 
183     if (low >= LOW_MAXVALUE)
184     {
185       // 0 >= low < LOW_MAXVALUE
186       // restart the low value;
187       low = 0;
188 
189       // increase the high value (one database hit);
190       high = getNextHighValue();
191     }
192 
193     // i.e.: 10000 * 1 + 9999 , 10000 * 2 + 0;
194     id = ((LOW_MAXVALUE * high) + low);
195 
196     //cat.debug(new StringBuffer("::getId - got new id [")
197     //              .append(id).append("]; (high, low) = [")
198     //              .append(high)
199     //              .append(",")
200     //              .append(low)
201     //              .append("]").toString());
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       // starting the transaction;
242       // it must prevent dirty reads, non-repeatable reads and phantom reads.
243       con.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
244       con.setAutoCommit(false);
245 
246       stGetHighValue = con.createStatement();
247       rs = stGetHighValue.executeQuery(qryGetHighValue);
248 
249       // get the high value; this is the value that will be returned;
250       if (rs.next())
251         high = rs.getLong("HIGH");
252 
253       // update the HIGH field value;
254       psUpdateHighValue = con.prepareStatement(qryUpdateHighValue);
255       psUpdateHighValue.setLong(1, (high + 1));
256       psUpdateHighValue.executeUpdate();
257 
258       // the transaction is committed into the finally clause...
259       // commit the transaction;
260       //con.commit();
261       //con.setAutoCommit(true);
262     }
263     catch (SQLException e)
264     {
265       // rollback and *close* the transaction;
266       // then execute the finally clause and rethrow the exception;
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 }