KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > objectstyle > cayenne > dba > JdbcPkGenerator


1 /* ====================================================================
2  *
3  * The ObjectStyle Group Software License, version 1.1
4  * ObjectStyle Group - http://objectstyle.org/
5  *
6  * Copyright (c) 2002-2005, Andrei (Andrus) Adamchik and individual authors
7  * of the software. All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  *
13  * 1. Redistributions of source code must retain the above copyright
14  * notice, this list of conditions and the following disclaimer.
15  *
16  * 2. Redistributions in binary form must reproduce the above copyright
17  * notice, this list of conditions and the following disclaimer in
18  * the documentation and/or other materials provided with the
19  * distribution.
20  *
21  * 3. The end-user documentation included with the redistribution, if any,
22  * must include the following acknowlegement:
23  * "This product includes software developed by independent contributors
24  * and hosted on ObjectStyle Group web site (http://objectstyle.org/)."
25  * Alternately, this acknowlegement may appear in the software itself,
26  * if and wherever such third-party acknowlegements normally appear.
27  *
28  * 4. The names "ObjectStyle Group" and "Cayenne" must not be used to endorse
29  * or promote products derived from this software without prior written
30  * permission. For written permission, email
31  * "andrus at objectstyle dot org".
32  *
33  * 5. Products derived from this software may not be called "ObjectStyle"
34  * or "Cayenne", nor may "ObjectStyle" or "Cayenne" appear in their
35  * names without prior written permission.
36  *
37  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
38  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40  * DISCLAIMED. IN NO EVENT SHALL THE OBJECTSTYLE GROUP OR
41  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
42  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
43  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
44  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
46  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
47  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
48  * SUCH DAMAGE.
49  * ====================================================================
50  *
51  * This software consists of voluntary contributions made by many
52  * individuals and hosted on ObjectStyle Group web site. For more
53  * information on the ObjectStyle Group, please see
54  * <http://objectstyle.org/>.
55  */

56
57 package org.objectstyle.cayenne.dba;
58
59 import java.sql.Connection JavaDoc;
60 import java.sql.DatabaseMetaData JavaDoc;
61 import java.sql.ResultSet JavaDoc;
62 import java.sql.SQLException JavaDoc;
63 import java.sql.Statement JavaDoc;
64 import java.sql.Types JavaDoc;
65 import java.util.ArrayList JavaDoc;
66 import java.util.Collections JavaDoc;
67 import java.util.HashMap JavaDoc;
68 import java.util.Iterator JavaDoc;
69 import java.util.List JavaDoc;
70 import java.util.Map JavaDoc;
71
72 import org.objectstyle.cayenne.CayenneRuntimeException;
73 import org.objectstyle.cayenne.access.DataNode;
74 import org.objectstyle.cayenne.access.QueryLogger;
75 import org.objectstyle.cayenne.access.util.DefaultOperationObserver;
76 import org.objectstyle.cayenne.map.DbAttribute;
77 import org.objectstyle.cayenne.map.DbEntity;
78 import org.objectstyle.cayenne.map.DbKeyGenerator;
79 import org.objectstyle.cayenne.map.ObjAttribute;
80 import org.objectstyle.cayenne.query.Query;
81 import org.objectstyle.cayenne.query.SQLTemplate;
82 import org.objectstyle.cayenne.util.IDUtil;
83
84 /**
85  * Default primary key generator implementation. Uses a lookup table named
86  * "AUTO_PK_SUPPORT" to search and increment primary keys for tables.
87  *
88  * @author Andrei Adamchik
89  */

90 public class JdbcPkGenerator implements PkGenerator {
91     public static final int DEFAULT_PK_CACHE_SIZE = 20;
92
93     /**
94      * @deprecated Since 1.2 unused.
95      */

96     protected static final String JavaDoc NEXT_ID = "NEXT_ID";
97     
98     /**
99      * @deprecated Since 1.2 unused.
100      */

101     protected static final ObjAttribute[] objDesc =
102         new ObjAttribute[] { new ObjAttribute("nextId", Integer JavaDoc.class.getName(), null)};
103     
104     /**
105      * @deprecated Since 1.2 unused.
106      */

107     protected static final DbAttribute[] resultDesc =
108         new DbAttribute[] { new DbAttribute(NEXT_ID, Types.INTEGER, null)};
109
110     
111     protected Map JavaDoc pkCache = new HashMap JavaDoc();
112     protected int pkCacheSize = DEFAULT_PK_CACHE_SIZE;
113
114     static {
115         objDesc[0].setDbAttributePath(NEXT_ID);
116     }
117
118     public void createAutoPk(DataNode node, List JavaDoc dbEntities) throws Exception JavaDoc {
119         // check if a table exists
120

121         // create AUTO_PK_SUPPORT table
122
if (!autoPkTableExists(node)) {
123             runUpdate(node, pkTableCreateString());
124         }
125
126         // delete any existing pk entries
127
runUpdate(node, pkDeleteString(dbEntities));
128
129         // insert all needed entries
130
Iterator JavaDoc it = dbEntities.iterator();
131         while (it.hasNext()) {
132             DbEntity ent = (DbEntity) it.next();
133             runUpdate(node, pkCreateString(ent.getName()));
134         }
135     }
136
137     public List JavaDoc createAutoPkStatements(List JavaDoc dbEntities) {
138         List JavaDoc list = new ArrayList JavaDoc();
139
140         list.add(pkTableCreateString());
141         list.add(pkDeleteString(dbEntities));
142
143         Iterator JavaDoc it = dbEntities.iterator();
144         while (it.hasNext()) {
145             DbEntity ent = (DbEntity) it.next();
146             list.add(pkCreateString(ent.getName()));
147         }
148
149         return list;
150     }
151
152     /**
153      * Drops table named "AUTO_PK_SUPPORT" if it exists in the
154      * database.
155      */

156     public void dropAutoPk(DataNode node, List JavaDoc dbEntities) throws Exception JavaDoc {
157         if (autoPkTableExists(node)) {
158             runUpdate(node, dropAutoPkString());
159         }
160     }
161
162     public List JavaDoc dropAutoPkStatements(List JavaDoc dbEntities) {
163         List JavaDoc list = new ArrayList JavaDoc();
164         list.add(dropAutoPkString());
165         return list;
166     }
167
168     protected String JavaDoc pkTableCreateString() {
169         StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
170         buf
171             .append("CREATE TABLE AUTO_PK_SUPPORT (")
172             .append(" TABLE_NAME CHAR(100) NOT NULL,")
173             .append(" NEXT_ID INTEGER NOT NULL")
174             .append(")");
175
176         return buf.toString();
177     }
178
179     protected String JavaDoc pkDeleteString(List JavaDoc dbEntities) {
180         StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
181         buf.append("DELETE FROM AUTO_PK_SUPPORT WHERE TABLE_NAME IN (");
182         int len = dbEntities.size();
183         for (int i = 0; i < len; i++) {
184             if (i > 0) {
185                 buf.append(", ");
186             }
187             DbEntity ent = (DbEntity) dbEntities.get(i);
188             buf.append('\'').append(ent.getName()).append('\'');
189         }
190         buf.append(')');
191         return buf.toString();
192     }
193
194     protected String JavaDoc pkCreateString(String JavaDoc entName) {
195         StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
196         buf
197             .append("INSERT INTO AUTO_PK_SUPPORT")
198             .append(" (TABLE_NAME, NEXT_ID)")
199             .append(" VALUES ('")
200             .append(entName)
201             .append("', 200)");
202         return buf.toString();
203     }
204
205     protected String JavaDoc pkSelectString(String JavaDoc entName) {
206         StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
207         buf
208             .append("SELECT NEXT_ID FROM AUTO_PK_SUPPORT WHERE TABLE_NAME = '")
209             .append(entName)
210             .append('\'');
211         return buf.toString();
212     }
213
214     protected String JavaDoc pkUpdateString(String JavaDoc entName) {
215         StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
216         buf
217             .append("UPDATE AUTO_PK_SUPPORT")
218             .append(" SET NEXT_ID = NEXT_ID + ")
219             .append(pkCacheSize)
220             .append(" WHERE TABLE_NAME = '")
221             .append(entName)
222             .append('\'');
223         return buf.toString();
224     }
225
226     protected String JavaDoc dropAutoPkString() {
227         return "DROP TABLE AUTO_PK_SUPPORT";
228     }
229
230     /**
231      * Checks if AUTO_PK_TABLE already exists in the database.
232      */

233     protected boolean autoPkTableExists(DataNode node) throws SQLException JavaDoc {
234         Connection JavaDoc con = node.getDataSource().getConnection();
235         boolean exists = false;
236         try {
237             DatabaseMetaData JavaDoc md = con.getMetaData();
238             ResultSet JavaDoc tables = md.getTables(null, null, "AUTO_PK_SUPPORT", null);
239             try {
240                 exists = tables.next();
241             } finally {
242                 tables.close();
243             }
244         } finally {
245             // return connection to the pool
246
con.close();
247         }
248
249         return exists;
250     }
251
252     /**
253      * Runs JDBC update over a Connection obtained from DataNode.
254      * Returns a number of objects returned from update.
255      *
256      * @throws SQLException in case of query failure.
257      */

258     public int runUpdate(DataNode node, String JavaDoc sql) throws SQLException JavaDoc {
259         QueryLogger.logQuery(QueryLogger.DEFAULT_LOG_LEVEL, sql, Collections.EMPTY_LIST);
260
261         Connection JavaDoc con = node.getDataSource().getConnection();
262         try {
263             Statement JavaDoc upd = con.createStatement();
264             try {
265                 return upd.executeUpdate(sql);
266             } finally {
267                 upd.close();
268             }
269         } finally {
270             con.close();
271         }
272     }
273
274     /**
275      * @deprecated Since 1.2 corresponding interface method is deprecated.
276      */

277     public String JavaDoc generatePkForDbEntityString(DbEntity ent) {
278         StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
279         buf.append(pkSelectString(ent.getName())).append('\n').append(
280             pkUpdateString(ent.getName()));
281         return buf.toString();
282     }
283
284     /**
285      * <p>Generates new (unique and non-repeating) primary key for specified
286      * dbEntity.</p>
287      *
288      * <p>This implementation is naive since it does not lock the database rows
289      * when executing select and subsequent update. Adapter-specific implementations
290      * are more robust.</p>
291      */

292
293     public Object JavaDoc generatePkForDbEntity(DataNode node, DbEntity ent) throws Exception JavaDoc {
294
295         // check for binary pk
296
Object JavaDoc binPK = binaryPK(ent);
297         if (binPK != null) {
298             return binPK;
299         }
300
301         DbKeyGenerator pkGenerator = ent.getPrimaryKeyGenerator();
302         int cacheSize;
303         if (pkGenerator != null && pkGenerator.getKeyCacheSize() != null)
304             cacheSize = pkGenerator.getKeyCacheSize().intValue();
305         else
306             cacheSize = pkCacheSize;
307
308         // if no caching, always generate fresh
309
if (cacheSize <= 1) {
310             return new Integer JavaDoc(pkFromDatabase(node, ent));
311         }
312
313         synchronized (pkCache) {
314             PkRange r = (PkRange) pkCache.get(ent.getName());
315
316             if (r == null) {
317                 // created exhaused PkRange
318
r = new PkRange(1, 0);
319                 pkCache.put(ent.getName(), r);
320             }
321
322             if (r.isExhausted()) {
323                 int val = pkFromDatabase(node, ent);
324                 r.reset(val, val + cacheSize - 1);
325             }
326
327             return r.getNextPrimaryKey();
328         }
329     }
330
331     /**
332      * @return a binary PK if DbEntity has a BINARY or VARBINARY pk, null otherwise.
333      * This method will likely be deprecated in 1.1 in favor of a more generic soultion.
334      * @since 1.0.2
335      */

336     protected byte[] binaryPK(DbEntity entity) {
337         List JavaDoc pkColumns = entity.getPrimaryKey();
338         if (pkColumns.size() == 1) {
339             DbAttribute pk = (DbAttribute) pkColumns.get(0);
340             if (pk.getMaxLength() > 0
341                 && (pk.getType() == Types.BINARY || pk.getType() == Types.VARBINARY)) {
342                 return IDUtil.pseudoUniqueByteSequence(pk.getMaxLength());
343             }
344         }
345
346         return null;
347     }
348
349     /**
350      * Performs primary key generation ignoring cache. Generates
351      * a range of primary keys as specified by
352      * "pkCacheSize" bean property.
353      *
354      * <p>This method is called internally from "generatePkForDbEntity"
355      * and then generated range of key values is saved in cache for
356      * performance. Subclasses that implement different primary key
357      * generation solutions should override this method,
358      * not "generatePkForDbEntity".</p>
359      */

360     protected int pkFromDatabase(DataNode node, DbEntity ent) throws Exception JavaDoc {
361         String JavaDoc select = "SELECT #result('NEXT_ID' 'int' 'NEXT_ID') "
362                 + "FROM AUTO_PK_SUPPORT "
363                 + "WHERE TABLE_NAME = '"
364                 + ent.getName()
365                 + '\'';
366
367         // run queries via DataNode to utilize its transactional behavior
368
List JavaDoc queries = new ArrayList JavaDoc(2);
369         queries.add(new SQLTemplate(ent, select, true));
370         queries.add(new SQLTemplate(ent, pkUpdateString(ent.getName()), false));
371
372         PkRetrieveProcessor observer = new PkRetrieveProcessor(ent.getName());
373         node.performQueries(queries, observer);
374         return observer.getNextId();
375     }
376
377     /**
378      * Returns a size of the entity primary key cache.
379      * Default value is 20. If cache size is set to a value
380      * less or equals than "one", no primary key caching is done.
381      */

382     public int getPkCacheSize() {
383         return pkCacheSize;
384     }
385
386     /**
387      * Sets the size of the entity primary key cache.
388      * If <code>pkCacheSize</code> parameter is less than 1,
389      * cache size is set to "one".
390      *
391      * <p><i>Note that our tests show that setting primary key
392      * cache value to anything much bigger than 20 does not give
393      * any significant performance increase. Therefore it does
394      * not make sense to use bigger values, since this may
395      * potentially create big gaps in the database primary
396      * key sequences in cases like application crashes or restarts.
397      * </i></p>
398      */

399     public void setPkCacheSize(int pkCacheSize) {
400         this.pkCacheSize = (pkCacheSize < 1) ? 1 : pkCacheSize;
401     }
402
403     public void reset() {
404         pkCache.clear();
405     }
406
407     /** OperationObserver for primary key retrieval. */
408     protected class PkRetrieveProcessor extends DefaultOperationObserver {
409         protected boolean success;
410         protected Integer JavaDoc nextId;
411         protected String JavaDoc entName;
412
413         public PkRetrieveProcessor(String JavaDoc entName) {
414             this.entName = entName;
415         }
416
417         public int getNextId() {
418             if (nextId != null) {
419                 return nextId.intValue();
420             } else {
421                 throw new CayenneRuntimeException("No key was retrieved.");
422             }
423         }
424
425         public void nextDataRows(Query query, List JavaDoc dataRows) {
426             super.nextDataRows(query, dataRows);
427
428             // process selected object, issue an update query
429
if (dataRows == null || dataRows.size() == 0) {
430                 throw new CayenneRuntimeException(
431                     "Error generating PK : entity not supported: " + entName);
432             }
433             if (dataRows.size() > 1) {
434                 throw new CayenneRuntimeException(
435                     "Error generating PK : too many rows for entity: " + entName);
436             }
437
438             Map JavaDoc lastPk = (Map JavaDoc) dataRows.get(0);
439             nextId = (Integer JavaDoc) lastPk.get("NEXT_ID");
440             if (nextId == null) {
441                 throw new CayenneRuntimeException("Error generating PK : null nextId.");
442             }
443         }
444
445         public void nextCount(Query query, int resultCount) {
446             super.nextCount(query, resultCount);
447
448             if (resultCount != 1)
449                 throw new CayenneRuntimeException(
450                     "Error generating PK : update count is wrong: " + resultCount);
451         }
452
453         public void nextQueryException(Query query, Exception JavaDoc ex) {
454             super.nextQueryException(query, ex);
455             String JavaDoc entityName =
456                 ((query != null) && (query.getRoot() != null))
457                     ? query.getRoot().toString()
458                     : null;
459             throw new CayenneRuntimeException(
460                 "Error generating PK for entity '" + entityName + "'.",
461                 ex);
462         }
463
464         public void nextGlobalException(Exception JavaDoc ex) {
465             super.nextGlobalException(ex);
466             throw new CayenneRuntimeException("Error generating PK.", ex);
467         }
468     }
469 }
Popular Tags