View Javadoc

1   /*
2    * Copyright 2004-2006 the Seasar Foundation and the Others.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 
13   * either express or implied. See the License for the specific language
14   * governing permissions and limitations under the License.
15   */
16  package org.seasar.tuigwaa.database;
17  
18  import java.io.InputStream;
19  import java.sql.Connection;
20  import java.sql.ResultSet;
21  import java.sql.ResultSetMetaData;
22  import java.sql.SQLException;
23  import java.sql.Statement;
24  import java.util.ArrayList;
25  import java.util.Arrays;
26  import java.util.Collection;
27  import java.util.Collections;
28  import java.util.HashMap;
29  import java.util.Iterator;
30  import java.util.List;
31  import java.util.Map;
32  
33  import javax.sql.DataSource;
34  import javax.sql.XADataSource;
35  import javax.transaction.TransactionManager;
36  
37  import org.apache.commons.beanutils.DynaBean;
38  import org.apache.commons.beanutils.DynaProperty;
39  import org.apache.commons.collections.CollectionUtils;
40  import org.apache.commons.logging.Log;
41  import org.apache.commons.logging.LogFactory;
42  import org.seasar.extension.dbcp.impl.ConnectionPoolImpl;
43  import org.seasar.extension.dbcp.impl.DataSourceImpl;
44  import org.seasar.extension.dbcp.impl.XADataSourceImpl;
45  import org.seasar.framework.exception.SQLRuntimeException;
46  import org.seasar.tuigwaa.database.util.CSVfileLoader;
47  import org.seasar.tuigwaa.database.util.DatafileLoader;
48  import org.seasar.tuigwaa.database.util.DynaBatchHandler;
49  import org.seasar.tuigwaa.database.util.DynaPropertyWrapper;
50  import org.seasar.tuigwaa.database.util.DynaSelectHandler;
51  import org.seasar.tuigwaa.database.util.EntityFactory;
52  import org.seasar.tuigwaa.model.common.DomainUtils;
53  import org.seasar.tuigwaa.system.Constants;
54  import org.seasar.tuigwaa.system.TgwServiceException;
55  import org.seasar.tuigwaa.util.TgwPathResolver;
56  import org.seasar.tuigwaa.util.TgwResource;
57  
58  import com.isenshi.util.ResourceUtils;
59  import com.isenshi.util.extlib.DiconResource;
60  
61  /***
62   * Need to fix the policy of byte array data backup/restore.
63   * 
64   * @author someda
65   */
66  public class BasicDatabaseServiceImpl implements BasicDatabaseService {
67  
68  	private static final String TRANSACTION_DICON = "dicon/transaction.dicon";
69  
70  	private MultiDataSource multiDataSource;
71  
72  	private Log log_ = LogFactory.getLog(getClass());
73  	
74  	private Map databaseMap = new HashMap();
75  	
76  	private Map schemaMap = new HashMap();
77  
78  	private Map driverMap = Collections.synchronizedMap(new HashMap());
79  
80  	private TransactionManager tmanager;
81  
82  	public BasicDatabaseServiceImpl(MultiDataSource multiDataSource,
83  			DynaSelectHandler selectHandler, DynaBatchHandler batchHandler,
84  			TransactionManager tmanager) {
85  		this.multiDataSource = multiDataSource;
86  //		this.selectHandler_ = selectHandler;
87  //		this.batchHandler_ = batchHandler;
88  		this.tmanager = tmanager;
89  	}
90  
91  	public String getDriver(String domainName) {
92  		String databaseName = getDataBaseName(domainName);
93  		String driver = (String) driverMap.get(databaseName);
94  		if (driver != null) {
95  			return driver;
96  		} else {
97  			return TgwResource.getProperty("database.driver");
98  		}
99  	}
100 
101 	public boolean hasDomain(String databaseName) {
102 		Iterator itr = databaseMap.keySet().iterator();
103 		while(itr.hasNext()){
104 			String domainName = (String)itr.next();
105 			String aDatabaseName = (String)databaseMap.get(domainName);
106 			if(databaseName.equals(aDatabaseName)){
107 				return true;
108 			}
109 		}
110 		return false;		
111 	}
112 	
113 	public String getSchemaName(String domainName) {
114 		String schemaName = (String) schemaMap.get(domainName);
115 		if (schemaName != null) {
116 			return schemaName;
117 		}
118 		return domainName;
119 	}
120 
121 	public void addExternalDatabase(String dbName, String driver, String user,
122 			String password, String url) {
123 
124 //		 create data source
125 		XADataSourceImpl xaDataSource = new XADataSourceImpl();
126 		xaDataSource.setDriverClassName(driver);
127 		xaDataSource.setUser(user);
128 		xaDataSource.setURL(url);
129 		xaDataSource.setPassword(password);
130 		ConnectionPoolImpl pool = new ConnectionPoolImpl();
131 		pool.setXADataSource(xaDataSource);
132 		pool.setTransactionManager(tmanager);
133 		DataSourceImpl dataSource = new DataSourceImpl(pool);
134 
135 		String path = TgwPathResolver.getDatabaseFilePath(dbName);
136 
137 		DiconResource dicon = new DiconResource();
138 		dicon.setNamespace(dbName);
139 		dicon.setPath(path);
140 		dicon.addComponent(xaDataSource, null, new String[] {
141 				"driverClassName", "user", "URL", "password" });
142 		dicon.addComponent(pool, null, new String[] {});
143 		dicon.addComponent(dataSource, null, new String[] {});
144 		dicon.save();
145 
146 		addDataSource(dataSource, dbName, driver);
147 	}
148 
149 	public void loadExternalDatabase() {
150 		String[] files = ResourceUtils.getChildrenNames(Constants.DIRECTORY_APP);
151 		if (files == null) {
152 			return;
153 		}
154 		for (int i = 0; i < files.length; i++) {
155 			if (files[i].endsWith(".db")) {
156 				DiconResource dicon = new DiconResource();
157 				dicon.setPath(Constants.DIRECTORY_APP + files[i]);
158 				dicon.load();
159 
160 				dicon.bindRoot();
161 				dicon.include(TRANSACTION_DICON);
162 
163 				String name = dicon.getNamespace();
164 				DataSource dataSource = (DataSource) dicon
165 						.getComponent(DataSource.class);
166 
167 				XADataSource xa = (XADataSource) dicon
168 						.getComponent(XADataSource.class);
169 				String driverName = ((XADataSourceImpl) xa)
170 						.getDriverClassName();
171 
172 				addDataSource(dataSource, name, driverName);
173 			}
174 		}
175 	}
176 
177 	private void addDataSource(DataSource dataSource, String dbName, String driverName){
178 		if (checkConnection(dataSource)) {
179 			driverMap.put(dbName, driverName);
180 			multiDataSource.addDataSource(dbName, dataSource);
181 		}
182 	}
183 	
184 	public void deleteExternalDatabase(String databaseName) {
185 		driverMap.remove(databaseName);
186 		multiDataSource.removeDataSource(databaseName);
187 	}
188 	
189 	private boolean checkConnection(DataSource dataSource) {
190 		boolean connFlag = false;
191 		try {
192 			Connection con = dataSource.getConnection();
193 			con.close();
194 			connFlag = true;
195 		} catch (SQLException e) {
196 			log_.warn("can't load external dabtabase");
197 		}
198 		return connFlag;
199 	}
200 
201 	public boolean useBaseDatabase(String domainName) {
202 		return databaseMap.get(domainName) == null;
203 	}
204 
205 	public void setExternalDatabaseMapping(String domainName, String dbName, String schemaName) throws TgwServiceException{
206 		if(!multiDataSource.isExistDatabase(dbName)){
207 			throw new TgwServiceException("doesn't exit database " + dbName);
208 		}
209 		putDataBaseName(domainName, dbName);
210 		if (schemaName != null) {
211 			schemaMap.put(domainName, schemaName);
212 		}
213 	}
214 
215 	public void removeExternalDatabaseMapping(String domainName) {
216 		removeDataBaseName(domainName);
217 		schemaMap.remove(domainName);
218 	}
219 	
220 	public List getTableNames(String domainName) {
221 		DatabaseInfo info = getDatabaseInfo(domainName);
222 		String schema = getSchemaName(domainName);
223 		return info.getTablenameNames(schema);
224 	}
225 
226 	public List getSchemaNames(String domainName) {
227 		DatabaseInfo info = getDatabaseInfo(domainName);
228 		return info.getSchemaNames();
229 	}
230 
231 	public boolean existSchema(String schema) {
232 		Iterator itr = getSchemaNames(schema).iterator();
233 		boolean flag = false;
234 		while (itr.hasNext()) {
235 			String schemaName = (String) itr.next();
236 			if (schemaName.equalsIgnoreCase(schema)) {
237 				flag = true;
238 				break;
239 			}
240 		}
241 		return flag;
242 	}
243 
244 	public Map createEntityMap(String domainName) throws SQLException {
245 		String schema = getSchemaName(domainName);
246 		DatabaseInfo info = getDatabaseInfo(domainName);
247 		EntityFactory factory = new EntityFactory(domainName, schema, info);
248 		Connection con = getConnection(domainName);
249 
250 		try {
251 			Statement stmt = con.createStatement();
252 
253 			for (Iterator i = info.getTablenameNames(schema).iterator(); i
254 					.hasNext();) {
255 				String tableName = (String) i.next();
256 				String sql = DatabaseUtils.buildBackupQuery(schema, tableName);
257 				ResultSet rs = stmt.executeQuery(sql);
258 				ResultSetMetaData metadata = rs.getMetaData();
259 
260 				String entityName = tableName.toLowerCase();
261 				factory.addEntity(metadata, entityName);
262 			}
263 		} catch (SQLException e) {
264 			e.printStackTrace();
265 		}
266 		return factory.getCreatedEntityMap();
267 	}
268 
269 	public boolean existTable(String domainName, String name) {
270 		Iterator itr = getTableNames(domainName).iterator();
271 		boolean flag = false;
272 		while (itr.hasNext()) {
273 			String tableName = (String) itr.next();
274 			if (tableName.equalsIgnoreCase(name)) {
275 				flag = true;
276 				break;
277 			}
278 		}
279 		return flag;
280 	}
281 
282 	public DatabaseInfo getBaseDatabaseInfo() {
283 		return getDatabaseInfo(multiDataSource.getBaseConnection());
284 	}
285 
286 	public DatabaseInfo getDatabaseInfo(String domainName) {
287 		try {
288 			Connection con = getConnection(domainName);
289 			return getDatabaseInfo(con);
290 		} catch (SQLException e) {
291 			throw new SQLRuntimeException(e);
292 		}
293 	}
294 
295 	public DatabaseInfo getDatabaseInfo(Connection conn) {
296 		DatabaseInfo info = null;
297 		try {
298 			info = new DatabaseInfo(conn);
299 		} catch (SQLException se) {
300 			log_.error(se.getMessage());
301 		} finally {
302 			DatabaseUtils.closeQueitly(conn);
303 		}
304 		return info;
305 	}
306 
307 	public String[] getExternalDatabaseNames() {
308 		return multiDataSource.getExternalDatabaseNames();
309 	}
310 
311 	public List getExternalDatabaseInfoList() {
312 		String[] names = getExternalDatabaseNames();
313 		List list = new ArrayList();
314 		for (int i = 0; i < names.length; i++) {
315 			try {
316 				Connection conn = multiDataSource.getConnection(names[i]);
317 				DatabaseInfo dbInfo = getDatabaseInfo(conn);
318 				dbInfo.setName(names[i]);
319 				list.add(dbInfo);
320 			} catch (SQLException e) {
321 				// TODO error handling when an external database doesn't run
322 			}
323 		}
324 		return list;
325 	}
326 	
327 	public void backup(String dbName, String schema, String[] tableNames, String targetdir) throws
328 	TgwServiceException{
329 		
330 		DatabaseInfo info = getDatabaseInfo(schema);
331 		if(dbName != null){
332 			try{
333 				Connection con = multiDataSource.getConnection(dbName);
334 				info = getDatabaseInfo(con);				
335 			}catch(SQLException se){
336 				throw new TgwServiceException(se);
337 			}
338 		}
339 		
340 		Collection backupTableNameList = null;		
341 		if(tableNames == null || tableNames.length == 0){// full backup
342 			backupTableNameList = info.getTablenameNames(schema);			
343 		}else{
344 			Collection c = new ArrayList(Arrays.asList(tableNames));			
345 			for(int i=0;i<tableNames.length;i++){
346 				List list = info.getForeignKeys(null,schema,tableNames[i]);
347 				c = CollectionUtils.union(c,list);
348 			}
349 			backupTableNameList = c;
350 		}		
351 		DiconResource resource = new DiconResource();
352 		resource.setPath(TgwPathResolver.getDatabaseBackupFilePath(targetdir));		
353 		
354 		for (Iterator i = backupTableNameList.iterator(); i.hasNext();) { // table						
355 			String table = (String) i.next();
356 			log_.info("back up table... " + table);			
357 			backupTable(dbName, schema, table, resource, targetdir);
358 		}
359 		resource.save();
360 	}
361 		
362 	public void restore(String dbName, String schema, String[] tableNames, String srcdir)
363 	throws TgwServiceException{
364 		// DatabaseInfo info = getDatabaseInfo(schema);
365 		// List tableList = info.getTablenameNames(schema);
366 		// List processList = new ArrayList();
367 		// info.sortByReference(null, schema, tableList, processList);
368 
369 		List processList = DomainUtils.getSortedTalbeNames(schema);
370 
371 		DiconResource resource = new DiconResource();
372 		resource.setPath(TgwPathResolver.getDatabaseBackupFilePath(srcdir));
373 		resource.load();
374 
375 		// for initial clean-up
376 		// if on-delete-cascade to be set, this reverse operation is unnecessary
377 		// FIXME: should ignore table which contains byte[] columns??
378 		Collections.reverse(processList);
379 		deleteDataAll(dbName, schema, processList, srcdir, resource);
380 
381 		// for data insert
382 		Collections.reverse(processList);
383 		restoreDataAll(dbName, schema, processList, srcdir, resource);		
384 		
385 	}
386 
387 	public Connection getConnection(String domainName) throws SQLException {
388 		String databaseName = getDataBaseName(domainName);
389 		if (databaseName != null) {
390 			return multiDataSource.getConnection(databaseName);
391 		} else {
392 			return multiDataSource.getBaseConnection();
393 		}
394 	}
395 			
396 	public void insertOnDelete(String dbName, String schema, String table, DatafileLoader loader, String[] columns, String[] types)
397 	throws TgwServiceException{
398 		delete(dbName, schema,table);
399 		insert(dbName, schema,table,loader,columns,types);
400 	}	
401 		
402 	public void insert(String dbName, String schema, String table, DatafileLoader loader, String[] columns, String[] types)
403 	throws TgwServiceException{
404 		
405 		String[] data;
406 		List bindDataList = new ArrayList();
407 		
408 		while((data =loader.load()) != null){
409 			Object[] obj = DatabaseUtils.bindArgs(data,types);
410 			bindDataList.add(obj);
411 		}
412 				
413 		String sql = DatabaseUtils.buildRestoreQuery(schema, table.toUpperCase(), columns);
414 		Class[] argTypes = convertTypes(types);
415 		Collections.reverse(bindDataList);
416 		
417 		log_.info("Starting restore contents of " + table + " " + sql);
418 		
419 		DynaBatchHandler handler = multiDataSource.getDynaBatchHandler(dbName);
420 		
421 		try{
422 //			batchHandler_.setSql(sql);
423 			handler.setSql(sql);
424 			int num = handler.execute(bindDataList, argTypes);
425 			log_.info(num + " rows inserted.");
426 		}catch(SQLRuntimeException e){
427 			throw new TgwServiceException(e);
428 		}				
429 	}
430 	
431 	public void delete(String dbName, String schema, String table) throws TgwServiceException{		
432 
433 		String sql = DatabaseUtils.buildCleanQuery(schema, table.toUpperCase());
434 		DynaBatchHandler handler = multiDataSource.getDynaBatchHandler("");
435 		
436 		log_.info("Starting cleanup contents of " + table + " " + sql);		
437 		try{
438 //			batchHandler_.setSql(sql);
439 //			batchHandler_.execute();
440 			handler.setSql(sql);
441 			handler.execute();
442 		}catch(SQLRuntimeException e){
443 			throw new TgwServiceException(e);
444 		}
445 	}
446 
447 	// [Start] ------ Private Method ------	
448 	private void putDataBaseName(String domainName, String databaseName){
449 		databaseMap.put(domainName, databaseName);
450 	}
451 
452 	private void removeDataBaseName(String domainName){
453 		databaseMap.remove(domainName);
454 	}
455 	
456 	private String getDataBaseName(String domainName){
457 		return (String)databaseMap.get(domainName);
458 	}
459 		
460 	private void backupTable(String dbName, String schema, String table,
461 			DiconResource resource, String targetdir) {
462 
463 		Map options = new HashMap();
464 		
465 		String uppercaseTableName = table.toUpperCase();		
466 		options.put(DiconResource.OPTION_NAME, uppercaseTableName);
467 
468 		String sql = DatabaseUtils.buildBackupQuery(schema, table);
469 		DynaSelectHandler handler = multiDataSource.getDynaSelectHandler(dbName);		
470 		
471 //		selectHandler_.setSql(sql);
472 		handler.setSql(sql);
473 
474 		List result = (List) handler.execute(null);
475 
476 		// if there is no data, skip backup.
477 		if (result.size() == 0) {
478 			log_.info(table + " is empty, skip to backup.");
479 			return;
480 		}
481 
482 		String path = TgwPathResolver.getTableBackupFilePath(targetdir, uppercaseTableName);				
483 		
484 		String[] names = null;
485 		StringBuffer buf = new StringBuffer();
486 		DynaPropertyWrapper propwrapper = new DynaPropertyWrapper();
487 
488 		for (Iterator rows = result.iterator(); rows.hasNext();) { // resultset
489 			DynaBean row = (DynaBean) rows.next();
490 
491 			if (names == null) { // first row
492 				DynaProperty[] props = row.getDynaClass().getDynaProperties();
493 				propwrapper.put(props);
494 				names = propwrapper.getNames();
495 				resource.addComponent(propwrapper, options);
496 			}
497 			backupColumns(buf, row, names);
498 		}
499 		ResourceUtils.writeContent(path, buf.toString());
500 	}
501 
502 	private void backupColumns(StringBuffer buf, DynaBean row, String[] names) {
503 		boolean firstColumn = true;
504 		for (int j = 0; j < names.length; j++) { // each column loop
505 
506 			if (firstColumn) {
507 				firstColumn = false;
508 			} else {
509 				buf.append(",");
510 			}
511 
512 			Object o = row.get(names[j]);
513 			if (o != null) {
514 				String s = o.toString().replaceAll("\"", "\"\"");
515 				buf.append("\"" + s + "\"");
516 			} else {
517 				buf.append("\"\"");
518 			}
519 
520 		}
521 		buf.append(Constants.LINEBREAK_CODE);
522 	}
523 
524 	private void deleteDataAll(String dbName, String schema, List processList, String srcdir,
525 			DiconResource resource) throws TgwServiceException{
526 		log_.info("delete order " + processList);
527 
528 		for (Iterator i = processList.iterator(); i.hasNext();) {
529 			String table = (String) i.next();
530 			delete(dbName, schema,table);
531 		}
532 	}
533 	
534 	private void restoreDataAll(String dbName, String schema, List processList, String srcdir,
535 			DiconResource resource) throws TgwServiceException{
536 		log_.info("restoring order " + processList);
537 		for (Iterator i = processList.iterator(); i.hasNext();) {
538 			String table = (String) i.next();
539 			table = table.toUpperCase();
540 			String path = TgwPathResolver.getTableBackupFilePath(srcdir, table);
541 
542 			if (ResourceUtils.isExist(path)) {
543 				DynaPropertyWrapper wrapper = (DynaPropertyWrapper) resource.getComponent(table);
544 				String[] types = wrapper.getTypes();
545 				String[] columns = wrapper.getNames();
546 				InputStream is = ResourceUtils.readContentAsStream(path);
547 				DatafileLoader loader = new CSVfileLoader(is);
548 				insert(dbName, schema,table,loader,columns,types);				
549 				loader.close();				
550 			}
551 		}
552 	}	
553 	
554 	private Class[] convertTypes(String[] types){
555 		
556 		if(types == null){
557 			return null;
558 		}		
559 		Class[] argTypes = new Class[types.length];
560 		for(int i=0; i<types.length;i++){			
561 			String className = types[i];
562 			if("java.sql.Date".equals(className) || "java.sql.Timestamp".equals(className)){
563 				className = "java.util.Date";
564 			}			
565 			try{
566 				argTypes[i] = Class.forName(className);			
567 			}catch(ClassNotFoundException cnfe){
568 				argTypes[i] = Object.class;
569 			}			
570 		}				
571 		return argTypes;		
572 	}
573 		
574 }