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.db;
066:
067: import com.jcorporate.expresso.core.dataobjects.Securable;
068: import com.jcorporate.expresso.core.dataobjects.jdbc.JDBCObjectMetaData;
069: import com.jcorporate.expresso.core.db.config.JDBCConfig;
070: import com.jcorporate.expresso.core.dbobj.DBIndex;
071: import com.jcorporate.expresso.core.dbobj.DBObject;
072: import com.jcorporate.expresso.core.dbobj.DBObjectDef;
073: import com.jcorporate.expresso.core.dbobj.Schema;
074: import com.jcorporate.expresso.core.misc.ConfigManager;
075: import com.jcorporate.expresso.core.misc.StringUtil;
076: import com.jcorporate.expresso.kernel.management.ExpressoRuntimeMap;
077: import com.jcorporate.expresso.kernel.util.FastStringBuffer;
078: import com.jcorporate.expresso.kernel.util.LocatorUtils;
079: import com.jcorporate.expresso.services.dbobj.DBOtherMap;
080: import com.jcorporate.expresso.services.dbobj.Setup;
081: import org.apache.log4j.Logger;
082:
083: import java.lang.ref.WeakReference;
084: import java.util.Enumeration;
085: import java.util.Iterator;
086: import java.util.Vector;
087:
088: /**
089: * A singleton utility class that helps create tables. It is stored as a weak reference
090: * since it will be seldom used and when done should be discarded.
091: *
092: * @author Michael Rimov
093: * <p/>
094: * Modify by Yves Henri AMAIZO <amy_amaizo@compuserve.com>
095: * @since $DatabaseSchema $Date: 2004/11/18 02:03:27 $
096: */
097:
098: public class TableCreator {
099:
100: /**
101: * The log4j Logger. This one is not static since the whole
102: * reference is a weak reference, this way, the System can gc the logger
103: * object as well.
104: */
105: private Logger log = Logger.getLogger(TableCreator.class);
106:
107: /**
108: * The one and only instance
109: */
110: static WeakReference theInstance = null;
111: /**
112: * setup name for switch about using HSQL option for 'cached' tables (special case for hsql driver)
113: */
114: public static final String USE_CACHED_HSQL = "UseCachedHSQL";
115:
116: protected TableCreator() {
117: }
118:
119: /**
120: * The way to a Table Creator's Heart; or more importantly the way to
121: * get a reference to a TableCreator object. The implementation doesn't
122: * care how hard it is to create a TableCreator instance since it is
123: * seldom used.
124: *
125: * @return a TableCreator instance.
126: */
127: public static synchronized TableCreator getInstance() {
128: synchronized (TableCreator.class) {
129: if (theInstance == null) {
130: TableCreator tc = new TableCreator();
131: theInstance = new WeakReference(tc);
132: return tc;
133: } else {
134: TableCreator returnValue = (TableCreator) theInstance
135: .get();
136: if (returnValue == null) {
137: returnValue = new TableCreator();
138: theInstance = new WeakReference(returnValue);
139: }
140:
141: return returnValue;
142: }
143: }
144: }
145:
146: /**
147: * Create the table needed by this DB Object in the database. Assumes it is
148: * not there already. This method is used by the Schema object when called
149: * from the DBCreate servlet to initialize a database to match the defined
150: * DB objects in a schema.
151: *
152: * @param dbObj The Instantiated Database Object to check against.
153: * @throws DBException If there's an error creating the table with the
154: * database
155: * @todo Refactor this method into smaller chunks... ie creating the table.
156: * Creating
157: * <p/>
158: * Modify by Yves Henri AMAIZO <amy_amaizo@compuserve.com>
159: * @since $DatabaseSchema $Date: 2004/11/18 02:03:27 $
160: */
161: public synchronized void createTable(DBObject dbObj)
162: throws DBException {
163: FastStringBuffer sqlStatement = FastStringBuffer.getInstance();
164: //Since we're only borrowing an instance, let's just go ahead and
165: //create it to make out logic easier.
166: FastStringBuffer pkStatement = FastStringBuffer.getInstance();
167:
168: JDBCObjectMetaData metadata = dbObj.getJDBCMetaData();
169:
170: try {
171:
172: DBConnectionPool myPool = null;
173: DBConnection myConnection = null;
174: boolean addComma = false;
175:
176: try {
177:
178: sqlStatement = createTableSQLDefinition(dbObj,
179: sqlStatement);
180:
181: myPool = DBConnectionPool.getInstance(dbObj
182: .getDataContext());
183: myConnection = myPool.getConnection("Table Creator");
184:
185: String myDBDriver = myConnection.getDBDriver();
186: Iterator e2 = metadata.getKeyFieldListArray()
187: .iterator();
188:
189: // Determine whether primary key fields have been defined.
190: if (e2.hasNext()) {
191:
192: /* If we're using a connection to PostgreSQL, we have a different */
193: /* syntax for primary key */
194: // Note: The driver name for the PostgreSQL jdbc driver has changed */
195: // for version 7.0. We now
196: // check for both driver names.
197: if (myDBDriver.equals("postgresql.Driver")
198: || myDBDriver
199: .equals("org.postgresql.Driver")) {
200: sqlStatement.append(", CONSTRAINT pk");
201: sqlStatement.append(metadata
202: .getTargetSQLTable(dbObj
203: .getDataContext()));
204: sqlStatement.append(" PRIMARY KEY (");
205: addComma = false;
206:
207: while (e2.hasNext()) {
208: String OneKey = (String) e2.next();
209:
210: if (addComma) {
211: sqlStatement.append(", ");
212: }
213:
214: sqlStatement.append(OneKey);
215: addComma = true;
216: }
217:
218: sqlStatement.append(")");
219: } else if ((myDBDriver
220: .equals("org.hsql.jdbcDriver"))
221: || (myDBDriver
222: .equals("org.hsqldb.jdbcDriver"))) { /* if Hypersonic */
223: sqlStatement.append(", PRIMARY KEY (");
224: addComma = false;
225:
226: while (e2.hasNext()) {
227: String OneKey = (String) e2.next();
228:
229: if (addComma) {
230: sqlStatement.append(", ");
231: }
232:
233: sqlStatement.append(OneKey);
234: addComma = true;
235: }
236:
237: sqlStatement.append(")");
238: } else {
239: pkStatement.append("alter table ");
240: pkStatement.append(metadata
241: .getTargetSQLTable(dbObj
242: .getDataContext()));
243: pkStatement.append(" add primary key(");
244: addComma = false;
245:
246: while (e2.hasNext()) {
247: String OneKey = (String) e2.next();
248:
249: if (addComma) {
250: pkStatement.append(", ");
251: }
252:
253: pkStatement.append(OneKey);
254: addComma = true;
255: }
256:
257: pkStatement.append(")");
258: } /* if not PSQL */
259:
260: } /* If primary key fields defined */
261:
262: sqlStatement.append(")");
263:
264: // Send SQL to database
265: if (log.isInfoEnabled()) {
266: log.info("Executing:" + sqlStatement.toString());
267: }
268: myConnection.executeUpdate(sqlStatement.toString());
269:
270: // If separate statement is needed, create primary key
271: String thepkSQL = pkStatement.toString();
272: if (thepkSQL != null && thepkSQL.length() > 0) {
273: if (log.isInfoEnabled()) {
274: log.info("Executing:" + thepkSQL);
275: }
276:
277: myConnection.executeUpdate(thepkSQL);
278: }
279: //
280: //Construct one or more indicies. for the dbObject
281: //
282: createIndices(dbObj, myConnection, true);
283: } catch (Throwable t) {
284: t.printStackTrace();
285: throw new DBException(t);
286: } finally {
287: if (myPool != null) {
288: myPool.release(myConnection);
289: }
290: }
291: } finally {
292: pkStatement.release();
293: sqlStatement.release();
294: }
295:
296: }
297:
298: /**
299: * Create the SQL statement to create the table.
300: *
301: * @param dbObj the dbobject where we get the parameters from.
302: * @param sqlStatement the initialized FastStringBuffer that we're going to
303: * append to. The return value is the same we pass in. Allocation and
304: * deallocation is up to the parent calling this function.
305: * @return FastStringBuffer with the create statements added
306: * @throws DBException upon creation error
307: * <p/>
308: * Modify by Yves Henri AMAIZO <amy_amaizo@compuserve.com>
309: * @since $DatabaseSchema $Date: 2004/11/18 02:03:27 $
310: */
311: protected FastStringBuffer createTableSQLDefinition(DBObject dbObj,
312: FastStringBuffer sqlStatement) throws DBException {
313: boolean addComma = false;
314:
315: //We have to use the mapped data context here because
316: String dataContext = dbObj.getMappedDataContext();
317: TypeMapper typeMapper = TypeMapper.getInstance(dataContext);
318: JDBCConfig myConfig = null;
319: JDBCObjectMetaData metadata = dbObj.getJDBCMetaData();
320:
321: try {
322: myConfig = ConfigManager.getJdbcRequired(dbObj
323: .getDataContext());
324: } catch (com.jcorporate.expresso.core.misc.ConfigurationException ce) {
325: throw new DBException(ce);
326: }
327: sqlStatement.append("CREATE");
328: if ("org.hsqldb.jdbcDriver".equals(myConfig.getDriver())) {
329: // add development option where we want all data NOT in
330: // editable text format, but rather cached for efficiency in a .data table
331: String useCachedHSQL = Setup.getValueUnrequired(dbObj
332: .getDataContext(), USE_CACHED_HSQL);
333: if (StringUtil.toBoolean(useCachedHSQL)) {
334: sqlStatement.append(" CACHED ");
335: }
336: }
337: sqlStatement.append(" TABLE ");
338: sqlStatement.append(metadata.getTargetSQLTable(dbObj
339: .getDataContext()));
340: sqlStatement.append("(");
341:
342: for (Iterator lf = metadata.getFieldListArray().iterator(); lf
343: .hasNext();) {
344: String fieldName = (String) lf.next();
345:
346: if (!dbObj.getJDBCMetaData().isVirtual(fieldName)) {
347: FastStringBuffer oneType = FastStringBuffer
348: .getInstance();
349: try {
350: if (addComma) {
351: sqlStatement.append(", ");
352: }
353:
354: oneType.append(typeMapper.getTypeForDB(metadata
355: .getType(fieldName)));
356:
357: if (!metadata.getLength(fieldName).equals("0")) {
358: oneType
359: .append("("
360: + dbObj.getLength(fieldName));
361:
362: if (metadata.getPrecision(fieldName) > 0) {
363: oneType.append(", "
364: + metadata.getPrecision(fieldName));
365: }
366:
367: oneType.append(")");
368: }
369:
370: sqlStatement.append(fieldName);
371: sqlStatement.append(" ");
372: sqlStatement.append(oneType.toString());
373:
374: if (!dbObj.getFieldMetaData(fieldName).allowsNull()) {
375: sqlStatement.append(" not null");
376: } else {
377: if (myConfig.useNullOnCreate()) {
378: sqlStatement.append(" null"); //Added for compatability w/Sybase...sql-92 standard?
379: }
380: }
381:
382: addComma = true;
383: } finally {
384: oneType.release();
385: }
386: } /* if field is not virtual */
387: } /* for each field */
388: return sqlStatement;
389: }
390:
391: /**
392: * Try to do a search on each DBObject member - if we fail
393: * we know the table does not exist. Create it
394: *
395: * @param oneSchema The schema to create tables for.
396: * @param dataContext The database context to create the tables.
397: * @return A Vector containing the created dbObjects
398: * <p/>
399: * Modify by Yves Henri AMAIZO <amy_amaizo@compuserve.com>
400: * @throws DBException If the create process fails
401: * @since $DatabaseSchema $Date: 2004/11/18 02:03:27 $
402: */
403: public synchronized Vector createAsNeeded(Schema oneSchema,
404: String dataContext) throws DBException {
405: Vector newObjs = new Vector(1);
406: DBObject oneMember = null;
407: String oneOtherDbName = null;
408:
409: JDBCObjectMetaData metadata;
410: for (Enumeration e = oneSchema.getMembers(); e
411: .hasMoreElements();) {
412: oneMember = (DBObject) e.nextElement();
413: if (oneMember instanceof Securable) {
414: ((Securable) oneMember)
415: .setRequestingUid(Securable.SYSTEM_ACCOUNT);
416: }
417: metadata = oneMember.getJDBCMetaData();
418:
419: // Changed by Adam to respect hardcoded
420: // DBName values. Now, each dbobject is inspected. If there is a
421: // dbName, it is used. If not, the default dbName is used.
422: oneOtherDbName = (String) oneSchema.getDBObjMap().get(
423: oneMember.getClass().getName());
424:
425: //
426: //While under most situations we wouldn't set the dbname to the mapped
427: //context, here we do it because there is most likely no mapping
428: //table set up.
429: //
430: if (oneOtherDbName != null) {
431: oneMember.setDataContext(oneOtherDbName);
432: } else {
433: oneMember.setDataContext(dataContext);
434: }
435:
436: if (log.isInfoEnabled()) {
437: log.info("Verifying table "
438: + metadata.getTargetSQLTable(oneMember
439: .getDataContext()) + " exists");
440: }
441:
442: try {
443: oneMember.count();
444:
445: // check indices on established objects
446: createIndices(oneMember, null, false);
447:
448: if (log.isInfoEnabled()) {
449: log.info("Table "
450: + metadata.getTargetSQLTable(oneMember
451: .getDataContext()) + " is OK");
452: }
453: } catch (DBException de) {
454: log.debug(de);
455: newObjs.addElement(metadata.getName());
456: if (log.isInfoEnabled()) {
457: log.info("Table for object " + metadata.getName()
458: + " did not exist in db '"
459: + oneMember.getDataContext()
460: + "' - creating");
461: }
462: createTable(oneMember);
463:
464: /* Now re-verify */
465: try {
466: oneMember.count();
467: } catch (DBException de2) {
468: throw new DBException("Table '"
469: + metadata.getTargetSQLTable(oneMember
470: .getDataContext())
471: + "' was not created successfully in db '"
472: + oneMember.getDataContext() + "'", de2);
473: }
474: }
475: //If this object resides in another database, write an entry to
476: //the DBOtherMap table
477: if (oneOtherDbName != null) {
478: try {
479: DBOtherMap otherdb = new DBOtherMap();
480: otherdb.setDataContext(dataContext);
481: otherdb.setField("DBObjName", oneMember.getClass()
482: .getName());
483:
484: if (!otherdb.find()) {
485: otherdb.setField("DBConnName", oneOtherDbName);
486: otherdb.setField("Descrip", metadata
487: .getDescription());
488: otherdb.add();
489: }
490: } catch (DBException dbo) {
491: log.error(dbo);
492: log
493: .error("Could not create an entry in the DBOtherMap "
494: + "table for object ' "
495: + oneMember.getClass().getName()
496: + "'");
497: }
498: }
499: }
500:
501: return newObjs;
502: }
503:
504: /**
505: * Protected method to get JDBC Configurations whether running Expresso Runtime
506: * or not.
507: *
508: * @param dataContext the Data Context to get the configuration for.
509: * @return JDBCConfig for the specified data context.
510: * @throws DBException if there is an error retrieving the JDBC configuration data.
511: */
512: protected JDBCConfig getJDBCConfig(String dataContext)
513: throws DBException {
514: if (ExpressoRuntimeMap.getDefaultRuntime() == null) {
515: try {
516: return ConfigManager.getContext(dataContext).getJdbc();
517: } catch (com.jcorporate.expresso.core.misc.ConfigurationException ex) {
518: log.error("Error getting configuration for context: "
519: + dataContext);
520: throw new DBException(
521: "Error getting configuration for context", ex);
522: }
523: } else {
524: LocatorUtils lc = new LocatorUtils(ExpressoRuntimeMap
525: .getDefaultRuntime());
526: DBConfig config = (DBConfig) lc.locateComponent(dataContext
527: + ".PersistenceManager.DBConfig");
528: if (config == null) {
529: throw new DBException(
530: "Unable to find DB Config in the default runtime.");
531: }
532:
533: return config.getCurrentConfig();
534: }
535: }
536:
537: /**
538: * create indices, using provided connection if any, and complaining upon failure if specified
539: *
540: * @param dbObj the object whose indices will be created
541: * @param providedConn any db connection we should use. can be null, in which case we get/dispose of one internally
542: * @param shouldComplain whether we should log a warning if index cannot be created. use 'false'
543: * if you are checking established dbobjects, and should not complain if all of the indices already exist
544: * @throws DBException upon error
545: */
546: protected void createIndices(DBObject dbObj,
547: DBConnection providedConn, boolean shouldComplain)
548: throws DBException {
549:
550: if (dbObj.getJDBCMetaData().hasIndex()) {
551: JDBCConfig myConfig = null;
552:
553: try {
554: myConfig = ConfigManager.getJdbcRequired(dbObj
555: .getDataContext());
556: } catch (com.jcorporate.expresso.core.misc.ConfigurationException ce) {
557: throw new DBException(ce);
558: }
559: if (myConfig.createTableIndicies()) {
560: Object[] indexArray = ((DBObjectDef) dbObj
561: .getMetaData()).getIndexArray();
562:
563: if (indexArray != null) {
564:
565: DBConnection usedConn = providedConn;
566: DBConnection localConn = null;
567: DBConnectionPool myPool = null;
568: try {
569: if (usedConn == null) {
570: // must provide local connection
571: myPool = DBConnectionPool.getInstance(dbObj
572: .getDataContext());
573: localConn = myPool
574: .getConnection("Table Creator");
575: usedConn = localConn;
576: }
577: for (int i = 0; i < indexArray.length; i++) {
578: String createIndexString = ((DBIndex) indexArray[i])
579: .constructSQL();
580:
581: if (log.isDebugEnabled() == true) {
582: log.debug("Executing "
583: + createIndexString);
584: }
585:
586: try {
587: usedConn
588: .executeUpdate(createIndexString);
589: } catch (DBException ex) {
590:
591: // we only complain if we expect success always.
592: // for example, if we are just checking that all indices should exist already,
593: // we will get errors trying to recreate them,
594: // and we should not complain
595: if (shouldComplain) {
596: log
597: .warn(
598: "Unable to create index. ",
599: ex);
600: }
601: }
602: }
603: } finally {
604: if (localConn != null) {
605: myPool.release(localConn);
606: }
607: }
608: }
609: }
610: }
611:
612: }
613:
614: }
|