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.29.2.5 2005/12/21 22:28:41 tomdz Exp $
115: */
116: public class SequenceManagerHighLowImpl extends AbstractSequenceManager {
117: private static Logger log = LoggerFactory
118: .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: super (broker);
136: Long start = SequenceManagerHelper
137: .getSeqStart(getConfigurationProperties());
138: sequenceStart = start != null ? start.longValue() : 1;
139: grabSize = Integer.parseInt(getConfigurationProperty(
140: PROPERTY_GRAB_SIZE, "20"));
141: useGlobalSequenceIdentities = Boolean
142: .getBoolean(getConfigurationProperty(
143: PROPERTY_GLOBAL_SEQUENCE_ID, "false"));
144: // support for deprecated properties
145: long globalSequenceStart = Long
146: .parseLong(getConfigurationProperty(
147: PROPERTY_GLOBAL_SEQUENCE_START, "1"));
148: if (useGlobalSequenceIdentities
149: && globalSequenceStart > sequenceStart) {
150: sequenceStart = globalSequenceStart;
151: }
152: }
153:
154: protected long getUniqueLong(FieldDescriptor field)
155: throws SequenceManagerException {
156: HighLowSequence seq;
157: String sequenceName = buildSequenceName(field);
158: synchronized (SequenceManagerHighLowImpl.class) {
159: // try to find sequence
160: seq = getSequence(sequenceName);
161:
162: if (seq == null) {
163: // not found, get sequence from database or create new
164: seq = getSequence(getBrokerForClass(), field,
165: sequenceName);
166: addSequence(sequenceName, seq);
167: }
168:
169: // now we have a sequence
170: long id = seq.getNextId();
171: // seq does not have reserved IDs => catch new block of keys
172: if (id == 0) {
173: seq = getSequence(getBrokerForClass(), field,
174: sequenceName);
175: // replace old sequence!!
176: addSequence(sequenceName, seq);
177: id = seq.getNextId();
178: if (id == 0) {
179: // something going wrong
180: removeSequence(sequenceName);
181: throw new SequenceManagerException(
182: "Sequence generation failed: "
183: + SystemUtils.LINE_SEPARATOR
184: + "Sequence: "
185: + seq
186: + ". Unable to build new ID, id was always 0."
187: + SystemUtils.LINE_SEPARATOR
188: + "Thread: "
189: + Thread.currentThread()
190: + SystemUtils.LINE_SEPARATOR
191: + "PB: " + getBrokerForClass());
192: }
193: }
194: return id;
195: }
196: }
197:
198: /**
199: * Returns last used sequence object or <code>null</code> if no sequence
200: * was add for given sequence name.
201: *
202: * @param sequenceName Name of the sequence.
203: * @return Sequence object or <code>null</code>
204: */
205: private HighLowSequence getSequence(String sequenceName) {
206: HighLowSequence result = null;
207: // now lookup the sequence map for calling DB
208: Map mapForDB = (Map) sequencesDBMap.get(getBrokerForClass()
209: .serviceConnectionManager().getConnectionDescriptor()
210: .getJcdAlias());
211: if (mapForDB != null) {
212: result = (HighLowSequence) mapForDB.get(sequenceName);
213: }
214: return result;
215: }
216:
217: /**
218: * Put new sequence object for given sequence name.
219: * @param sequenceName Name of the sequence.
220: * @param seq The sequence object to add.
221: */
222: private void addSequence(String sequenceName, HighLowSequence seq) {
223: // lookup the sequence map for calling DB
224: String jcdAlias = getBrokerForClass()
225: .serviceConnectionManager().getConnectionDescriptor()
226: .getJcdAlias();
227: Map mapForDB = (Map) sequencesDBMap.get(jcdAlias);
228: if (mapForDB == null) {
229: mapForDB = new HashMap();
230: }
231: mapForDB.put(sequenceName, seq);
232: sequencesDBMap.put(jcdAlias, mapForDB);
233: }
234:
235: /**
236: * Remove the sequence for given sequence name.
237: *
238: * @param sequenceName Name of the sequence to remove.
239: */
240: protected void removeSequence(String sequenceName) {
241: // lookup the sequence map for calling DB
242: Map mapForDB = (Map) sequencesDBMap.get(getBrokerForClass()
243: .serviceConnectionManager().getConnectionDescriptor()
244: .getJcdAlias());
245: if (mapForDB != null) {
246: synchronized (SequenceManagerHighLowImpl.class) {
247: mapForDB.remove(sequenceName);
248: }
249: }
250: }
251:
252: protected HighLowSequence getSequence(
253: PersistenceBroker brokerForSequence, FieldDescriptor field,
254: String sequenceName) throws SequenceManagerException {
255: HighLowSequence newSequence = null;
256: PersistenceBroker internBroker = null;
257: try {
258: /*
259: arminw:
260: we use a new internBroker instance, because we run into problems
261: when current internBroker was rollback, then we have new sequence
262: in memory, but not in database and a concurrent thread will
263: get the same sequence.
264: Thus we use a new internBroker instance (with new connection) to
265: avoid this problem.
266: */
267: internBroker = PersistenceBrokerFactory
268: .createPersistenceBroker(brokerForSequence
269: .getPBKey());
270: internBroker.beginTransaction();
271:
272: newSequence = lookupStoreSequence(internBroker, field,
273: sequenceName);
274:
275: internBroker.commitTransaction();
276:
277: if (log.isDebugEnabled())
278: log.debug("new sequence was " + newSequence);
279: } catch (Exception e) {
280: log.error("Can't lookup new HighLowSequence for field "
281: + (field != null ? field.getAttributeName() : null)
282: + " using sequence name " + sequenceName, e);
283: if (internBroker != null && internBroker.isInTransaction())
284: internBroker.abortTransaction();
285: throw new SequenceManagerException(
286: "Can't build new sequence", e);
287: } finally {
288: attempts = 0;
289: if (internBroker != null)
290: internBroker.close();
291: }
292: return newSequence;
293: }
294:
295: protected HighLowSequence lookupStoreSequence(
296: PersistenceBroker broker, FieldDescriptor field,
297: String seqName) {
298: HighLowSequence newSequence;
299: boolean needsInsert = false;
300:
301: Identity oid = broker.serviceIdentity().buildIdentity(
302: HighLowSequence.class, seqName);
303: // first we lookup sequence object in database
304: newSequence = (HighLowSequence) broker.getObjectByIdentity(oid);
305:
306: //not in db --> we have to store a new sequence
307: if (newSequence == null) {
308: if (log.isDebugEnabled()) {
309: log
310: .debug("sequence for field "
311: + field
312: + " not found in db, store new HighLowSequence");
313: }
314: /*
315: here we lookup the max key for the given field in system
316: */
317: // !!! here we use current broker instance to avoid deadlock !!!
318: long maxKey = getMaxKeyForSequence(getBrokerForClass(),
319: field);
320:
321: newSequence = newSequenceObject(seqName, field);
322: newSequence.setMaxKey(maxKey);
323: needsInsert = true;
324: }
325: // maybe property 'sequenceStart' was changed, so we check maxKey against
326: // current set sequence start index
327: if (newSequence.getMaxKey() < sequenceStart) {
328: newSequence.setMaxKey(sequenceStart);
329: }
330:
331: // set current grab size
332: newSequence.setGrabSize(grabSize);
333:
334: //grab the next key scope
335: newSequence.grabNextKeySet();
336:
337: //store the sequence to db
338: try {
339: if (needsInsert)
340: broker.store(newSequence, ObjectModification.INSERT);
341: else
342: broker.store(newSequence, ObjectModification.UPDATE);
343: } catch (OptimisticLockException e) {
344: // we try five times to get a new sequence
345: if (attempts < 5) {
346: log
347: .info("OptimisticLockException was thrown, will try again to store sequence. Sequence was "
348: + newSequence);
349: attempts++;
350: newSequence = lookupStoreSequence(broker, field,
351: seqName);
352: } else
353: throw e;
354: }
355: return newSequence;
356: }
357:
358: protected HighLowSequence newSequenceObject(String sequenceName,
359: FieldDescriptor field) {
360: HighLowSequence seq = new HighLowSequence();
361: seq.setName(sequenceName);
362: seq.setGrabSize(grabSize);
363: return seq;
364: }
365:
366: protected long getMaxKeyForSequence(PersistenceBroker broker,
367: FieldDescriptor field) {
368: long maxKey;
369: if (useGlobalSequenceIdentities) {
370: maxKey = sequenceStart;
371: } else {
372: /*
373: here we lookup the max key for the given field in system
374: */
375: maxKey = SequenceManagerHelper.getMaxForExtent(broker,
376: field);
377: // check against start index
378: maxKey = sequenceStart > maxKey ? sequenceStart : maxKey;
379: }
380: return maxKey;
381: }
382:
383: private String buildSequenceName(FieldDescriptor field)
384: throws SequenceManagerException {
385: String seqName;
386: if (useGlobalSequenceIdentities) {
387: seqName = GLOBAL_SEQUENCE_NAME;
388: } else {
389: seqName = calculateSequenceName(field);
390: }
391: return seqName;
392: }
393: }
|