View Javadoc

1   package org.apache.ojb.broker.util.sequence;
2   
3   /* Copyright 2002-2005 The Apache Software Foundation
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * 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 implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  import java.util.HashMap;
19  import java.util.Map;
20  
21  import org.apache.commons.lang.SystemUtils;
22  import org.apache.ojb.broker.Identity;
23  import org.apache.ojb.broker.OptimisticLockException;
24  import org.apache.ojb.broker.PersistenceBroker;
25  import org.apache.ojb.broker.PersistenceBrokerFactory;
26  import org.apache.ojb.broker.metadata.FieldDescriptor;
27  import org.apache.ojb.broker.util.ObjectModification;
28  import org.apache.ojb.broker.util.logging.Logger;
29  import org.apache.ojb.broker.util.logging.LoggerFactory;
30  
31  /**
32   * High/Low sequence manager implementation generates unique and continuous
33   * id's (during runtime) by using sequences to avoid database access.
34   * <br/>
35   *
36   * <p>
37   * Implementation configuration properties:
38   * </p>
39   *
40   * <table cellspacing="2" cellpadding="2" border="3" frame="box">
41   * <tr>
42   *     <td><strong>Property Key</strong></td>
43   *     <td><strong>Property Values</strong></td>
44   * </tr>
45   * <tr>
46   *     <td>seq.start</td>
47   *     <td>
48   *         Set the start index of used sequences (e.g. set 100000, id generation starts with 100001).
49   *         Default start index is <em>1</em>.
50   *    </td>
51   * </tr>
52   * <tr>
53   *     <td>grabSize</td>
54   *     <td>
55   *         Integer entry determines the
56   *         number of IDs allocated within the
57   *         H/L sequence manager implementation.
58   *         Default was '20'.
59   *    </td>
60   * </tr>
61   * <tr>
62   *     <td>autoNaming</td>
63   *     <td>
64   *          Default was 'true'. If set 'true' OJB try to build a
65   *          sequence name automatic if none found in field-descriptor
66   *          and set this generated name as <code>sequence-name</code>
67   *          in field-descriptor. If set 'false' OJB throws an exception
68   *          if none sequence name was found in field-descriptor.
69   *    </td>
70   * </tr>
71   * <tr>
72   *     <td>globalSequenceId</td>
73   *     <td>
74   *         Deprecated! If set 'true' implementation use global unique
75   *         id's for all fields. Default was 'false'.
76   *    </td>
77   * </tr>
78   * <tr>
79   *     <td>globalSequenceStart</td>
80   *     <td>
81   *         <em>Deprecated, use property 'seq.start'.</em> Set the start index of used global id
82   *         generation (e.g. set 100000, id generation starts with 100001)
83   *    </td>
84   * </tr>
85   *  <tr>
86   *     <td>sequenceStart</td>
87   *     <td>
88   *         <em>Deprecated, use property 'seq.start'.</em> Set the start index of used
89   *          sequences (e.g. set 100000, id generation starts with 100001). Default start index is <em>1</em>.
90   *    </td>
91   * </tr>
92   * </table>
93   *
94   * <br/>
95   * <p>
96   * <b>Limitations:</b>
97   * <ul>
98   *	<li>Do NOT use this implementation in managed environment or
99   * any comparable system where any connection was associated
100  * with the running transaction.</li>
101  * </ul>
102  * </p>
103  *
104  *
105  * <br/>
106  * <br/>
107  *
108  *
109  * @see org.apache.ojb.broker.util.sequence.SequenceManager
110  * @see org.apache.ojb.broker.util.sequence.SequenceManagerFactory
111  * @see org.apache.ojb.broker.util.sequence.SequenceManagerHelper
112  *
113  * @author <a href="mailto:armin@codeAuLait.de">Armin Waibel</a>
114  * @version $Id: SequenceManagerHighLowImpl.java,v 1.1 2007-08-24 22:17:29 ewestfal Exp $
115  */
116 public class SequenceManagerHighLowImpl extends AbstractSequenceManager
117 {
118     private static Logger log = LoggerFactory.getLogger(SequenceManagerHighLowImpl.class);
119     /**
120      * sequence name used for global id generation.
121      */
122     private static final String GLOBAL_SEQUENCE_NAME = "global - default sequence name";
123     public static final String PROPERTY_GRAB_SIZE = "grabSize";
124     public static final String PROPERTY_GLOBAL_SEQUENCE_ID = "globalSequenceId";
125     public static final String PROPERTY_GLOBAL_SEQUENCE_START = "globalSequenceStart";
126 
127     protected static Map sequencesDBMap = new HashMap();
128 
129     protected boolean useGlobalSequenceIdentities;
130     protected int grabSize;
131     protected long sequenceStart;
132     protected int attempts;
133 
134     public SequenceManagerHighLowImpl(PersistenceBroker broker)
135     {
136         super(broker);
137         Long start = SequenceManagerHelper.getSeqStart(getConfigurationProperties());
138         sequenceStart = start != null ? start.longValue() : 1;
139         grabSize = Integer.parseInt(getConfigurationProperty(PROPERTY_GRAB_SIZE, "20"));
140         useGlobalSequenceIdentities = Boolean.getBoolean(getConfigurationProperty(PROPERTY_GLOBAL_SEQUENCE_ID, "false"));
141         // support for deprecated properties
142         long globalSequenceStart = Long.parseLong(getConfigurationProperty(PROPERTY_GLOBAL_SEQUENCE_START, "1"));
143         if(useGlobalSequenceIdentities && globalSequenceStart > sequenceStart)
144         {
145             sequenceStart = globalSequenceStart;
146         }
147     }
148 
149     protected long getUniqueLong(FieldDescriptor field) throws SequenceManagerException
150     {
151         HighLowSequence seq;
152         String sequenceName = buildSequenceName(field);
153         synchronized (SequenceManagerHighLowImpl.class)
154         {
155             // try to find sequence
156             seq = getSequence(sequenceName);
157 
158             if (seq == null)
159             {
160                 // not found, get sequence from database or create new
161                 seq = getSequence(getBrokerForClass(), field, sequenceName);
162                 addSequence(sequenceName, seq);
163             }
164 
165             // now we have a sequence
166             long id = seq.getNextId();
167             // seq does not have reserved IDs => catch new block of keys
168             if (id == 0)
169             {
170                 seq = getSequence(getBrokerForClass(), field, sequenceName);
171                 // replace old sequence!!
172                 addSequence(sequenceName, seq);
173                 id = seq.getNextId();
174                 if (id == 0)
175                 {
176                     // something going wrong
177                     removeSequence(sequenceName);
178                     throw new SequenceManagerException("Sequence generation failed: " +
179                             SystemUtils.LINE_SEPARATOR + "Sequence: " + seq +
180                             ". Unable to build new ID, id was always 0." +
181                             SystemUtils.LINE_SEPARATOR + "Thread: " + Thread.currentThread() +
182                             SystemUtils.LINE_SEPARATOR + "PB: " + getBrokerForClass());
183                 }
184             }
185             return id;
186         }
187     }
188 
189     /**
190      * Returns last used sequence object or <code>null</code> if no sequence
191      * was add for given sequence name.
192      *
193      * @param sequenceName Name of the sequence.
194      * @return Sequence object or <code>null</code>
195      */
196     private HighLowSequence getSequence(String sequenceName)
197     {
198         HighLowSequence result = null;
199         // now lookup the sequence map for calling DB
200         Map mapForDB = (Map) sequencesDBMap.get(getBrokerForClass()
201                 .serviceConnectionManager().getConnectionDescriptor().getJcdAlias());
202         if(mapForDB != null)
203         {
204             result = (HighLowSequence) mapForDB.get(sequenceName);
205         }
206         return result;
207     }
208 
209     /**
210      * Put new sequence object for given sequence name.
211      * @param sequenceName Name of the sequence.
212      * @param seq The sequence object to add.
213      */
214     private void addSequence(String sequenceName, HighLowSequence seq)
215     {
216         // lookup the sequence map for calling DB
217         String jcdAlias = getBrokerForClass()
218                 .serviceConnectionManager().getConnectionDescriptor().getJcdAlias();
219         Map mapForDB = (Map) sequencesDBMap.get(jcdAlias);
220         if(mapForDB == null)
221         {
222             mapForDB = new HashMap();
223         }
224         mapForDB.put(sequenceName, seq);
225         sequencesDBMap.put(jcdAlias, mapForDB);
226     }
227 
228     /**
229      * Remove the sequence for given sequence name.
230      *
231      * @param sequenceName Name of the sequence to remove.
232      */
233     protected void removeSequence(String sequenceName)
234     {
235         // lookup the sequence map for calling DB
236         Map mapForDB = (Map) sequencesDBMap.get(getBrokerForClass()
237                 .serviceConnectionManager().getConnectionDescriptor().getJcdAlias());
238         if(mapForDB != null)
239         {
240             synchronized(SequenceManagerHighLowImpl.class)
241             {
242                 mapForDB.remove(sequenceName);
243             }
244         }
245     }
246 
247     protected HighLowSequence getSequence(PersistenceBroker brokerForSequence,
248                                         FieldDescriptor field,
249                                         String sequenceName)  throws SequenceManagerException
250     {
251         HighLowSequence newSequence = null;
252         PersistenceBroker internBroker = null;
253         try
254         {
255             /*
256             arminw:
257             we use a new internBroker instance, because we run into problems
258             when current internBroker was rollback, then we have new sequence
259             in memory, but not in database and a concurrent thread will
260             get the same sequence.
261             Thus we use a new internBroker instance (with new connection) to
262             avoid this problem.
263             */
264             internBroker = PersistenceBrokerFactory.createPersistenceBroker(brokerForSequence.getPBKey());
265             internBroker.beginTransaction();
266 
267             newSequence = lookupStoreSequence(internBroker, field, sequenceName);
268 
269             internBroker.commitTransaction();
270 
271             if (log.isDebugEnabled()) log.debug("new sequence was " + newSequence);
272         }
273         catch(Exception e)
274         {
275             log.error("Can't lookup new HighLowSequence for field "
276                     + (field != null ? field.getAttributeName() : null)
277                     + " using sequence name " + sequenceName, e);
278             if(internBroker != null && internBroker.isInTransaction()) internBroker.abortTransaction();
279             throw new SequenceManagerException("Can't build new sequence", e);
280         }
281         finally
282         {
283             attempts = 0;
284             if (internBroker != null) internBroker.close();
285         }
286         return newSequence;
287     }
288 
289     protected HighLowSequence lookupStoreSequence(PersistenceBroker broker, FieldDescriptor field, String seqName)
290     {
291         HighLowSequence newSequence;
292         boolean needsInsert = false;
293 
294         Identity oid = broker.serviceIdentity().buildIdentity(HighLowSequence.class, seqName);
295         // first we lookup sequence object in database
296         newSequence = (HighLowSequence) broker.getObjectByIdentity(oid);
297 
298         //not in db --> we have to store a new sequence
299         if (newSequence == null)
300         {
301             if (log.isDebugEnabled())
302             {
303                 log.debug("sequence for field " + field + " not found in db, store new HighLowSequence");
304             }
305             /*
306             here we lookup the max key for the given field in system
307             */
308             // !!! here we use current broker instance to avoid deadlock !!!
309             long maxKey = getMaxKeyForSequence(getBrokerForClass(), field);
310 
311             newSequence = newSequenceObject(seqName, field);
312             newSequence.setMaxKey(maxKey);
313             needsInsert = true;
314         }
315         // maybe property 'sequenceStart' was changed, so we check maxKey against
316         // current set sequence start index
317         if(newSequence.getMaxKey() < sequenceStart)
318         {
319             newSequence.setMaxKey(sequenceStart);
320         }
321 
322         // set current grab size
323         newSequence.setGrabSize(grabSize);
324 
325         //grab the next key scope
326         newSequence.grabNextKeySet();
327 
328         //store the sequence to db
329         try
330         {
331             if(needsInsert) broker.store(newSequence, ObjectModification.INSERT);
332             else broker.store(newSequence, ObjectModification.UPDATE);
333         }
334         catch (OptimisticLockException e)
335         {
336             // we try five times to get a new sequence
337             if(attempts < 5)
338             {
339                 log.info("OptimisticLockException was thrown, will try again to store sequence. Sequence was "+newSequence);
340                 attempts++;
341                 newSequence = lookupStoreSequence(broker, field, seqName);
342             }
343             else throw e;
344         }
345         return newSequence;
346     }
347 
348     protected HighLowSequence newSequenceObject(String sequenceName,
349                                               FieldDescriptor field)
350     {
351         HighLowSequence seq = new HighLowSequence();
352         seq.setName(sequenceName);
353         seq.setGrabSize(grabSize);
354         return seq;
355     }
356 
357     protected long getMaxKeyForSequence(PersistenceBroker broker,
358                                         FieldDescriptor field)
359     {
360         long maxKey;
361         if (useGlobalSequenceIdentities)
362         {
363             maxKey = sequenceStart;
364         }
365         else
366         {
367             /*
368             here we lookup the max key for the given field in system
369             */
370             maxKey = SequenceManagerHelper.getMaxForExtent(broker, field);
371             // check against start index
372             maxKey = sequenceStart > maxKey ? sequenceStart : maxKey;
373         }
374         return maxKey;
375     }
376 
377     private String buildSequenceName(FieldDescriptor field) throws SequenceManagerException
378     {
379         String seqName;
380         if (useGlobalSequenceIdentities)
381         {
382             seqName = GLOBAL_SEQUENCE_NAME;
383         }
384         else
385         {
386             seqName = calculateSequenceName(field);
387         }
388         return seqName;
389     }
390 }