001 package org.apache.ojb.broker.util.sequence;
002
003 /* Copyright 2002-2005 The Apache Software Foundation
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 import java.util.HashMap;
019 import java.util.Map;
020
021 import org.apache.commons.lang.SystemUtils;
022 import org.apache.ojb.broker.Identity;
023 import org.apache.ojb.broker.OptimisticLockException;
024 import org.apache.ojb.broker.PersistenceBroker;
025 import org.apache.ojb.broker.PersistenceBrokerFactory;
026 import org.apache.ojb.broker.metadata.FieldDescriptor;
027 import org.apache.ojb.broker.util.ObjectModification;
028 import org.apache.ojb.broker.util.logging.Logger;
029 import org.apache.ojb.broker.util.logging.LoggerFactory;
030
031 /**
032 * High/Low sequence manager implementation generates unique and continuous
033 * id's (during runtime) by using sequences to avoid database access.
034 * <br/>
035 *
036 * <p>
037 * Implementation configuration properties:
038 * </p>
039 *
040 * <table cellspacing="2" cellpadding="2" border="3" frame="box">
041 * <tr>
042 * <td><strong>Property Key</strong></td>
043 * <td><strong>Property Values</strong></td>
044 * </tr>
045 * <tr>
046 * <td>seq.start</td>
047 * <td>
048 * Set the start index of used sequences (e.g. set 100000, id generation starts with 100001).
049 * Default start index is <em>1</em>.
050 * </td>
051 * </tr>
052 * <tr>
053 * <td>grabSize</td>
054 * <td>
055 * Integer entry determines the
056 * number of IDs allocated within the
057 * H/L sequence manager implementation.
058 * Default was '20'.
059 * </td>
060 * </tr>
061 * <tr>
062 * <td>autoNaming</td>
063 * <td>
064 * Default was 'true'. If set 'true' OJB try to build a
065 * sequence name automatic if none found in field-descriptor
066 * and set this generated name as <code>sequence-name</code>
067 * in field-descriptor. If set 'false' OJB throws an exception
068 * if none sequence name was found in field-descriptor.
069 * </td>
070 * </tr>
071 * <tr>
072 * <td>globalSequenceId</td>
073 * <td>
074 * Deprecated! If set 'true' implementation use global unique
075 * id's for all fields. Default was 'false'.
076 * </td>
077 * </tr>
078 * <tr>
079 * <td>globalSequenceStart</td>
080 * <td>
081 * <em>Deprecated, use property 'seq.start'.</em> Set the start index of used global id
082 * generation (e.g. set 100000, id generation starts with 100001)
083 * </td>
084 * </tr>
085 * <tr>
086 * <td>sequenceStart</td>
087 * <td>
088 * <em>Deprecated, use property 'seq.start'.</em> Set the start index of used
089 * sequences (e.g. set 100000, id generation starts with 100001). Default start index is <em>1</em>.
090 * </td>
091 * </tr>
092 * </table>
093 *
094 * <br/>
095 * <p>
096 * <b>Limitations:</b>
097 * <ul>
098 * <li>Do NOT use this implementation in managed environment or
099 * 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 }