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