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    }