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