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.sql.ResultSet;
019    import java.sql.Statement;
020    import java.util.Collection;
021    import java.util.Iterator;
022    import java.util.Properties;
023    import java.util.Vector;
024    
025    import org.apache.ojb.broker.PersistenceBroker;
026    import org.apache.ojb.broker.PersistenceBrokerException;
027    import org.apache.ojb.broker.accesslayer.StatementManagerIF;
028    import org.apache.ojb.broker.metadata.ClassDescriptor;
029    import org.apache.ojb.broker.metadata.FieldDescriptor;
030    import org.apache.ojb.broker.query.Query;
031    import org.apache.ojb.broker.util.logging.Logger;
032    import org.apache.ojb.broker.util.logging.LoggerFactory;
033    
034    /**
035     * Helper class for SequenceManager implementations.
036     *
037     * @author <a href="mailto:armin@codeAuLait.de">Armin Waibel</a>
038     * @version $Id: SequenceManagerHelper.java,v 1.1 2007-08-24 22:17:29 ewestfal Exp $
039     */
040    public class SequenceManagerHelper
041    {
042        private static Logger log = LoggerFactory.getLogger(SequenceManagerHelper.class);
043    
044        /**
045         * Property name used to configure sequence manager implementations.
046         */
047        public static final String PROP_SEQ_AS = "seq.as";
048        /**
049         * Property name used to configure sequence manager implementations.
050         * @deprecated use {@link #PROP_SEQ_START} instead.
051         */
052        public static final String PROP_SEQ_START_OLD = "sequenceStart";
053        /**
054         * Property name used to configure sequence manager implementations.
055         */
056        public static final String PROP_SEQ_START = "seq.start";
057        /**
058         * Property name used to configure sequence manager implementations.
059         */
060        public static final String PROP_SEQ_INCREMENT_BY = "seq.incrementBy";
061        /**
062         * Property name used to configure sequence manager implementations.
063         */
064        public static final String PROP_SEQ_MAX_VALUE = "seq.maxValue";
065        /**
066         * Property name used to configure sequence manager implementations.
067         */
068        public static final String PROP_SEQ_MIN_VALUE = "seq.minValue";
069        /**
070         * Property name used to configure sequence manager implementations.
071         */
072        public static final String PROP_SEQ_CYCLE = "seq.cycle";
073        /**
074         * Property name used to configure sequence manager implementations.
075         */
076        public static final String PROP_SEQ_CACHE = "seq.cache";
077        /**
078         * Property name used to configure sequence manager implementations.
079         */
080        public static final String PROP_SEQ_ORDER = "seq.order";
081    
082        private static final String SEQ_PREFIX = "SEQ_";
083        private static final String SEQ_UNASSIGNED = "UNASSIGNED";
084        private static final String SM_SELECT_MAX = "SELECT MAX(";
085        private static final String SM_FROM = ") FROM ";
086    
087        /**
088         * Prefix for global sequence names.
089         */
090    
091        /**
092         * Returns a unique sequence name (unique across all extents).
093         * <br/>
094         * If we found a non null value for the 'sequence-name' attribute in
095         * the field descriptor, we use the 'sequence-name' value as sequence name.
096         * <br/>
097         * Else if the top-level class of the target class has extents,
098         * we take the first extent class table name of the extents as
099         * 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    }