Coverage Report - org.apache.ojb.broker.util.sequence.SequenceManagerHighLowImpl
 
Classes in this File Line Coverage Branch Coverage Complexity
SequenceManagerHighLowImpl
N/A
N/A
3.7
 
 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  
 }