001: /* ====================================================================
002: * The Jcorporate Apache Style Software License, Version 1.2 05-07-2002
003: *
004: * Copyright (c) 1995-2002 Jcorporate Ltd. All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions
008: * are met:
009: *
010: * 1. Redistributions of source code must retain the above copyright
011: * notice, this list of conditions and the following disclaimer.
012: *
013: * 2. Redistributions in binary form must reproduce the above copyright
014: * notice, this list of conditions and the following disclaimer in
015: * the documentation and/or other materials provided with the
016: * distribution.
017: *
018: * 3. The end-user documentation included with the redistribution,
019: * if any, must include the following acknowledgment:
020: * "This product includes software developed by Jcorporate Ltd.
021: * (http://www.jcorporate.com/)."
022: * Alternately, this acknowledgment may appear in the software itself,
023: * if and wherever such third-party acknowledgments normally appear.
024: *
025: * 4. "Jcorporate" and product names such as "Expresso" must
026: * not be used to endorse or promote products derived from this
027: * software without prior written permission. For written permission,
028: * please contact info@jcorporate.com.
029: *
030: * 5. Products derived from this software may not be called "Expresso",
031: * or other Jcorporate product names; nor may "Expresso" or other
032: * Jcorporate product names appear in their name, without prior
033: * written permission of Jcorporate Ltd.
034: *
035: * 6. No product derived from this software may compete in the same
036: * market space, i.e. framework, without prior written permission
037: * of Jcorporate Ltd. For written permission, please contact
038: * partners@jcorporate.com.
039: *
040: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
041: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
042: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
043: * DISCLAIMED. IN NO EVENT SHALL JCORPORATE LTD OR ITS CONTRIBUTORS
044: * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
045: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
046: * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
047: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
048: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
049: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
050: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
051: * SUCH DAMAGE.
052: * ====================================================================
053: *
054: * This software consists of voluntary contributions made by many
055: * individuals on behalf of the Jcorporate Ltd. Contributions back
056: * to the project(s) are encouraged when you make modifications.
057: * Please send them to support@jcorporate.com. For more information
058: * on Jcorporate Ltd. and its products, please see
059: * <http://www.jcorporate.com/>.
060: *
061: * Portions of this software are based upon other open source
062: * products and are subject to their respective licenses.
063: */
064:
065: package com.jcorporate.expresso.core.dbobj;
066:
067: import EDU.oswego.cs.dl.util.concurrent.ConcurrentReaderHashMap;
068: import com.jcorporate.expresso.core.dataobjects.DataFieldMetaData;
069: import com.jcorporate.expresso.core.dataobjects.jdbc.JDBCObjectMetaData;
070: import com.jcorporate.expresso.core.db.DBException;
071: import com.jcorporate.expresso.core.misc.ConfigManager;
072: import com.jcorporate.expresso.core.misc.ReusableLong;
073:
074: import java.util.Enumeration;
075: import java.util.Iterator;
076:
077: /**
078: * this class is a SOLITAIRE, used in NextNumber
079: *
080: * @author Original by Michael Nash, rewritten by Michael Rimov, Larry Hamel
081: * <p/>
082: * Modify by Yves Henri AMAIZO <amy_amaizo@compuserve.com>
083: * @see com.jcorporate.expresso.core.dbobj.NextNumber
084: * NextNumber - Manages in-memory, database independent autoincrement values. This
085: * version is not cluster safe. See Expresso Enterprise for a cluster-safe version.
086: * @since $DatabaseSchema $Date: 2004/11/17 20:48:11 $
087: */
088: public class NextNumberImpl extends NextNumber {
089:
090: /**
091: * Top level hashMap - This level separates the db contexts. The object
092: * returned by a get() is another Map() containing lists of dbobject
093: * fields. The key is DBObject.getTargetTable() + "." + DBField.getFieldName()
094: * this will return the Integer object that will be the number is internally
095: * incremented,
096: */
097: protected volatile ConcurrentReaderHashMap contextMap = new ConcurrentReaderHashMap();
098:
099: private Object mInitLock = new Object();
100:
101: /**
102: * Create the base level hashes for each db key.
103: */
104: public NextNumberImpl() {
105: for (Enumeration e = ConfigManager.getAllConfigKeys(); e
106: .hasMoreElements();) {
107: String configKey = (String) e.nextElement();
108: contextMap.put(configKey, new ConcurrentReaderHashMap(40));
109: }
110: }
111:
112: /**
113: * Initializes hash entries for all auto-inc fields in a particular DBOBject. This is
114: * called the first time that a particular DBObject has a nextnumber called for any field.
115: * this method should be synchronized externally.
116: *
117: * @param db The db to hook this nextnumber object to.
118: * @param callingObject the object to initialize from.
119: */
120: public void initialize(String db, DBObject callingObject)
121: throws DBException {
122: callingObject.setDataContext(db);
123:
124: Iterator i = callingObject.getMetaData().getAllFieldsMap()
125: .values().iterator();
126: while (i.hasNext()) {
127: DBField dbf = (DBField) i.next();
128:
129: if (dbf.isAutoIncremented()) {
130: String fieldName = dbf.getName();
131: // registerField has proper synchronization
132: registerField(db, callingObject, fieldName);
133: }
134:
135: } /* While More Fields */
136: }
137:
138: /**
139: * Register a field for next number information.
140: */
141: public void registerField(String db, DBObject callingDBOBject,
142: String fieldName) throws DBException {
143: String hashKey = getKey(callingDBOBject, fieldName);
144: ConcurrentReaderHashMap nextNumMap = (ConcurrentReaderHashMap) contextMap
145: .get(db);
146:
147: if (nextNumMap == null) {
148: throw new DBException(
149: "Uninitialized nextnumber context map");
150: }
151: if (!nextNumMap.containsKey(hashKey)) {
152:
153: // no need to sync for getMax; efficient to keep this out of sync
154: long l = getMax(db, callingDBOBject, fieldName);
155: long setValue = 0;
156: boolean didInit = false;
157:
158: // nextNumMap is unique item corresponding to db, and
159: // we are part of a solitaire object, so this sync is
160: // effectively a class-object (static) sync
161: synchronized (nextNumMap) {
162: // retest after getting sync lock
163: if (!nextNumMap.containsKey(hashKey)) {
164: nextNumMap.put(hashKey, new ReusableLong(l));
165: // check setting for logging
166: setValue = ((ReusableLong) nextNumMap.get(hashKey))
167: .longValue();
168: didInit = true;
169: }
170: } /* synchronized */
171:
172: if (didInit && log.isDebugEnabled()) {
173: log.debug("initialized hash entry for key: " + hashKey
174: + " to value: " + l + " and confirmed value: "
175: + setValue);
176: }
177:
178: } /* If key is not present */
179: }
180:
181: /**
182: * Returns the next number object without checking correctness for input parameters
183: * May result in NullPointerException if the table is fed bad input.
184: * if the field is not an auto-inc field, a warning will be logged.
185: *
186: * @param db The context to get this out of.
187: * @param callingDBObject - the DBObject that this nextnumber object belongs to.
188: * @param fieldName - the field name within the callingObject that this nextnumber belongs to.
189: * <p/>
190: * Modify by Yves Henri AMAIZO <amy_amaizo@compuserve.com>
191: * @since $DatabaseSchema $Date: 2004/11/17 20:48:11 $
192: */
193: private ReusableLong getNextNumberObject(String db,
194: DBObject callingDBObject, String fieldName)
195: throws DBException {
196: String key = getKey(callingDBObject, fieldName);
197: ConcurrentReaderHashMap myDBMap = (ConcurrentReaderHashMap) contextMap
198: .get(db);
199: ReusableLong rl = (ReusableLong) myDBMap.get(key);
200:
201: if (rl == null) {
202: // synchronize in order to permit only one initialization per key
203: // if we assume that the JVM is pooling strings, then we could synchronize
204: // on just the string. However, the penalty we pay in synchronizing on
205: // "mInitLock" is small because it only happens for each callingobject on the first call
206: synchronized (mInitLock) {
207: // retest after getting sync
208: if (myDBMap.get(key) == null) {
209: initialize(db, callingDBObject);
210: }
211: }
212:
213: rl = (ReusableLong) myDBMap.get(key);
214:
215: if (rl == null) {
216: // unexpected since initialize() will register all auto-inc fields
217: // is this field an increment field?
218: DataFieldMetaData meta = callingDBObject
219: .getFieldMetaData(fieldName);
220: if (!meta.isAutoIncremented() && log.isDebugEnabled()) {
221: log
222: .debug("NextNumber.getNext() called for table.field, '"
223: + ((JDBCObjectMetaData) callingDBObject
224: .getMetaData())
225: .getTargetSQLTable(callingDBObject
226: .getDataContext())
227: + "."
228: + fieldName
229: + "', which is not auto-incrementing type. Setting up hash entry anyway.");
230: }
231: registerField(db, callingDBObject, fieldName);
232: rl = (ReusableLong) myDBMap.get(key);
233: }
234:
235: if (rl == null) {
236: throw new DBException(
237: "Unable to locate nextnumber entry for key: "
238: + key);
239: }
240: }
241:
242: return rl;
243: }
244:
245: /**
246: * All parameters are
247: * fully checked to avoid potential NullPointerExceptions
248: *
249: * @param db The context to get this out of.
250: * @param callingObject - the DBObject that this nextnumber object belongs to.
251: * @param fieldName - the field name within the callingObject that this nextnumber belongs to.
252: */
253: protected void checkParams(String db, DBObject callingObject,
254: String fieldName) throws DBException {
255:
256: if (db == null || db.length() == 0) {
257: throw new DBException(
258: "NextNumber parameter 'db' must not be null or zero length");
259: }
260: if (callingObject == null) {
261: throw new DBException(
262: "NextNumber parameter 'callingDBOBject' must not be null");
263: }
264: if (fieldName == null) {
265: throw new DBException(
266: "NextNumber parameter 'fieldName' must not be null");
267: }
268:
269: if (contextMap.get(db) == null) {
270: throw new DBException(
271: "NextNumber Unable to locate nextnumber map for context "
272: + db);
273: }
274:
275: }
276:
277: /**
278: * Get the nextnumber for this dbobject. Increments the internal value.
279: */
280: public long getNext(String db, DBObject callingDBObject,
281: String fieldName) throws DBException {
282:
283: if (CHECK_PARAMETERS) {
284: checkParams(db, callingDBObject, fieldName);
285: }
286:
287: ReusableLong theNextNumberObject = getNextNumberObject(db,
288: callingDBObject, fieldName);
289:
290: long result;
291:
292: // theNextNumberObject is unique item corresponding to db + object table, and
293: // we are part of a solitaire object, so this sync is
294: // effectively a class-object (static) sync corresponding to the object table
295: synchronized (theNextNumberObject) {
296: result = theNextNumberObject.longValue();
297: result++;
298: theNextNumberObject.setLong(result);
299: }
300:
301: if (log.isDebugEnabled()) {
302: log.debug("getNext returns, for hash key: "
303: + getKey(callingDBObject, fieldName)
304: + " the value: " + result);
305: }
306:
307: return result;
308: }
309:
310: /**
311: * Reset the counts for the paritcular db each subsequent operation will
312: * require a new getMax(). There is questionable threadsafety about the
313: * reset methods. Please only do it on a "non-live" server. Used after a
314: * DBCreate or DeleteSchema has been called.
315: */
316: public void reset(String db) {
317: ConcurrentReaderHashMap myDBMap = (ConcurrentReaderHashMap) contextMap
318: .get(db);
319:
320: if (myDBMap != null) {
321: // theNextNumberObject is unique item corresponding to db + object table, and
322: // we are part of a solitaire object, so this sync is
323: // effectively a class-object (static) sync corresponding to the object table
324: synchronized (myDBMap) {
325: myDBMap.clear();
326: }
327: }
328: }
329:
330: /**
331: * Clears the table on a particular dbobject. Similar to reset db
332: *
333: * @param db The db context that the next number resides in.
334: * @param callingObject the object that links to the various nextnumber
335: * objects
336: */
337: public void reset(String db, DBObject callingObject) {
338: ConcurrentReaderHashMap myDBMap = (ConcurrentReaderHashMap) contextMap
339: .get(db);
340:
341: try {
342: if (myDBMap != null) {
343: Iterator i = callingObject.getMetaData()
344: .getAllFieldsMap().values().iterator();
345:
346: while (i.hasNext()) {
347: DBField dbf = (DBField) i.next();
348:
349: if (dbf.isAutoIncremented()) {
350: String fieldName = dbf.getName();
351: String hashKey = getKey(callingObject,
352: fieldName);
353:
354: if (myDBMap.containsKey(hashKey)) {
355:
356: // hm is unique item corresponding to db, and
357: // we are part of a solitaire object, so this sync is
358: // effectively a class-object (static) sync
359: synchronized (myDBMap) {
360: // retest after getting sync
361: if (myDBMap.containsKey(hashKey)) {
362: myDBMap.remove(hashKey);
363: }
364: } /* synchronized */
365:
366: } /* If key is not present */
367:
368: } /* if the type == auto-inc */
369:
370: } /* While More Fields */
371: }
372: } catch (DBException dbe) {
373: log
374: .error("Error clearing one dbobject for next number, clearing whole stack");
375:
376: if (myDBMap != null) {
377: synchronized (myDBMap) {
378: myDBMap.clear();
379: }
380: }
381: }
382: }
383: }
|