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 }