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.sql.ResultSet;
19  import java.sql.Statement;
20  import java.util.Collection;
21  import java.util.Iterator;
22  import java.util.Properties;
23  import java.util.Vector;
24  
25  import org.apache.ojb.broker.PersistenceBroker;
26  import org.apache.ojb.broker.PersistenceBrokerException;
27  import org.apache.ojb.broker.accesslayer.StatementManagerIF;
28  import org.apache.ojb.broker.metadata.ClassDescriptor;
29  import org.apache.ojb.broker.metadata.FieldDescriptor;
30  import org.apache.ojb.broker.query.Query;
31  import org.apache.ojb.broker.util.logging.Logger;
32  import org.apache.ojb.broker.util.logging.LoggerFactory;
33  
34  /**
35   * Helper class for SequenceManager implementations.
36   *
37   * @author <a href="mailto:armin@codeAuLait.de">Armin Waibel</a>
38   * @version $Id: SequenceManagerHelper.java,v 1.1 2007-08-24 22:17:29 ewestfal Exp $
39   */
40  public class SequenceManagerHelper
41  {
42      private static Logger log = LoggerFactory.getLogger(SequenceManagerHelper.class);
43  
44      /**
45       * Property name used to configure sequence manager implementations.
46       */
47      public static final String PROP_SEQ_AS = "seq.as";
48      /**
49       * Property name used to configure sequence manager implementations.
50       * @deprecated use {@link #PROP_SEQ_START} instead.
51       */
52      public static final String PROP_SEQ_START_OLD = "sequenceStart";
53      /**
54       * Property name used to configure sequence manager implementations.
55       */
56      public static final String PROP_SEQ_START = "seq.start";
57      /**
58       * Property name used to configure sequence manager implementations.
59       */
60      public static final String PROP_SEQ_INCREMENT_BY = "seq.incrementBy";
61      /**
62       * Property name used to configure sequence manager implementations.
63       */
64      public static final String PROP_SEQ_MAX_VALUE = "seq.maxValue";
65      /**
66       * Property name used to configure sequence manager implementations.
67       */
68      public static final String PROP_SEQ_MIN_VALUE = "seq.minValue";
69      /**
70       * Property name used to configure sequence manager implementations.
71       */
72      public static final String PROP_SEQ_CYCLE = "seq.cycle";
73      /**
74       * Property name used to configure sequence manager implementations.
75       */
76      public static final String PROP_SEQ_CACHE = "seq.cache";
77      /**
78       * Property name used to configure sequence manager implementations.
79       */
80      public static final String PROP_SEQ_ORDER = "seq.order";
81  
82      private static final String SEQ_PREFIX = "SEQ_";
83      private static final String SEQ_UNASSIGNED = "UNASSIGNED";
84      private static final String SM_SELECT_MAX = "SELECT MAX(";
85      private static final String SM_FROM = ") FROM ";
86  
87      /**
88       * Prefix for global sequence names.
89       */
90  
91      /**
92       * Returns a unique sequence name (unique across all extents).
93       * <br/>
94       * If we found a non null value for the 'sequence-name' attribute in
95       * the field descriptor, we use the 'sequence-name' value as sequence name.
96       * <br/>
97       * Else if the top-level class of the target class has extents,
98       * we take the first extent class table name of the extents as
99       * sequence name.
100      * <br/>
101      * Else we take the table name of the target class.
102      * <p>
103      * If the method argument 'autoNaming' is true, the generated
104      * sequence name will be set in the given field descriptor
105      * using {@link org.apache.ojb.broker.metadata.FieldDescriptor#setSequenceName}
106      * to speed up sequence name lookup in future calls.
107      * </p>
108      * @param brokerForClass current used PB instance
109      * @param field target field
110      * @param autoNaming if 'false' no auto sequence name was build and
111      * a exception was throw if none could be found in field.
112      */
113     public static String buildSequenceName(PersistenceBroker brokerForClass,
114                                            FieldDescriptor field, boolean autoNaming)
115             throws SequenceManagerException
116     {
117         String seqName = field.getSequenceName();
118         /*
119         if we found a sequence name bound to the field descriptor
120         via 'sequence-name' attribute we use that name
121         */
122         if (seqName != null && seqName.trim().length() != 0)
123         {
124             return seqName;
125         }
126         else if (!autoNaming)
127         {
128             /*
129             arminw:
130             we don't find a sequence name and we should not automatic build one,
131             thus we throw an exception
132             */
133             throw new SequenceManagerException("Could not find sequence-name for field '" +
134                     field + "' of class '" + field.getClassDescriptor().getClassNameOfObject() +
135                     "', property 'autoNaming' in sequence-manager element in repository was '" +
136                     autoNaming + "'. Set autoNaming true in sequence-descriptor or define a " +
137                     " sequence-name in field-descriptor.");
138         }
139 
140         ClassDescriptor cldTargetClass = field.getClassDescriptor();
141         /*
142         check for inheritance on multiple table
143         */
144         cldTargetClass = findInheritanceRoot(cldTargetClass);
145         Class topLevel = brokerForClass.getTopLevelClass(cldTargetClass.getClassOfObject());
146         ClassDescriptor cldTopLevel = brokerForClass.getClassDescriptor(topLevel);
147         /**
148          *
149          * MBAIRD
150          * Should not use classname for the sequenceName as we will end up
151          * re-using sequence numbers for classes mapped to the same table.
152          * Instead, make the FullTableName the discriminator since it will
153          * always be unique for that table, and hence that class.
154          *
155          * arminw:
156          * If the found top-level class has extents, we take the first
157          * found extent class table name as sequence name. Else we take
158          * the table name of the 'targetClass'.
159          *
160          */
161         if (cldTopLevel.isExtent())
162         {
163             /*
164             arminw:
165             this is a little critical, because we do not know if the extent classes
166             will change by and by and the first found extent class may change, thus the
167             returned table name could change!
168             But I don't know a way to resolve this problem. I put a comment to the
169             sequence manager docs
170             TODO: find better solution
171             */
172 //            seqName = brokerForClass.getClassDescriptor(((Class) cldTopLevel.getExtentClasses().
173 //                    get(0))).getFullTableName();
174             seqName = firstFoundTableName(brokerForClass, cldTopLevel);
175         }
176         else
177         {
178             seqName = cldTargetClass.getFullTableName();
179         }
180 //        log.info("* targetClass: "+targetClass +", toplevel: "+topLevel+ " seqName: "+seqName);
181         if (seqName == null)
182         {
183             seqName = SEQ_UNASSIGNED;
184             log.warn("Too complex structure, can not assign automatic sequence name for field '" +
185                     field.getAttributeName() + "' in class '" +
186                     field.getClassDescriptor().getClassNameOfObject() +
187                     "'. Use a default sequence name instead: " + (SEQ_PREFIX + seqName));
188         }
189 //        System.out.println("* targetClass: " + cldTargetClass.getClassNameOfObject() + ", toplevel: " + topLevel + " seqName: " + seqName);
190         seqName = SEQ_PREFIX + seqName;
191         if (log.isDebugEnabled())
192                 log.debug("Set automatic generated sequence-name for field '" +
193                         field.getAttributeName() + "' in class '" +
194                         field.getClassDescriptor().getClassNameOfObject() +
195                         "'.");
196         field.setSequenceName(seqName);
197         return seqName;
198     }
199 
200     /**
201      * Returns the root {@link org.apache.ojb.broker.metadata.ClassDescriptor} of the inheriatance
202      * hierachy of the given descriptor or the descriptor itself if no inheriatance on multiple table is
203      * used.
204      */
205     private static ClassDescriptor findInheritanceRoot(ClassDescriptor cld)
206     {
207         ClassDescriptor result = cld;
208         if(cld.getSuperClassDescriptor() != null)
209         {
210             result = findInheritanceRoot(cld.getSuperClassDescriptor());
211         }
212         return result;
213     }
214 
215     /**
216      * try to find the first none null table name for the given class-descriptor.
217      * If cld has extent classes, all of these cld's searched for the first none null
218      * table name.
219      */
220     private static String firstFoundTableName(PersistenceBroker brokerForClass, ClassDescriptor cld)
221     {
222         String name = null;
223         if (!cld.isInterface() && cld.getFullTableName() != null)
224         {
225             return cld.getFullTableName();
226         }
227         if (cld.isExtent())
228         {
229             Collection extentClasses = cld.getExtentClasses();
230             for (Iterator iterator = extentClasses.iterator(); iterator.hasNext();)
231             {
232                 name = firstFoundTableName(brokerForClass, brokerForClass.getClassDescriptor((Class) iterator.next()));
233                 // System.out.println("## " + cld.getClassNameOfObject()+" - name: "+name);
234                 if (name != null) break;
235             }
236         }
237         return name;
238     }
239 
240     /**
241      * Lookup all tables associated with given class (search all extent classes)
242      * to find the current maximum value for the given field.
243      * <br><b>Note:</b> Only works for <code>long</code> autoincrement fields.
244      * @param brokerForClass persistence broker instance match the database of the
245      * given field/class
246      * @param field the target field
247      */
248     public static long getMaxForExtent(PersistenceBroker brokerForClass, FieldDescriptor field) throws PersistenceBrokerException
249     {
250         if (field == null)
251         {
252             log.error("Given FieldDescriptor was null, could not detect max value across all extents");
253             return 0;
254             // throw new PersistenceBrokerException("Given FieldDescriptor was null");
255         }
256         // first lookup top-level class
257         Class topLevel = brokerForClass.getTopLevelClass(field.getClassDescriptor().getClassOfObject());
258         return getMaxId(brokerForClass, topLevel, field);
259     }
260 
261     /**
262      * Search down all extent classes and return max of all found
263      * PK values.
264      */
265     public static long getMaxId(PersistenceBroker brokerForClass, Class topLevel, FieldDescriptor original) throws PersistenceBrokerException
266     {
267         long max = 0;
268         long tmp;
269         ClassDescriptor cld = brokerForClass.getClassDescriptor(topLevel);
270 
271         // if class is not an interface / not abstract we have to search its directly mapped table
272         if (!cld.isInterface() && !cld.isAbstract())
273         {
274             tmp = getMaxIdForClass(brokerForClass, cld, original);
275             if (tmp > max)
276             {
277                 max = tmp;
278             }
279         }
280         // if class is an extent we have to search through its subclasses
281         if (cld.isExtent())
282         {
283             Vector extentClasses = cld.getExtentClasses();
284             for (int i = 0; i < extentClasses.size(); i++)
285             {
286                 Class extentClass = (Class) extentClasses.get(i);
287                 if (cld.getClassOfObject().equals(extentClass))
288                 {
289                     throw new PersistenceBrokerException("Circular extent in " + extentClass +
290                             ", please check the repository");
291                 }
292                 else
293                 {
294                     // fix by Mark Rowell
295                     // Call recursive
296                     tmp = getMaxId(brokerForClass, extentClass, original);
297                 }
298                 if (tmp > max)
299                 {
300                     max = tmp;
301                 }
302             }
303         }
304         return max;
305     }
306 
307     /**
308      * lookup current maximum value for a single field in
309      * table the given class descriptor was associated.
310      */
311     public static long getMaxIdForClass(
312             PersistenceBroker brokerForClass, ClassDescriptor cldForOriginalOrExtent, FieldDescriptor original)
313             throws PersistenceBrokerException
314     {
315         FieldDescriptor field = null;
316         if (!original.getClassDescriptor().equals(cldForOriginalOrExtent))
317         {
318             // check if extent match not the same table
319             if (!original.getClassDescriptor().getFullTableName().equals(
320                     cldForOriginalOrExtent.getFullTableName()))
321             {
322                 // we have to look for id's in extent class table
323                 field = cldForOriginalOrExtent.getFieldDescriptorByName(original.getAttributeName());
324             }
325         }
326         else
327         {
328             field = original;
329         }
330         if (field == null)
331         {
332             // if null skip this call
333             return 0;
334         }
335 
336         String column = field.getColumnName();
337         long result = 0;
338         ResultSet rs = null;
339         Statement stmt = null;
340         StatementManagerIF sm = brokerForClass.serviceStatementManager();
341         String table = cldForOriginalOrExtent.getFullTableName();
342         // String column = cld.getFieldDescriptorByName(fieldName).getColumnName();
343         String sql = SM_SELECT_MAX + column + SM_FROM + table;
344         try
345         {
346             //lookup max id for the current class
347             stmt = sm.getGenericStatement(cldForOriginalOrExtent, Query.NOT_SCROLLABLE);
348             rs = stmt.executeQuery(sql);
349             rs.next();
350             result = rs.getLong(1);
351         }
352         catch (Exception e)
353         {
354             log.warn("Cannot lookup max value from table " + table + " for column " + column +
355                     ", PB was " + brokerForClass + ", using jdbc-descriptor " +
356                     brokerForClass.serviceConnectionManager().getConnectionDescriptor(), e);
357         }
358         finally
359         {
360             try
361             {
362                 sm.closeResources(stmt, rs);
363             }
364             catch (Exception ignore)
365             {
366                 // ignore it
367            }
368         }
369         return result;
370     }
371 
372     /**
373      * Database sequence properties helper method.
374      * Return sequence <em>start value</em> or <em>null</em>
375      * if not set.
376      *
377      * @param prop The {@link java.util.Properties} instance to use.
378      * @return The found expression or <em>null</em>.
379      */
380     public static Long getSeqStart(Properties prop)
381     {
382         String result = prop.getProperty(PROP_SEQ_START, null);
383         if(result == null)
384         {
385             result = prop.getProperty(PROP_SEQ_START_OLD, null);
386         }
387         if(result != null)
388         {
389             return new Long(Long.parseLong(result));
390         }
391         else
392         {
393             return null;
394         }
395     }
396 
397     /**
398      * Database sequence properties helper method.
399      * Return sequence <em>increment by value</em> or <em>null</em>
400      * if not set.
401      *
402      * @param prop The {@link java.util.Properties} instance to use.
403      * @return The found expression or <em>null</em>.
404      */
405     public static Long getSeqIncrementBy(Properties prop)
406     {
407         String result = prop.getProperty(PROP_SEQ_INCREMENT_BY, null);
408         if(result != null)
409         {
410             return new Long(Long.parseLong(result));
411         }
412         else
413         {
414             return null;
415         }
416     }
417 
418     /**
419      * Database sequence properties helper method.
420      * Return sequence <em>max value</em> or <em>null</em>
421      * if not set.
422      *
423      * @param prop The {@link java.util.Properties} instance to use.
424      * @return The found expression or <em>null</em>.
425      */
426     public static Long getSeqMaxValue(Properties prop)
427     {
428         String result = prop.getProperty(PROP_SEQ_MAX_VALUE, null);
429         if(result != null)
430         {
431             return new Long(Long.parseLong(result));
432         }
433         else
434         {
435             return null;
436         }
437     }
438 
439     /**
440      * Database sequence properties helper method.
441      * Return sequence <em>min value</em> or <em>null</em>
442      * if not set.
443      *
444      * @param prop The {@link java.util.Properties} instance to use.
445      * @return The found expression or <em>null</em>.
446      */
447     public static Long getSeqMinValue(Properties prop)
448     {
449         String result = prop.getProperty(PROP_SEQ_MIN_VALUE, null);
450         if(result != null)
451         {
452             return new Long(Long.parseLong(result));
453         }
454         else
455         {
456             return null;
457         }
458     }
459 
460     /**
461      * Database sequence properties helper method.
462      * Return sequence <em>cache value</em> or <em>null</em>
463      * if not set.
464      *
465      * @param prop The {@link java.util.Properties} instance to use.
466      * @return The found expression or <em>null</em>.
467      */
468     public static Long getSeqCacheValue(Properties prop)
469     {
470         String result = prop.getProperty(PROP_SEQ_CACHE, null);
471         if(result != null)
472         {
473             return new Long(Long.parseLong(result));
474         }
475         else
476         {
477             return null;
478         }
479     }
480 
481     /**
482      * Database sequence properties helper method.
483      * Return sequence <em>cycle</em> Booelan or <em>null</em>
484      * if not set.
485      *
486      * @param prop The {@link java.util.Properties} instance to use.
487      * @return The found expression or <em>null</em>.
488      */
489     public static Boolean getSeqCycleValue(Properties prop)
490     {
491         String result = prop.getProperty(PROP_SEQ_CYCLE, null);
492         if(result != null)
493         {
494             return Boolean.valueOf(result);
495         }
496         else
497         {
498             return null;
499         }
500     }
501 
502     /**
503      * Database sequence properties helper method.
504      * Return sequence <em>order</em> Booelan or <em>null</em>
505      * if not set.
506      *
507      * @param prop The {@link java.util.Properties} instance to use.
508      * @return The found expression or <em>null</em>.
509      */
510     public static Boolean getSeqOrderValue(Properties prop)
511     {
512         String result = prop.getProperty(PROP_SEQ_ORDER, null);
513         if(result != null)
514         {
515             return Boolean.valueOf(result);
516         }
517         else
518         {
519             return null;
520         }
521     }
522 
523     /**
524      * Database sequence properties helper method.
525      * Return the datatype to set for the sequence or <em>null</em>
526      * if not set.
527      *
528      * @param prop The {@link java.util.Properties} instance to use.
529      * @return The found expression or <em>null</em>.
530      */
531     public static String getSeqAsValue(Properties prop)
532     {
533         return prop.getProperty(PROP_SEQ_AS, null);
534     }
535 }