View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.jetspeed.serializer;
18  
19  import java.io.StringReader;
20  import java.io.StringWriter;
21  import java.util.Iterator;
22  import java.util.List;
23  import java.util.Map;
24  
25  import javax.sql.DataSource;
26  
27  import org.apache.commons.beanutils.BeanUtils;
28  import org.apache.commons.beanutils.DynaBean;
29  import org.apache.commons.beanutils.DynaProperty;
30  import org.apache.commons.dbcp.BasicDataSource;
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  import org.apache.ddlutils.DatabaseOperationException;
34  import org.apache.ddlutils.Platform;
35  import org.apache.ddlutils.PlatformFactory;
36  import org.apache.ddlutils.PlatformUtils;
37  import org.apache.ddlutils.io.DataReader;
38  import org.apache.ddlutils.io.DataToDatabaseSink;
39  import org.apache.ddlutils.io.DatabaseIO;
40  import org.apache.ddlutils.model.Column;
41  import org.apache.ddlutils.model.Database;
42  import org.apache.ddlutils.model.JdbcTypeCategoryEnum;
43  import org.apache.ddlutils.model.Table;
44  
45  /***
46   * Jetspeed DDLUtil
47   * <p>
48   * The Jetspeed DDL Utility is capabale of extracting existing schema
49   * information as well as recreating databases.
50   * 
51   * @author <a href="mailto:hajo@bluesunrise.com">Hajo Birthelmer</a>
52   * @version $Id: $
53   */
54  public class JetspeedDDLUtil
55  {
56  	public static final String DATASOURCE_DATABASENAME = "DATABASENAME".intern();
57  	public static final String DATASOURCE_CLASS = "DATASOURCE_CLASS".intern();
58  	public static final String DATASOURCE_DRIVER = "driverClassName".intern();
59  	public static final String DATASOURCE_URL = "url".intern();
60  	public static final String DATASOURCE_USERNAME = "username".intern();
61  	public static final String DATASOURCE_PASSWORD = "password".intern();
62  
63  	
64      /*** Logger */
65      private static final Log log = LogFactory.getLog(JetspeedDDLUtil.class);
66  
67  	JdbcTypeCategoryEnum temEnum = null;
68  	
69  	Map parameters;
70  
71  	PlatformUtils utils;
72  
73  	StringWriter writer;
74  
75  	private Platform platform;
76  
77  	/*** The data source to test against. */
78  	private DataSource dataSource;
79  	/*** The database name. */
80  	private String _databaseName;
81  	/*** The database model. */
82  	private Database model;
83  
84  	private boolean connected = false;
85  
86  	public JetspeedDDLUtil()
87  	{
88  
89  	}
90  
91  	public void startUp()
92  	{
93  
94  	}
95  
96  	public void tearDown()
97  	{
98  		if (connected)
99  		{
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 	{
118 		if (utils.determineDatabaseType(driverName, jdbcConnectionUrl) != null)
119 			return true;
120 		else
121 			return false;
122 
123 	}
124 
125 	
126 
127 	/***
128 	 * Parses the database defined in the given XML file and creates a database
129 	 * schema (model) object
130 	 * 
131 	 * @param fileName 
132 	 */
133 	public void writeDatabaseSchematoFile(String fileName)
134 	{
135 		new DatabaseIO().write(model, fileName);
136 	}
137 
138 	
139 	/***
140 	 * Parses the database defined in the given XML file and creates a database
141 	 * schema (model) object
142 	 * 
143 	 * @param dbDef
144 	 *            The database XML definition
145 	 * @return The database model
146 	 */
147 	protected Database createDatabaseSchemaFromXML(String fileName)
148 	{
149 		DatabaseIO io = new DatabaseIO();
150 		io.setValidateXml(false);
151 		return io.read(fileName);
152 	}
153 
154 	/***
155 	 * Parses the database defined in the given XML definition String and
156 	 * creates a database schema (model) object
157 	 * 
158 	 * @param dbDef
159 	 *            The database XML definition
160 	 * @return The database model
161 	 */
162 	protected Database createDatabaseSchemaFromString(String dbDef)
163 	{
164 		DatabaseIO dbIO = new DatabaseIO();
165 
166 		dbIO.setUseInternalDtd(true);
167 		dbIO.setValidateXml(false);
168 		return dbIO.read(new StringReader(dbDef));
169 	}
170 
171 	/***
172 	 * <p>
173 	 * Create a database connection (platform instance) from a data source
174 	 * </p>
175 	 * 
176 	 * @param dataSource
177 	 */
178 	protected Platform connectToDatabase(DataSource dataSource)
179 	{
180 		return PlatformFactory.createNewPlatformInstance(dataSource);
181 	}
182 
183 	/***
184 	 * <p>
185 	 * Create a database connection (platform instance) from a (case
186 	 * insensitive) database type (like MySQL)
187 	 * </p>
188 	 * 
189 	 * @param dataSource
190 	 */
191 	protected Platform connectToDatabase(String databaseType)
192 	{
193 		return PlatformFactory.createNewPlatformInstance(databaseType);
194 	}
195 	/***
196 	 * <p>
197 	 * Update a given database schema to match the schema of targetModel If
198 	 * alterDB is true, the routine attempts to modify the existing database
199 	 * shcema while preserving the data (as much as possible). If not, the
200 	 * existing tables are dropped prior to recreate
201 	 * 
202 	 * @param targetModel
203 	 *            The new database model
204 	 * @param alterDb
205 	 *            if true, try to use alter database and preserve data
206 	 */
207 	protected void updateDatabaseSchema(Database targetModel, boolean alterDb)
208 			throws SerializerException
209 	{
210 		try
211 		{
212 			platform.setSqlCommentsOn(false);
213 			try
214 			{
215 				targetModel.resetDynaClassCache();
216 			} catch (Exception internalEx)
217 			{
218 				internalEx.printStackTrace();
219 			}
220 			if (alterDb)
221 			{
222 				model.mergeWith(targetModel);
223 				try
224 				{
225 					platform.alterTables(model, true);
226 				}
227 				catch (Exception aEX)
228 				{
229 					System.out.println("Error in ALTER DATABASE");
230 					aEX.printStackTrace();
231 					log.error(aEX);
232 				}
233 			} else
234 			{
235 				try
236 				{
237 				
238 //					if (log.isDebugEnabled())
239 //					{
240 //						String s = platform.getDropTablesSql(model, true);
241 //						log.debug(s);
242 //					}
243                     if (model == null)
244                     {
245                         model = targetModel;
246                     }
247                     platform.dropTables(model, true);				
248 				}
249 				catch (Exception aEX)
250 				{
251 					log.error(aEX);
252 				}
253 				try
254 				{
255 				    platform.createTables(model, false, true);
256                     if (this._databaseName.startsWith("oracle"))
257                     {
258                         model = this.readModelFromDatabase(null);
259                         modifyVarBinaryColumn(model, "PA_METADATA_FIELDS", "COLUMN_VALUE");
260                         modifyVarBinaryColumn(model, "PD_METADATA_FIELDS", "COLUMN_VALUE");
261                         modifyVarBinaryColumn(model, "LANGUAGE", "KEYWORDS");
262                         modifyVarBinaryColumn(model, "PORTLET_CONTENT_TYPE", "MODES");
263                         modifyVarBinaryColumn(model, "PARAMETER", "PARAMETER_VALUE");
264                         modifyVarBinaryColumn(model, "LOCALIZED_DESCRIPTION", "DESCRIPTION");
265                         modifyVarBinaryColumn(model, "LOCALIZED_DISPLAY_NAME", "DISPLAY_NAME");
266                         modifyVarBinaryColumn(model, "CUSTOM_PORTLET_MODE", "DESCRIPTION");
267                         modifyVarBinaryColumn(model, "CUSTOM_WINDOW_STATE", "DESCRIPTION");
268                         modifyVarBinaryColumn(model, "MEDIA_TYPE", "DESCRIPTION");
269                         platform.alterTables(model, true);                        
270                     }
271 				}
272 				catch (Exception aEX)
273 				{
274                     aEX.printStackTrace();
275 					log.error(aEX);
276 				}
277 			}
278 			// TODO: DST: REMOVE, AINT WORKING IN ORACLE model = this.readModelFromDatabase(null);
279 		} catch (Exception ex)
280 		{
281 			ex.printStackTrace();
282 			throw new SerializerException(
283 			// TODO: HJB create exception
284 					SerializerException.CREATE_OBJECT_FAILED.create(ex.getLocalizedMessage()));
285 		}
286 	}
287 
288     private void modifyVarBinaryColumn(Database targetModel, String tableName, String columnName)
289     {
290         Table table = targetModel.findTable(tableName);
291         Column c = table.findColumn(columnName);
292         c.setType("VARCHAR");        
293         c.setSize("2000");
294         System.out.println("updating column " + c.getName() + " for table " + table.getName());
295     }
296 	/***
297 	 * Alter an existing database from the given model. Data is preserved as
298 	 * much as possible
299 	 * 
300 	 * @param model
301 	 *            The new database model
302 	 */
303 	public void alterDatabase(Database model) throws SerializerException
304 	{
305 		updateDatabaseSchema(model, true);
306 	}
307 
308 	/***
309 	 * Creates a new database from the given model. Note that all data is LOST
310 	 * 
311 	 * @param model
312 	 *            The new database model
313 	 */
314 	public void createDatabase(Database model) throws SerializerException
315 	{
316 		updateDatabaseSchema(model, false);
317 	}
318 
319 	/***
320 	 * <p>
321 	 * Inserts data into the database. Data is expected to be in the format
322 	 * </p>
323 	 * <p>
324 	 * <?xml version='1.0' encoding='ISO-8859-1'?> <data> <TABLENAME
325 	 * FIELD1='TEXTVALUE' FIELD2=INTVALUE .... /> <TABLENAME FIELD1='TEXTVALUE'
326 	 * FIELD2=INTVALUE .... /> </data>
327 	 * </p>
328 	 * 
329 	 * @param model
330 	 *            The database model
331 	 * @param dataXml
332 	 *            The data xml
333 	 * @return The database
334 	 */
335 	protected Database insertData(Database model, String dataXml)
336 			throws DatabaseOperationException
337 	{
338 		try
339 		{
340 			DataReader dataReader = new DataReader();
341 
342 			dataReader.setModel(model);
343 			dataReader.setSink(new DataToDatabaseSink(platform, model));
344 			dataReader.parse(new StringReader(dataXml));
345 			return model;
346 		} catch (Exception ex)
347 		{
348 			throw new DatabaseOperationException(ex);
349 		}
350 	}
351 
352 	/***
353 	 * Drops the tables defined in the database model on this connection.
354 	 * 
355 	 * @param model
356 	 *            The database model
357 	 * 
358 	 */
359 	protected void dropDatabaseTables(Database model)
360 			throws DatabaseOperationException
361 	{
362 		platform.dropTables(model, true);
363 	}
364 
365 	/***
366 	 * Reads the database model from a live database.
367 	 * 
368 	 * @param platform
369 	 *            The physical database connection
370 	 * @param databaseName
371 	 *            The name of the resulting database
372 	 * @return The model
373 	 */
374 	public Database readModelFromDatabase(String databaseName)
375 	{
376 		return platform.readModelFromDatabase(databaseName);
377 	}
378 
379 	/***
380 	 * datasource.class=org.apache.commons.dbcp.BasicDataSource
381 	 * datasource.driverClassName=com.mysql.jdbc.Driver
382 	 * datasource.url=jdbc:mysql://localhost/ddlutils datasource.username=root
383 	 * datasource.password=root123
384 	 * 
385 	 */
386 
387 	/***
388 	 * Initializes the datasource and the connection (platform)
389 	 */
390 	public void init(Map parameters)
391 	{
392 		if (connected)
393 			tearDown();
394 
395 		try
396 		{
397 			String dataSourceClass = (String) parameters.get(DATASOURCE_CLASS);
398 			if (dataSourceClass == null)
399 				dataSourceClass = BasicDataSource.class.getName();
400 
401 			dataSource = (DataSource) Class.forName(dataSourceClass)
402 					.newInstance();
403 
404 			for (Iterator it = parameters.entrySet().iterator(); it.hasNext();)
405 			{
406 				Map.Entry entry = (Map.Entry) it.next();
407 				String propName = (String) entry.getKey();
408 
409 				if (!(propName.equals(DATASOURCE_CLASS)))
410 				{
411 					BeanUtils.setProperty(dataSource, propName, entry
412 							.getValue());
413 				}
414 			}
415 		} catch (Exception ex)
416 		{
417 			throw new DatabaseOperationException(ex);
418 		}
419 		String databaseName = null;
420 		_databaseName = null;        
421 		try
422 		{
423 			databaseName = (String) parameters.get(DATASOURCE_DATABASENAME);
424 			if (databaseName != null)
425 			{
426 				platform = PlatformFactory.createNewPlatformInstance(databaseName);
427 				if (platform != null)
428 					_databaseName = databaseName;
429 			}
430 		} catch (Exception ex)
431 		{
432 			log.warn("Exception in trying to establish connection to " + databaseName + " : " + ex.getLocalizedMessage());
433 			log.warn(ex);
434 		}
435 		if (_databaseName == null)
436 		{
437 			_databaseName = new PlatformUtils().determineDatabaseType(dataSource);
438 			if (_databaseName == null)
439 			{
440 				throw new DatabaseOperationException(
441 					"Could not determine platform from datasource, please specify it in the jdbc.properties via the ddlutils.platform property");
442 			}
443 			else
444 			{
445 				try
446 				{
447 					platform = PlatformFactory.createNewPlatformInstance(_databaseName);
448 				} catch (Exception ex)
449 				{
450 					throw new DatabaseOperationException(
451 					"Could not establish connection to " + _databaseName + " : " + ex.getLocalizedMessage(),ex);
452 				}
453 			}
454 		}
455 //		com.mysql.jdbc.Driver
456 		
457 		writer = new StringWriter();
458 		platform.getSqlBuilder().setWriter(writer);
459 //		if (platform.getPlatformInfo().isDelimitedIdentifiersSupported())
460 //		{
461 //			platform.setDelimitedIdentifierModeOn(true);
462 //		}
463 
464 	
465 		platform.setDataSource(dataSource);
466         System.out.println("reading model...");
467 		model = this.readModelFromDatabase(null);
468         System.out.println("done reading model...");
469 /***		
470 		JdbcModelReader reader = platform.getModelReader();		
471 		try
472 		{
473 		model = reader.getDatabase(platform.borrowConnection(), null);
474 		} catch (Exception ex)
475 		{
476 			throw new DatabaseOperationException(ex);
477 		}
478 */
479 		
480 		connected = true;
481 	}
482 
483 	/***
484 	 * Returns the database model.
485 	 * 
486 	 * @return The model
487 	 */
488 	protected Database getModel()
489 	{
490 		return model;
491 	}
492 
493 	/***
494 	 * Inserts data into the database.
495 	 * 
496 	 * @param dataXml
497 	 *            The data xml
498 	 * @return The database
499 	 */
500 	protected Database insertData(String dataXml)
501 			throws DatabaseOperationException
502 	{
503 		try
504 		{
505 			DataReader dataReader = new DataReader();
506 
507 			dataReader.setModel(model);
508 			dataReader.setSink(new DataToDatabaseSink(platform, model));
509 			dataReader.parse(new StringReader(dataXml));
510 			return model;
511 		} catch (Exception ex)
512 		{
513 			throw new DatabaseOperationException(ex);
514 		}
515 	}
516 
517 	/***
518 	 * Drops the tables defined in the database model.
519 	 */
520 	protected void dropDatabase() throws DatabaseOperationException
521 	{
522 		platform.dropTables(model, true);
523 	}
524 
525 	/***
526 	 * Determines the value of the bean's property that has the given name.
527 	 * Depending on the case-setting of the current builder, the case of teh
528 	 * name is considered or not.
529 	 * 
530 	 * @param bean
531 	 *            The bean
532 	 * @param propName
533 	 *            The name of the property
534 	 * @return The value
535 	 */
536 	protected Object getPropertyValue(DynaBean bean, String propName)
537 	{
538 		if (platform.isDelimitedIdentifierModeOn())
539 		{
540 			return bean.get(propName);
541 		} else
542 		{
543 			DynaProperty[] props = bean.getDynaClass().getDynaProperties();
544 
545 			for (int idx = 0; idx < props.length; idx++)
546 			{
547 				if (propName.equalsIgnoreCase(props[idx].getName()))
548 				{
549 					return bean.get(props[idx].getName());
550 				}
551 			}
552 			throw new IllegalArgumentException(
553 					"The bean has no property with the name " + propName);
554 		}
555 	}
556 
557 	public DataSource getDataSource()
558 	{
559 		return dataSource;
560 	}
561 
562 	public Platform getPlatform()
563 	{
564 		return platform;
565 	}
566 	
567 		 
568 	
569     public  List getRows(String tableName)
570     {
571         Table table = getModel().findTable(tableName, getPlatform().isDelimitedIdentifierModeOn());
572         
573         return getPlatform().fetch(getModel(), getSelectQueryForAllString( table), new Table[] { table });
574     }
575 
576     
577     public  String getSelectQueryForAllString( Table table)
578     {
579     
580 	    StringBuffer query = new StringBuffer();
581 	
582 	    query.append("SELECT * FROM ");
583 	    if (getPlatform().isDelimitedIdentifierModeOn())
584 	    {
585 	        query.append(getPlatform().getPlatformInfo().getDelimiterToken());
586 	    }
587 	    query.append(table.getName());
588 	    if (getPlatform().isDelimitedIdentifierModeOn())
589 	    {
590 	        query.append(getPlatform().getPlatformInfo().getDelimiterToken());
591 	    }
592 	    return query.toString();
593     }
594 	
595     
596   
597     
598 }