001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. 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: package org.apache.jetspeed.serializer;
018:
019: import java.io.StringReader;
020: import java.io.StringWriter;
021: import java.util.Iterator;
022: import java.util.List;
023: import java.util.Map;
024:
025: import javax.sql.DataSource;
026:
027: import org.apache.commons.beanutils.BeanUtils;
028: import org.apache.commons.beanutils.DynaBean;
029: import org.apache.commons.beanutils.DynaProperty;
030: import org.apache.commons.dbcp.BasicDataSource;
031: import org.apache.commons.logging.Log;
032: import org.apache.commons.logging.LogFactory;
033: import org.apache.ddlutils.DatabaseOperationException;
034: import org.apache.ddlutils.Platform;
035: import org.apache.ddlutils.PlatformFactory;
036: import org.apache.ddlutils.PlatformUtils;
037: import org.apache.ddlutils.io.DataReader;
038: import org.apache.ddlutils.io.DataToDatabaseSink;
039: import org.apache.ddlutils.io.DatabaseIO;
040: import org.apache.ddlutils.model.Column;
041: import org.apache.ddlutils.model.Database;
042: import org.apache.ddlutils.model.JdbcTypeCategoryEnum;
043: import org.apache.ddlutils.model.Table;
044:
045: /**
046: * Jetspeed DDLUtil
047: * <p>
048: * The Jetspeed DDL Utility is capabale of extracting existing schema
049: * information as well as recreating databases.
050: *
051: * @author <a href="mailto:hajo@bluesunrise.com">Hajo Birthelmer</a>
052: * @version $Id: $
053: */
054: public class JetspeedDDLUtil {
055: public static final String DATASOURCE_DATABASENAME = "DATABASENAME"
056: .intern();
057: public static final String DATASOURCE_CLASS = "DATASOURCE_CLASS"
058: .intern();
059: public static final String DATASOURCE_DRIVER = "driverClassName"
060: .intern();
061: public static final String DATASOURCE_URL = "url".intern();
062: public static final String DATASOURCE_USERNAME = "username"
063: .intern();
064: public static final String DATASOURCE_PASSWORD = "password"
065: .intern();
066:
067: /** Logger */
068: private static final Log log = LogFactory
069: .getLog(JetspeedDDLUtil.class);
070:
071: JdbcTypeCategoryEnum temEnum = null;
072:
073: Map parameters;
074:
075: PlatformUtils utils;
076:
077: StringWriter writer;
078:
079: private Platform platform;
080:
081: /** The data source to test against. */
082: private DataSource dataSource;
083: /** The database name. */
084: private String _databaseName;
085: /** The database model. */
086: private Database model;
087:
088: private boolean connected = false;
089:
090: public JetspeedDDLUtil() {
091:
092: }
093:
094: public void startUp() {
095:
096: }
097:
098: public void tearDown() {
099: if (connected) {
100: platform = null;
101: // todo: closeup
102: }
103: }
104:
105: /**
106: * Tries to determine whether a the jdbc driver and connection url re
107: * supported.
108: *
109: * @param driverName
110: * The fully qualified name of the JDBC driver
111: * @param jdbcConnectionUrl
112: * The connection url
113: * @return True if this driver/url is supported
114: */
115: public boolean isDatabaseSupported(String driverName,
116: String jdbcConnectionUrl) {
117: if (utils.determineDatabaseType(driverName, jdbcConnectionUrl) != null)
118: return true;
119: else
120: return false;
121:
122: }
123:
124: /**
125: * Parses the database defined in the given XML file and creates a database
126: * schema (model) object
127: *
128: * @param fileName
129: */
130: public void writeDatabaseSchematoFile(String fileName) {
131: new DatabaseIO().write(model, fileName);
132: }
133:
134: /**
135: * Parses the database defined in the given XML file and creates a database
136: * schema (model) object
137: *
138: * @param dbDef
139: * The database XML definition
140: * @return The database model
141: */
142: protected Database createDatabaseSchemaFromXML(String fileName) {
143: DatabaseIO io = new DatabaseIO();
144: io.setValidateXml(false);
145: return io.read(fileName);
146: }
147:
148: /**
149: * Parses the database defined in the given XML definition String and
150: * creates a database schema (model) object
151: *
152: * @param dbDef
153: * The database XML definition
154: * @return The database model
155: */
156: protected Database createDatabaseSchemaFromString(String dbDef) {
157: DatabaseIO dbIO = new DatabaseIO();
158:
159: dbIO.setUseInternalDtd(true);
160: dbIO.setValidateXml(false);
161: return dbIO.read(new StringReader(dbDef));
162: }
163:
164: /**
165: * <p>
166: * Create a database connection (platform instance) from a data source
167: * </p>
168: *
169: * @param dataSource
170: */
171: protected Platform connectToDatabase(DataSource dataSource) {
172: return PlatformFactory.createNewPlatformInstance(dataSource);
173: }
174:
175: /**
176: * <p>
177: * Create a database connection (platform instance) from a (case
178: * insensitive) database type (like MySQL)
179: * </p>
180: *
181: * @param dataSource
182: */
183: protected Platform connectToDatabase(String databaseType) {
184: return PlatformFactory.createNewPlatformInstance(databaseType);
185: }
186:
187: /**
188: * <p>
189: * Update a given database schema to match the schema of targetModel If
190: * alterDB is true, the routine attempts to modify the existing database
191: * shcema while preserving the data (as much as possible). If not, the
192: * existing tables are dropped prior to recreate
193: *
194: * @param targetModel
195: * The new database model
196: * @param alterDb
197: * if true, try to use alter database and preserve data
198: */
199: protected void updateDatabaseSchema(Database targetModel,
200: boolean alterDb) throws SerializerException {
201: try {
202: platform.setSqlCommentsOn(false);
203: try {
204: targetModel.resetDynaClassCache();
205: } catch (Exception internalEx) {
206: internalEx.printStackTrace();
207: }
208: if (alterDb) {
209: model.mergeWith(targetModel);
210: try {
211: platform.alterTables(model, true);
212: } catch (Exception aEX) {
213: System.out.println("Error in ALTER DATABASE");
214: aEX.printStackTrace();
215: log.error(aEX);
216: }
217: } else {
218: try {
219:
220: // if (log.isDebugEnabled())
221: // {
222: // String s = platform.getDropTablesSql(model, true);
223: // log.debug(s);
224: // }
225: if (model == null) {
226: model = targetModel;
227: }
228: platform.dropTables(model, true);
229: } catch (Exception aEX) {
230: log.error(aEX);
231: }
232: try {
233: platform.createTables(model, false, true);
234: if (this ._databaseName.startsWith("oracle")) {
235: model = this .readModelFromDatabase(null);
236: modifyVarBinaryColumn(model,
237: "PA_METADATA_FIELDS", "COLUMN_VALUE");
238: modifyVarBinaryColumn(model,
239: "PD_METADATA_FIELDS", "COLUMN_VALUE");
240: modifyVarBinaryColumn(model, "LANGUAGE",
241: "KEYWORDS");
242: modifyVarBinaryColumn(model,
243: "PORTLET_CONTENT_TYPE", "MODES");
244: modifyVarBinaryColumn(model, "PARAMETER",
245: "PARAMETER_VALUE");
246: modifyVarBinaryColumn(model,
247: "LOCALIZED_DESCRIPTION", "DESCRIPTION");
248: modifyVarBinaryColumn(model,
249: "LOCALIZED_DISPLAY_NAME",
250: "DISPLAY_NAME");
251: modifyVarBinaryColumn(model,
252: "CUSTOM_PORTLET_MODE", "DESCRIPTION");
253: modifyVarBinaryColumn(model,
254: "CUSTOM_WINDOW_STATE", "DESCRIPTION");
255: modifyVarBinaryColumn(model, "MEDIA_TYPE",
256: "DESCRIPTION");
257: platform.alterTables(model, true);
258: }
259: } catch (Exception aEX) {
260: aEX.printStackTrace();
261: log.error(aEX);
262: }
263: }
264: // TODO: DST: REMOVE, AINT WORKING IN ORACLE model = this.readModelFromDatabase(null);
265: } catch (Exception ex) {
266: ex.printStackTrace();
267: throw new SerializerException(
268: // TODO: HJB create exception
269: SerializerException.CREATE_OBJECT_FAILED.create(ex
270: .getLocalizedMessage()));
271: }
272: }
273:
274: private void modifyVarBinaryColumn(Database targetModel,
275: String tableName, String columnName) {
276: Table table = targetModel.findTable(tableName);
277: Column c = table.findColumn(columnName);
278: c.setType("VARCHAR");
279: c.setSize("2000");
280: System.out.println("updating column " + c.getName()
281: + " for table " + table.getName());
282: }
283:
284: /**
285: * Alter an existing database from the given model. Data is preserved as
286: * much as possible
287: *
288: * @param model
289: * The new database model
290: */
291: public void alterDatabase(Database model)
292: throws SerializerException {
293: updateDatabaseSchema(model, true);
294: }
295:
296: /**
297: * Creates a new database from the given model. Note that all data is LOST
298: *
299: * @param model
300: * The new database model
301: */
302: public void createDatabase(Database model)
303: throws SerializerException {
304: updateDatabaseSchema(model, false);
305: }
306:
307: /**
308: * <p>
309: * Inserts data into the database. Data is expected to be in the format
310: * </p>
311: * <p>
312: * <?xml version='1.0' encoding='ISO-8859-1'?> <data> <TABLENAME
313: * FIELD1='TEXTVALUE' FIELD2=INTVALUE .... /> <TABLENAME FIELD1='TEXTVALUE'
314: * FIELD2=INTVALUE .... /> </data>
315: * </p>
316: *
317: * @param model
318: * The database model
319: * @param dataXml
320: * The data xml
321: * @return The database
322: */
323: protected Database insertData(Database model, String dataXml)
324: throws DatabaseOperationException {
325: try {
326: DataReader dataReader = new DataReader();
327:
328: dataReader.setModel(model);
329: dataReader.setSink(new DataToDatabaseSink(platform, model));
330: dataReader.parse(new StringReader(dataXml));
331: return model;
332: } catch (Exception ex) {
333: throw new DatabaseOperationException(ex);
334: }
335: }
336:
337: /**
338: * Drops the tables defined in the database model on this connection.
339: *
340: * @param model
341: * The database model
342: *
343: */
344: protected void dropDatabaseTables(Database model)
345: throws DatabaseOperationException {
346: platform.dropTables(model, true);
347: }
348:
349: /**
350: * Reads the database model from a live database.
351: *
352: * @param platform
353: * The physical database connection
354: * @param databaseName
355: * The name of the resulting database
356: * @return The model
357: */
358: public Database readModelFromDatabase(String databaseName) {
359: return platform.readModelFromDatabase(databaseName);
360: }
361:
362: /**
363: * datasource.class=org.apache.commons.dbcp.BasicDataSource
364: * datasource.driverClassName=com.mysql.jdbc.Driver
365: * datasource.url=jdbc:mysql://localhost/ddlutils datasource.username=root
366: * datasource.password=root123
367: *
368: */
369:
370: /**
371: * Initializes the datasource and the connection (platform)
372: */
373: public void init(Map parameters) {
374: if (connected)
375: tearDown();
376:
377: try {
378: String dataSourceClass = (String) parameters
379: .get(DATASOURCE_CLASS);
380: if (dataSourceClass == null)
381: dataSourceClass = BasicDataSource.class.getName();
382:
383: dataSource = (DataSource) Class.forName(dataSourceClass)
384: .newInstance();
385:
386: for (Iterator it = parameters.entrySet().iterator(); it
387: .hasNext();) {
388: Map.Entry entry = (Map.Entry) it.next();
389: String propName = (String) entry.getKey();
390:
391: if (!(propName.equals(DATASOURCE_CLASS))) {
392: BeanUtils.setProperty(dataSource, propName, entry
393: .getValue());
394: }
395: }
396: } catch (Exception ex) {
397: throw new DatabaseOperationException(ex);
398: }
399: String databaseName = null;
400: _databaseName = null;
401: try {
402: databaseName = (String) parameters
403: .get(DATASOURCE_DATABASENAME);
404: if (databaseName != null) {
405: platform = PlatformFactory
406: .createNewPlatformInstance(databaseName);
407: if (platform != null)
408: _databaseName = databaseName;
409: }
410: } catch (Exception ex) {
411: log.warn("Exception in trying to establish connection to "
412: + databaseName + " : " + ex.getLocalizedMessage());
413: log.warn(ex);
414: }
415: if (_databaseName == null) {
416: _databaseName = new PlatformUtils()
417: .determineDatabaseType(dataSource);
418: if (_databaseName == null) {
419: throw new DatabaseOperationException(
420: "Could not determine platform from datasource, please specify it in the jdbc.properties via the ddlutils.platform property");
421: } else {
422: try {
423: platform = PlatformFactory
424: .createNewPlatformInstance(_databaseName);
425: } catch (Exception ex) {
426: throw new DatabaseOperationException(
427: "Could not establish connection to "
428: + _databaseName + " : "
429: + ex.getLocalizedMessage(), ex);
430: }
431: }
432: }
433: // com.mysql.jdbc.Driver
434:
435: writer = new StringWriter();
436: platform.getSqlBuilder().setWriter(writer);
437: // if (platform.getPlatformInfo().isDelimitedIdentifiersSupported())
438: // {
439: // platform.setDelimitedIdentifierModeOn(true);
440: // }
441:
442: platform.setDataSource(dataSource);
443: System.out.println("reading model...");
444: model = this .readModelFromDatabase(null);
445: System.out.println("done reading model...");
446: /**
447: JdbcModelReader reader = platform.getModelReader();
448: try
449: {
450: model = reader.getDatabase(platform.borrowConnection(), null);
451: } catch (Exception ex)
452: {
453: throw new DatabaseOperationException(ex);
454: }
455: */
456:
457: connected = true;
458: }
459:
460: /**
461: * Returns the database model.
462: *
463: * @return The model
464: */
465: protected Database getModel() {
466: return model;
467: }
468:
469: /**
470: * Inserts data into the database.
471: *
472: * @param dataXml
473: * The data xml
474: * @return The database
475: */
476: protected Database insertData(String dataXml)
477: throws DatabaseOperationException {
478: try {
479: DataReader dataReader = new DataReader();
480:
481: dataReader.setModel(model);
482: dataReader.setSink(new DataToDatabaseSink(platform, model));
483: dataReader.parse(new StringReader(dataXml));
484: return model;
485: } catch (Exception ex) {
486: throw new DatabaseOperationException(ex);
487: }
488: }
489:
490: /**
491: * Drops the tables defined in the database model.
492: */
493: protected void dropDatabase() throws DatabaseOperationException {
494: platform.dropTables(model, true);
495: }
496:
497: /**
498: * Determines the value of the bean's property that has the given name.
499: * Depending on the case-setting of the current builder, the case of teh
500: * name is considered or not.
501: *
502: * @param bean
503: * The bean
504: * @param propName
505: * The name of the property
506: * @return The value
507: */
508: protected Object getPropertyValue(DynaBean bean, String propName) {
509: if (platform.isDelimitedIdentifierModeOn()) {
510: return bean.get(propName);
511: } else {
512: DynaProperty[] props = bean.getDynaClass()
513: .getDynaProperties();
514:
515: for (int idx = 0; idx < props.length; idx++) {
516: if (propName.equalsIgnoreCase(props[idx].getName())) {
517: return bean.get(props[idx].getName());
518: }
519: }
520: throw new IllegalArgumentException(
521: "The bean has no property with the name "
522: + propName);
523: }
524: }
525:
526: public DataSource getDataSource() {
527: return dataSource;
528: }
529:
530: public Platform getPlatform() {
531: return platform;
532: }
533:
534: public List getRows(String tableName) {
535: Table table = getModel().findTable(tableName,
536: getPlatform().isDelimitedIdentifierModeOn());
537:
538: return getPlatform().fetch(getModel(),
539: getSelectQueryForAllString(table),
540: new Table[] { table });
541: }
542:
543: public String getSelectQueryForAllString(Table table) {
544:
545: StringBuffer query = new StringBuffer();
546:
547: query.append("SELECT * FROM ");
548: if (getPlatform().isDelimitedIdentifierModeOn()) {
549: query.append(getPlatform().getPlatformInfo()
550: .getDelimiterToken());
551: }
552: query.append(table.getName());
553: if (getPlatform().isDelimitedIdentifierModeOn()) {
554: query.append(getPlatform().getPlatformInfo()
555: .getDelimiterToken());
556: }
557: return query.toString();
558: }
559:
560: }
|