(no commit message)
[cdmlib.git] / cdmlib-persistence / src / main / java / eu / etaxonomy / cdm / database / CdmDataSource.java
1 package eu.etaxonomy.cdm.database;
2
3 import java.io.File;
4 import java.io.FileInputStream;
5 import java.io.FileNotFoundException;
6 import java.io.FileOutputStream;
7 import java.io.FilenameFilter;
8 import java.util.ArrayList;
9 import java.util.Enumeration;
10 import java.util.HashMap;
11 import java.util.Iterator;
12 import java.util.List;
13 import java.util.Map;
14 import java.util.Properties;
15
16 import org.apache.log4j.Logger;
17 import org.hibernate.cache.CacheProvider;
18 import org.hibernate.cache.NoCacheProvider;
19 import org.jdom.Attribute;
20 import org.jdom.Document;
21 import org.jdom.Element;
22 import org.jdom.output.Format;
23 import org.springframework.beans.MutablePropertyValues;
24 import org.springframework.beans.factory.config.BeanDefinition;
25 import org.springframework.beans.factory.config.PropertiesFactoryBean;
26 import org.springframework.beans.factory.support.AbstractBeanDefinition;
27 import org.springframework.beans.factory.support.RootBeanDefinition;
28 import org.springframework.jdbc.datasource.DriverManagerDataSource;
29
30 import eu.etaxonomy.cdm.api.application.CdmApplicationUtils;
31 import eu.etaxonomy.cdm.common.CdmUtils;
32 import eu.etaxonomy.cdm.common.XmlHelp;
33
34 import static eu.etaxonomy.cdm.common.XmlHelp.getFirstAttributedChild;
35 import static eu.etaxonomy.cdm.common.XmlHelp.getOrAddChild;
36 import static eu.etaxonomy.cdm.common.XmlHelp.getRoot;
37 import static eu.etaxonomy.cdm.common.XmlHelp.insertXmlBean;
38 import static eu.etaxonomy.cdm.common.XmlHelp.insertXmlRefProperty;
39 import static eu.etaxonomy.cdm.common.XmlHelp.insertXmlValueProperty;
40 import static eu.etaxonomy.cdm.common.XmlHelp.saveToXml;
41
42
43 /**
44 * class to access an CdmDataSource
45 */
46 public class CdmDataSource {
47 private static final Logger logger = Logger.getLogger(CdmDataSource.class);
48
49 public static final String DATASOURCE_BEAN_POSTFIX = "DataSource";
50 public final static String DATASOURCE_FILE_NAME = "cdm.datasources.xml";
51 private final static Format format = Format.getPrettyFormat();
52
53 public enum DbProperties{
54 DRIVER_CLASS,
55 URL,
56 USERNAME,
57 PASSWORD;
58
59 @Override
60 public String toString(){
61 switch (this){
62 case DRIVER_CLASS:
63 return "driverClassName";
64 case URL:
65 return "url";
66 case USERNAME:
67 return "username";
68 case PASSWORD:
69 return "password";
70 default:
71 throw new IllegalArgumentException( "Unknown enumeration type" );
72 }
73 }
74 }
75
76 public enum HBM2DDL{
77 VALIDATE,
78 UPDATE,
79 CREATE,
80 CREATE_DROP;
81
82 @Override
83 public String toString(){
84 switch (this){
85 case VALIDATE:
86 return "validate";
87 case UPDATE:
88 return "update";
89 case CREATE:
90 return "create";
91 case CREATE_DROP:
92 return "create-drop";
93 default:
94 throw new IllegalArgumentException( "Unknown enumeration type" );
95 }
96 }
97 }
98
99 //name
100 protected String dataSourceName;
101
102
103 /**
104 * Returns the default CdmDataSource
105 * @return the default CdmDataSource
106 */
107 public final static CdmDataSource NewDefaultInstance(){
108 try {
109 return NewInstance("default");
110 } catch (DataSourceNotFoundException e) {
111 logger.error("Default datasource does not exist in config file");
112 return null;
113 }
114 }
115
116
117 /**
118 * Returns the default CdmDataSource
119 * @return the default CdmDataSource
120 */
121 public final static CdmDataSource NewLocalHsqlInstance(){
122 try {
123 return NewInstance("localDefaultHsql");
124 } catch (DataSourceNotFoundException e) {
125 logger.error("Local datasource does not exist in config file");
126 return null;
127 }
128 }
129
130 /**
131 * Returns the CdmDataSource named by strDataSource
132 * @param strDataSource
133 * @return
134 */
135 public final static CdmDataSource NewInstance(String dataSourceName)
136 throws DataSourceNotFoundException{
137 if (exists(dataSourceName)){
138 return new CdmDataSource(dataSourceName);
139 }else{
140 throw new DataSourceNotFoundException("Datasource not found: " + dataSourceName);
141 }
142 }
143
144 /**
145 * Private Constructor. Use NewXXX factory methods for creating a new instance of CdmDataSource!
146 * @param strDataSource
147 */
148 private CdmDataSource(String strDataSource){
149 dataSourceName = strDataSource;
150 }
151
152 /**
153 * Returns the name of the bean.
154 * @return
155 */
156 public String getName(){
157 return dataSourceName;
158 }
159
160
161 /**
162 * Returns the name of the bean Element in the xml config file.
163 * @return bean name
164 */
165 private static String getBeanName(String name){
166 return name == null? null : name + DATASOURCE_BEAN_POSTFIX;
167 }
168
169
170
171 /**
172 * Returns the database type of the data source.
173 * @return the database type of the data source. Null if the bean or the driver class property does not exist or the driver class is unknown.
174 */
175 public DatabaseTypeEnum getDatabaseType(){
176 Element bean = getDatasourceBeanXml(this.dataSourceName);
177 if (bean == null){
178 return null;
179 }else{
180 Element driverProp = XmlHelp.getFirstAttributedChild(bean, "property", "name", "driverClassName");
181 if (driverProp == null){
182 logger.warn("Unknown property driverClass");
183 return null;
184 }else{
185 String strDriverClass = driverProp.getAttributeValue("value");
186 DatabaseTypeEnum dbType = DatabaseTypeEnum.getDatabaseEnumByDriverClass(strDriverClass);
187 return dbType;
188 }
189 }
190 }
191
192
193
194 /**
195 * Returns a defined property of the datasource
196 * @return the property of the data source. NULL if the datasource bean or the property does not exist.
197 */
198 public String getDbProperty(DbProperties dbProp){
199 Element bean = getDatasourceBeanXml(this.dataSourceName);
200 if (bean == null){
201 return null;
202 }else{
203 Element elProperty = XmlHelp.getFirstAttributedChild(bean, "property", "name", dbProp.toString());
204 if (elProperty == null){
205 logger.warn("Unknown property: " + dbProp.toString());
206 return null;
207 }else{
208 String strValue = elProperty.getAttributeValue("value");
209 return strValue;
210 }
211 }
212 }
213
214
215 /**
216 * Returns the list of properties that are defined in the datasource
217 * @return
218 */
219 public Properties getDbProperties(){
220 Properties result = new Properties();
221 Element bean = getDatasourceBeanXml(this.dataSourceName);
222 if (bean == null){
223 return null;
224 }else{
225 List<Element> elProperties = XmlHelp.getAttributedChildList(bean, "property", "name");
226 Iterator<Element> iterator = elProperties.iterator();
227 while(iterator.hasNext()){
228 String strName = iterator.next().getAttributeValue("name");
229 String strValue = iterator.next().getAttributeValue("value");
230 result.put(strName, strValue);
231 }
232 }
233 return result;
234 }
235
236 /**
237 * Returns a BeanDefinition object of type DriverManagerDataSource that contains
238 * datsource properties (url, username, password, ...)
239 * @return
240 */
241 public BeanDefinition getDatasourceBean(){
242 DatabaseTypeEnum dbtype = DatabaseTypeEnum.getDatabaseEnumByDriverClass(getDbProperty(DbProperties.DRIVER_CLASS));
243
244 AbstractBeanDefinition bd = new RootBeanDefinition(dbtype.getDriverManagerDataSourceClass());
245
246 MutablePropertyValues props = new MutablePropertyValues();
247 Properties persistentProperties = getDbProperties();
248 Enumeration<String> keys = (Enumeration)persistentProperties.keys();
249 while (keys.hasMoreElements()){
250 String key = (String)keys.nextElement();
251 props.addPropertyValue(key, persistentProperties.getProperty(key));
252 }
253 // props.addPropertyValue("driverClassName", dbtype.getDriverClassName());
254 // props.addPropertyValue("url", getDbProperty(DbProperties.URL));
255 // props.addPropertyValue("username", getDbProperty(DbProperties.USERNAME));
256 // props.addPropertyValue("password", getDbProperty(DbProperties.PASSWORD));
257 bd.setPropertyValues(props);
258 return bd;
259 }
260
261 /**
262 * @param hbm2dll
263 * @param showSql
264 * @return
265 */
266 public BeanDefinition getHibernatePropertiesBean(HBM2DDL hbm2dll){
267 boolean showSql = false;
268 boolean formatSql = false;
269 Class<? extends CacheProvider> cacheProviderClass = NoCacheProvider.class;
270 return getHibernatePropertiesBean(hbm2dll, showSql, formatSql, cacheProviderClass);
271 }
272
273
274 /**
275 * @param hbm2dll
276 * @param showSql
277 * @return
278 */
279 public BeanDefinition getHibernatePropertiesBean(HBM2DDL hbm2dll, Boolean showSql, Boolean formatSql, Class<? extends CacheProvider> cacheProviderClass){
280 //Hibernate default values
281 if (hbm2dll == null){
282 hbm2dll = HBM2DDL.VALIDATE;
283 }
284 if (showSql == null){
285 showSql = false;
286 }
287 if (formatSql == null){
288 formatSql = false;
289 }
290 if (cacheProviderClass == null){
291 cacheProviderClass = NoCacheProvider.class;
292 }
293
294 DatabaseTypeEnum dbtype = getDatabaseType();
295 AbstractBeanDefinition bd = new RootBeanDefinition(PropertiesFactoryBean.class);
296 MutablePropertyValues hibernateProps = new MutablePropertyValues();
297
298 Properties props = new Properties();
299 props.setProperty("hibernate.hbm2ddl.auto", hbm2dll.toString());
300 props.setProperty("hibernate.dialect", dbtype.getHibernateDialect());
301 props.setProperty("hibernate.cache.provider_class", cacheProviderClass.getName());
302 props.setProperty("hibernate.show_sql", String.valueOf(showSql));
303 props.setProperty("hibernate.format_sql", String.valueOf(formatSql));
304
305 hibernateProps.addPropertyValue("properties",props);
306 bd.setPropertyValues(hibernateProps);
307 return bd;
308 }
309
310
311 /**
312 * Tests existing of the datsource in the according config file.
313 * @return true if a datasource with the given name exists in the according datasource config file.
314 */
315 public static boolean exists(String strDataSourceName){
316 Element bean = getDatasourceBeanXml(strDataSourceName);
317 return (bean != null);
318 }
319
320
321 /**
322 * Saves or updates the datasource to the datasource config file.
323 * Uses default port.
324 * @param strDataSourceName name of the datasource (without postfix DataSource)
325 * @param databaseTypeEnum
326 * @param server
327 * @param database
328 * @param username
329 * @param password
330 * @return the CdmDataSource, null if not successful.
331 */
332 public static CdmDataSource save(String strDataSourceName, DatabaseTypeEnum databaseTypeEnum, String server, String database,
333 String username, String password){
334 return save(strDataSourceName, databaseTypeEnum, server, database,
335 databaseTypeEnum.getDefaultPort(), username, password);
336 }
337
338 /**
339 * Saves or updates the datasource to the datasource config file.
340 * @param strDataSourceName name of the datasource (without postfix DataSource)
341 * @param databaseTypeEnum
342 * @param server
343 * @param database
344 * @param port
345 * @param username
346 * @param password
347 * @return the CdmDataSource, null if not successful.
348 */
349 public static CdmDataSource save(String strDataSourceName, DatabaseTypeEnum databaseTypeEnum, String server, String database,
350 int port, String username, String password){
351 Class<? extends DriverManagerDataSource> driverManagerDataSource = DriverManagerDataSource.class;
352 return save(strDataSourceName, databaseTypeEnum, server, database, port, username, password, driverManagerDataSource, null, null, null, null, null);
353 }
354
355
356 public static CdmDataSource saveLocalHsqlDb(String strDataSourceName, String databasePath, String databaseName, String username, String password){
357 DatabaseTypeEnum databaseTypeEnum = DatabaseTypeEnum.HSqlDb;
358 Class<? extends DriverManagerDataSource> driverManagerDataSource = LocalHsqldb.class;
359 String server = "localhost";
360 int port = databaseTypeEnum.getDefaultPort();
361 return save(strDataSourceName, databaseTypeEnum, server, databaseName, port, username, password, driverManagerDataSource, "init", "destroy", true, true, databasePath);
362 }
363
364 //
365 private static CdmDataSource save(String strDataSourceName,
366 DatabaseTypeEnum databaseTypeEnum,
367 String server,
368 String database,
369 int port,
370 String username,
371 String password,
372 Class<? extends DriverManagerDataSource> driverManagerDataSource,
373 String initMethod,
374 String destroyMethod,
375 Boolean startSilent,
376 Boolean startServer,
377 String databasePath
378 ){
379 //root
380 Element root = getRoot(getDataSourceInputStream());
381 if (root == null){
382 return null;
383 }
384 //bean
385 Element bean = XmlHelp.getFirstAttributedChild(root, "bean", "id", getBeanName(strDataSourceName));
386 if (bean != null){
387 bean.detach(); //delete old version if necessary
388 }
389 bean = insertXmlBean(root, getBeanName(strDataSourceName), driverManagerDataSource.getName());
390 //attributes
391 bean.setAttribute("lazy-init", "true");
392 if (initMethod != null) {bean.setAttribute("init-method", initMethod);}
393 if (destroyMethod != null) {bean.setAttribute("destroy-method", destroyMethod);}
394
395 //set properties
396 insertXmlValueProperty(bean, "driverClassName", databaseTypeEnum.getDriverClassName());
397 insertXmlValueProperty(bean, "url", databaseTypeEnum.getConnectionString(server, database, port));
398 if (username != null) {insertXmlValueProperty(bean, "username", username );}
399 if (password != null) {insertXmlValueProperty(bean, "password", password );}
400 if (startSilent != null) {insertXmlValueProperty(bean, "startSilent", startSilent.toString() );}
401 if (startServer != null) {insertXmlValueProperty(bean, "startServer", startServer.toString() );}
402 if (startServer != null) {insertXmlValueProperty(bean, "databasePath", databasePath );}
403
404 //save
405 saveToXml(root.getDocument(), getResourceDirectory(), DATASOURCE_FILE_NAME, format );
406 try {
407 return NewInstance(strDataSourceName) ;
408 } catch (DataSourceNotFoundException e) {
409 logger.error("Error when saving datasource");
410 return null;
411 }
412 }
413
414
415 /**
416 * Deletes a dataSource
417 * @param dataSource
418 */
419 public static void delete (CdmDataSource dataSource){
420 Element bean = getDatasourceBeanXml(dataSource.getName());
421 if (bean != null){
422 Document doc = bean.getDocument();
423 bean.detach();
424 saveToXml(doc, getDataSourceOutputStream(), format );
425 }
426 }
427
428
429 /**
430 * Returns a list of all datasources stored in the datasource config file
431 * @return all existing data sources
432 */
433 static public List<CdmDataSource> getAllDataSources(){
434 List<CdmDataSource> dataSources = new ArrayList<CdmDataSource>();
435
436 Element root = getRoot(getDataSourceInputStream());
437 if (root == null){
438 return null;
439 }else{
440 List<Element> lsChildren = root.getChildren("bean", root.getNamespace());
441
442 for (Element elBean : lsChildren){
443 String strId = elBean.getAttributeValue("id");
444 if (strId != null && strId.endsWith(DATASOURCE_BEAN_POSTFIX)){
445 strId = strId.replace(DATASOURCE_BEAN_POSTFIX, "");
446 dataSources.add(new CdmDataSource(strId));
447 }
448 }
449 }
450 return dataSources;
451 }
452
453
454 /* (non-Javadoc)
455 * @see java.lang.Object#toString()
456 */
457 public String toString(){
458 if (this.dataSourceName != null){
459 return dataSourceName;
460 }else{
461 return null;
462 }
463 }
464
465
466
467 /**
468 * Returns the datasource config file input stream.
469 * @return data source config file input stream
470 */
471 static protected FileInputStream getDataSourceInputStream(){
472 String dir = getResourceDirectory();
473 File file = new File(dir + File.separator + DATASOURCE_FILE_NAME);
474 return fileInputStream(file);
475 }
476
477
478 /**
479 * Returns the datasource config file outputStream.
480 * @return data source config file outputStream
481 */
482 static protected FileOutputStream getDataSourceOutputStream(){
483 String dir = getResourceDirectory();
484 File file = new File(dir + File.separator + DATASOURCE_FILE_NAME);
485 return fileOutputStream(file);
486 }
487
488 /**
489 * Returns the jdom Element representing the data source bean in the config file.
490 * @return
491 */
492 private static Element getDatasourceBeanXml(String strDataSourceName){
493 Element root = getRoot(getDataSourceInputStream());
494 if (root == null){
495 return null;
496 }else{
497 Element xmlBean = XmlHelp.getFirstAttributedChild(root, "bean", "id", getBeanName(strDataSourceName));
498 if (xmlBean == null){
499 //TODO warn or info
500 logger.debug("Unknown Element 'bean id=" +strDataSourceName + "' ");
501 };
502 return xmlBean;
503 }
504 }
505
506 // returns the directory containing the resources
507 private static String getResourceDirectory(){
508 File f = CdmApplicationUtils.getWritableResourceDir();
509 return f.getPath();
510 }
511
512 static private FileInputStream fileInputStream(File file){
513 try {
514 FileInputStream fis = new FileInputStream(file);
515 return fis;
516 } catch (FileNotFoundException e) {
517 logger.warn("File " + file == null?"null":file.getAbsolutePath() + " does not exist in the file system");
518 return null;
519 }
520 }
521
522 static private FileOutputStream fileOutputStream(File file){
523 try {
524 FileOutputStream fos = new FileOutputStream(file);
525 return fos;
526 } catch (FileNotFoundException e) {
527 logger.warn("File " + (file == null?"null":file.getAbsolutePath()) + " does not exist in the file system");
528 return null;
529 }
530 }
531
532
533 /**
534 * Filter class to define datasource file format
535 */
536 private static class DataSourceFileNameFilter implements FilenameFilter{
537 public boolean accept(File dir, String name) {
538 return (name.endsWith(DATASOURCE_FILE_NAME));
539 }
540 }
541
542 public boolean equals(Object obj){
543 if (obj == null){
544 return false;
545 }else if (! CdmDataSource.class.isAssignableFrom(obj.getClass())){
546 return false;
547 }else{
548 CdmDataSource dataSource = (CdmDataSource)obj;
549 return (this.dataSourceName == dataSource.dataSourceName);
550 }
551
552 }
553 }