2 * Copyright (C) 2007 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
10 package eu
.etaxonomy
.cdm
.database
;
12 import static eu
.etaxonomy
.cdm
.common
.XmlHelp
.getBeansRoot
;
13 import static eu
.etaxonomy
.cdm
.common
.XmlHelp
.insertXmlBean
;
14 import static eu
.etaxonomy
.cdm
.common
.XmlHelp
.insertXmlValueProperty
;
15 import static eu
.etaxonomy
.cdm
.common
.XmlHelp
.saveToXml
;
18 import java
.io
.FileInputStream
;
19 import java
.io
.FileNotFoundException
;
20 import java
.io
.FileOutputStream
;
21 import java
.io
.IOException
;
22 import java
.util
.ArrayList
;
23 import java
.util
.Enumeration
;
24 import java
.util
.Iterator
;
25 import java
.util
.List
;
26 import java
.util
.Properties
;
28 import javax
.sql
.DataSource
;
30 import org
.apache
.commons
.dbcp
.BasicDataSource
;
31 import org
.apache
.log4j
.Logger
;
32 import org
.hibernate
.cache
.CacheProvider
;
33 import org
.hibernate
.cache
.NoCacheProvider
;
34 import org
.jdom
.Attribute
;
35 import org
.jdom
.Document
;
36 import org
.jdom
.Element
;
37 import org
.jdom
.output
.Format
;
38 import org
.springframework
.beans
.MutablePropertyValues
;
39 import org
.springframework
.beans
.factory
.config
.BeanDefinition
;
40 import org
.springframework
.beans
.factory
.config
.PropertiesFactoryBean
;
41 import org
.springframework
.beans
.factory
.support
.AbstractBeanDefinition
;
42 import org
.springframework
.beans
.factory
.support
.RootBeanDefinition
;
44 import eu
.etaxonomy
.cdm
.api
.application
.CdmApplicationUtils
;
45 import eu
.etaxonomy
.cdm
.common
.CdmUtils
;
46 import eu
.etaxonomy
.cdm
.common
.XmlHelp
;
47 import eu
.etaxonomy
.cdm
.database
.types
.IDatabaseType
;
48 import eu
.etaxonomy
.cdm
.model
.name
.NomenclaturalCode
;
52 * class to access an CdmDataSource
54 public class CdmPersistentDataSource
extends CdmDataSourceBase
{
55 private static final Logger logger
= Logger
.getLogger(CdmPersistentDataSource
.class);
57 public static final String DATASOURCE_BEAN_POSTFIX
= "DataSource";
58 public final static String DATASOURCE_FILE_NAME
= "cdm.datasources.xml";
59 public final static String DATASOURCE_PATH
= "/eu/etaxonomy/cdm/";
61 private final static Format format
= Format
.getPrettyFormat();
63 public enum DbProperties
{
76 public String
toString(){
79 return "driverClassName";
96 case NOMENCLATURAL_CODE
:
97 return "nomenclaturalCode";
99 throw new IllegalArgumentException( "Unknown enumeration type" );
106 protected String dataSourceName
;
110 * Returns the default CdmDataSource
111 * @return the default CdmDataSource
113 public final static CdmPersistentDataSource
NewDefaultInstance(){
115 return NewInstance("default");
116 } catch (DataSourceNotFoundException e
) {
117 logger
.error("Default datasource does not exist in config file");
124 * Returns the default CdmDataSource
125 * @return the default CdmDataSource
127 public final static CdmPersistentDataSource
NewLocalHsqlInstance(){
129 return NewInstance("localDefaultHsql");
130 } catch (DataSourceNotFoundException e
) {
131 logger
.error("Local datasource does not exist in config file");
137 * Returns the CdmDataSource named by strDataSource
138 * @param strDataSource
141 public final static CdmPersistentDataSource
NewInstance(String dataSourceName
)
142 throws DataSourceNotFoundException
{
143 if (exists(dataSourceName
)){
144 return new CdmPersistentDataSource(dataSourceName
);
146 throw new DataSourceNotFoundException("Datasource not found: " + dataSourceName
);
151 * Private Constructor. Use NewXXX factory methods for creating a new instance of CdmDataSource!
152 * @param strDataSource
154 private CdmPersistentDataSource(String strDataSource
){
155 dataSourceName
= strDataSource
;
159 * Returns the name of the bean.
162 public String
getName(){
163 return dataSourceName
;
168 * Returns the name of the bean Element in the xml config file.
171 private static String
getBeanName(String name
){
172 return name
== null?
null : name
+ DATASOURCE_BEAN_POSTFIX
;
177 public String
getDatabase() {
178 return getDatabaseProperty(DbProperties
.DATABASE
);
182 public String
getFilePath() {
184 return getDatabaseProperty(DbProperties
.FILEPATH
);
188 public H2Mode
getMode() {
190 return H2Mode
.fromString(getDatabaseProperty(DbProperties
.MODE
));
195 * @see eu.etaxonomy.cdm.database.ICdmDataSource#getNomenclaturalCode()
197 public NomenclaturalCode
getNomenclaturalCode() {
199 return NomenclaturalCode
.fromString(getDatabaseProperty(DbProperties
.NOMENCLATURAL_CODE
));
202 public int getPort() {
203 String port
= CdmUtils
.Nz(getDatabaseProperty(DbProperties
.PORT
));
204 if ("".equals(port
)){
207 //TODO exception if non integer
208 return Integer
.valueOf(port
);
213 public String
getServer() {
214 return getDatabaseProperty(DbProperties
.SERVER
);
218 * Returns the database type of the data source.
219 * @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.
221 public DatabaseTypeEnum
getDatabaseType(){
222 Element bean
= getDatasourceBeanXml(this.dataSourceName
);
226 Element driverProp
= XmlHelp
.getFirstAttributedChild(bean
, "property", "name", "driverClassName");
227 if (driverProp
== null){
228 logger
.warn("Unknown property driverClass");
231 String strDriverClass
= driverProp
.getAttributeValue("value");
232 DatabaseTypeEnum dbType
= DatabaseTypeEnum
.getDatabaseEnumByDriverClass(strDriverClass
);
240 * Returns the database type of the data source.
241 * @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.
243 protected String
getDatabaseProperty(DbProperties property
){
244 Element bean
= getDatasourceBeanXml(this.dataSourceName
);
246 String result
= null;
248 result
= getPropertyValue(bean
, property
.toString());
249 if (result
== null){ //test if property is database, server or port which are included in the url
250 url
= getPropertyValue(bean
, DbProperties
.URL
.toString());
251 DatabaseTypeEnum dbTypeEnum
= getDatabaseType();
252 if (dbTypeEnum
!= null){
253 IDatabaseType dbType
= dbTypeEnum
.getDatabaseType();
254 if (property
.equals(DbProperties
.DATABASE
)){
255 result
= dbType
.getDatabaseNameByConnectionString(url
);
256 }else if(property
.equals(DbProperties
.SERVER
)){
257 result
= dbType
.getServerNameByConnectionString(url
);
258 }else if(property
.equals(DbProperties
.PORT
)){
259 result
= String
.valueOf(dbType
.getPortByConnectionString(url
));
261 logger
.debug("Unknown property: " + property
);
269 private String
getPropertyValue(Element bean
, String property
){
270 Element driverProp
= XmlHelp
.getFirstAttributedChild(bean
, "property", "name", property
);
271 if (driverProp
== null){
272 logger
.debug("Unknown property: " + property
);
275 String strProperty
= driverProp
.getAttributeValue("value");
283 * Returns the list of properties that are defined in the datasource
286 @SuppressWarnings("unchecked")
287 public List
<Attribute
> getDatasourceAttributes(){
288 List
<Attribute
> result
= new ArrayList
<Attribute
>();
289 Element bean
= getDatasourceBeanXml(this.dataSourceName
);
293 result
= bean
.getAttributes();
299 * Returns a defined property of the datasource
300 * @return the property of the data source. NULL if the datasource bean or the property does not exist.
302 public String
getDatasourceProperty(DbProperties dbProp
){
303 Element bean
= getDatasourceBeanXml(this.dataSourceName
);
307 Element elProperty
= XmlHelp
.getFirstAttributedChild(bean
, "property", "name", dbProp
.toString());
308 if (elProperty
== null){
309 logger
.warn("Unknown property: " + dbProp
.toString());
312 String strValue
= elProperty
.getAttributeValue("value");
320 * Returns the list of properties that are defined in the datasource
323 public Properties
getDatasourceProperties(){
324 Properties result
= new Properties();
325 Element bean
= getDatasourceBeanXml(this.dataSourceName
);
329 List
<Element
> elProperties
= XmlHelp
.getAttributedChildList(bean
, "property", "name");
330 Iterator
<Element
> iterator
= elProperties
.iterator();
331 while(iterator
.hasNext()){
332 Element next
= iterator
.next();
333 String strName
= next
.getAttributeValue("name");
334 String strValue
= next
.getAttributeValue("value");
335 result
.put(strName
, strValue
);
342 * Returns a BeanDefinition object of type DataSource that contains
343 * datsource properties (url, username, password, ...)
346 @SuppressWarnings("unchecked")
347 public BeanDefinition
getDatasourceBean(){
348 DatabaseTypeEnum dbtype
= DatabaseTypeEnum
.getDatabaseEnumByDriverClass(getDatasourceProperty(DbProperties
.DRIVER_CLASS
));
350 AbstractBeanDefinition bd
= new RootBeanDefinition(dbtype
.getDataSourceClass());
352 Iterator
<Attribute
> iterator
= getDatasourceAttributes().iterator();
353 while(iterator
.hasNext()){
354 Attribute attribute
= iterator
.next();
355 if (attribute
.getName().equals("lazy-init")){
356 bd
.setLazyInit(Boolean
.valueOf(attribute
.getValue()));
358 if (attribute
.getName().equals("init-method")){
359 bd
.setInitMethodName(attribute
.getValue());
361 if (attribute
.getName().equals("destroy-method")){
362 bd
.setDestroyMethodName(attribute
.getValue());
364 //Attribute attribute = iterator.next();
365 //bd.setAttribute(attribute.getName(), attribute.getValue());
369 MutablePropertyValues props
= new MutablePropertyValues();
370 Properties persistentProperties
= getDatasourceProperties();
371 Enumeration
<String
> keys
= (Enumeration
)persistentProperties
.keys();
372 while (keys
.hasMoreElements()){
373 String key
= (String
)keys
.nextElement();
374 props
.addPropertyValue(key
, persistentProperties
.getProperty(key
));
377 bd
.setPropertyValues(props
);
386 public BeanDefinition
getHibernatePropertiesBean(DbSchemaValidation hbm2dll
){
387 boolean showSql
= false;
388 boolean formatSql
= false;
389 boolean registerSearchListener
= false;
390 Class
<?
extends CacheProvider
> cacheProviderClass
= NoCacheProvider
.class;
391 return getHibernatePropertiesBean(hbm2dll
, showSql
, formatSql
, registerSearchListener
, cacheProviderClass
);
400 public BeanDefinition
getHibernatePropertiesBean(DbSchemaValidation hbm2dll
, Boolean showSql
, Boolean formatSql
, Boolean registerSearchListener
, Class
<?
extends CacheProvider
> cacheProviderClass
){
401 //Hibernate default values
402 if (hbm2dll
== null){
403 hbm2dll
= DbSchemaValidation
.VALIDATE
;
405 if (showSql
== null){
408 if (formatSql
== null){
411 if (cacheProviderClass
== null){
412 cacheProviderClass
= NoCacheProvider
.class;
414 if(registerSearchListener
== null){
415 registerSearchListener
= false;
418 DatabaseTypeEnum dbtype
= getDatabaseType();
419 AbstractBeanDefinition bd
= new RootBeanDefinition(PropertiesFactoryBean
.class);
420 MutablePropertyValues hibernateProps
= new MutablePropertyValues();
422 Properties props
= new Properties();
423 props
.setProperty("hibernate.hbm2ddl.auto", hbm2dll
.toString());
424 props
.setProperty("hibernate.dialect", dbtype
.getHibernateDialect());
425 props
.setProperty("hibernate.cache.provider_class", cacheProviderClass
.getName());
426 props
.setProperty("hibernate.show_sql", String
.valueOf(showSql
));
427 props
.setProperty("hibernate.format_sql", String
.valueOf(formatSql
));
428 props
.setProperty("hibernate.search.autoregister_listeners", String
.valueOf(registerSearchListener
));
430 hibernateProps
.addPropertyValue("properties",props
);
431 bd
.setPropertyValues(hibernateProps
);
437 * Tests existing of the datsource in the according config file.
438 * @return true if a datasource with the given name exists in the according datasource config file.
440 public static boolean exists(String strDataSourceName
){
441 Element bean
= getDatasourceBeanXml(strDataSourceName
);
442 return (bean
!= null);
448 * @param strDataSourceName
449 * @param databaseTypeEnum
455 * @param dataSourceClass
457 * @param destroyMethod
464 private static CdmPersistentDataSource
save(String strDataSourceName
,
465 DatabaseTypeEnum databaseTypeEnum
,
471 Class
<?
extends DataSource
> dataSourceClass
,
473 String destroyMethod
,
478 NomenclaturalCode code
481 int portNumber
= "".equals(port
) ? databaseTypeEnum
.getDefaultPort() : Integer
.valueOf(port
);
483 ICdmDataSource dataSource
= new CdmDataSource(databaseTypeEnum
, server
, database
, portNumber
, username
, password
, filePath
, mode
, code
);
486 Element root
= getBeansRoot(getDataSourceInputStream());
491 Element bean
= XmlHelp
.getFirstAttributedChild(root
, "bean", "id", getBeanName(strDataSourceName
));
493 bean
.detach(); //delete old version if necessary
495 bean
= insertXmlBean(root
, getBeanName(strDataSourceName
), dataSourceClass
.getName());
497 bean
.setAttribute("lazy-init", "true");
498 if (initMethod
!= null) {bean
.setAttribute("init-method", initMethod
);}
499 if (destroyMethod
!= null) {bean
.setAttribute("destroy-method", destroyMethod
);}
502 insertXmlValueProperty(bean
, "driverClassName", databaseTypeEnum
.getDriverClassName());
504 insertXmlValueProperty(bean
, "url", databaseTypeEnum
.getConnectionString(dataSource
));
505 if (username
!= null) {insertXmlValueProperty(bean
, "username", username
);}
506 if (password
!= null) {insertXmlValueProperty(bean
, "password", password
);}
507 if (startSilent
!= null) {insertXmlValueProperty(bean
, "startSilent", startSilent
.toString() );}
508 if (startServer
!= null) {insertXmlValueProperty(bean
, "startServer", startServer
.toString() );}
509 if (filePath
!= null) {insertXmlValueProperty(bean
, "filePath", filePath
);}
510 if (mode
!= null) {insertXmlValueProperty(bean
, "mode", mode
.toString() );}
511 if (code
!= null) {insertXmlValueProperty(bean
, "nomenclaturalCode", code
.name());}
514 saveToXml(root
.getDocument(), getResourceDirectory(), DATASOURCE_FILE_NAME
, format
);
516 return NewInstance(strDataSourceName
) ;
517 } catch (DataSourceNotFoundException e
) {
518 logger
.error("Error when saving datasource");
524 * @param strDataSourceName
528 * the updated dataSource, null if not succesful
530 public static CdmPersistentDataSource
update(String strDataSourceName
,
531 ICdmDataSource dataSource
) throws DataSourceNotFoundException
, IllegalArgumentException
{
532 delete(CdmPersistentDataSource
.NewInstance(strDataSourceName
));
533 return save(strDataSourceName
, dataSource
);
537 * Saves a datasource to the datasource config file. If strDataSourceName differs a new dataSource
538 * will be created in config file. Use update() of real update functionality.
540 * @param strDataSourceName
544 public static CdmPersistentDataSource
save(String strDataSourceName
,
545 ICdmDataSource dataSource
) throws IllegalArgumentException
{
547 if(dataSource
.getDatabaseType() == null){
548 new IllegalArgumentException("Database type not specified");
551 if(dataSource
.getDatabaseType().equals(DatabaseTypeEnum
.H2
)){
552 Class
<?
extends DataSource
> dataSourceClass
= LocalH2
.class;
553 if(dataSource
.getMode() == null){
554 new IllegalArgumentException("H2 mode not specified");
558 dataSource
.getDatabaseType(),
560 getCheckedDataSourceParameter(dataSource
.getDatabase()),
561 dataSource
.getDatabaseType().getDefaultPort() + "",
562 getCheckedDataSourceParameter(dataSource
.getUsername()),
563 getCheckedDataSourceParameter(dataSource
.getPassword()),
565 null, null, null, null,
566 getCheckedDataSourceParameter(dataSource
.getFilePath()),
567 dataSource
.getMode(),
568 dataSource
.getNomenclaturalCode());
570 Class
<?
extends DataSource
> dataSourceClass
= BasicDataSource
.class; //TODO make configurable
574 dataSource
.getDatabaseType(),
575 getCheckedDataSourceParameter(dataSource
.getServer()),
576 getCheckedDataSourceParameter(dataSource
.getDatabase()),
577 dataSource
.getPort() + "",
578 getCheckedDataSourceParameter(dataSource
.getUsername()),
579 getCheckedDataSourceParameter(dataSource
.getPassword()),
581 null, null, null, null, null, null,
582 dataSource
.getNomenclaturalCode());
586 private static String
getCheckedDataSourceParameter(String parameter
) throws IllegalArgumentException
{
587 if(parameter
!= null){
590 new IllegalArgumentException("Non obsolete paramater was assigned a null value: " + parameter
);
596 * Deletes a dataSource
599 public static void delete (CdmPersistentDataSource dataSource
){
600 Element bean
= getDatasourceBeanXml(dataSource
.getName());
602 Document doc
= bean
.getDocument();
604 saveToXml(doc
, getDataSourceOutputStream(), format
);
610 * Returns a list of all datasources stored in the datasource config file
611 * @return all existing data sources
613 @SuppressWarnings("unchecked")
614 static public List
<CdmPersistentDataSource
> getAllDataSources(){
615 List
<CdmPersistentDataSource
> dataSources
= new ArrayList
<CdmPersistentDataSource
>();
617 Element root
= getBeansRoot(getDataSourceInputStream());
621 List
<Element
> lsChildren
= root
.getChildren("bean", root
.getNamespace());
623 for (Element elBean
: lsChildren
){
624 String strId
= elBean
.getAttributeValue("id");
625 if (strId
!= null && strId
.endsWith(DATASOURCE_BEAN_POSTFIX
)){
626 strId
= strId
.replace(DATASOURCE_BEAN_POSTFIX
, "");
627 dataSources
.add(new CdmPersistentDataSource(strId
));
634 public String
getUsername(){
635 return getDatasourceProperty(DbProperties
.USERNAME
);
638 public String
getPassword(){
639 return getDatasourceProperty(DbProperties
.PASSWORD
);
644 * @see java.lang.Object#toString()
646 public String
toString(){
647 if (this.dataSourceName
!= null){
648 return dataSourceName
;
657 * Returns the datasource config file input stream.
658 * @return data source config file input stream
660 static protected FileInputStream
getDataSourceInputStream(){
661 String dir
= getResourceDirectory();
662 File file
= new File(dir
+ File
.separator
+ DATASOURCE_FILE_NAME
);
663 return fileInputStream(file
);
668 * Returns the datasource config file outputStream.
669 * @return data source config file outputStream
671 static protected FileOutputStream
getDataSourceOutputStream(){
672 String dir
= getResourceDirectory();
673 File file
= new File(dir
+ File
.separator
+ DATASOURCE_FILE_NAME
);
674 return fileOutputStream(file
);
678 * Returns the jdom Element representing the data source bean in the config file.
681 private static Element
getDatasourceBeanXml(String strDataSourceName
){
682 FileInputStream inStream
= getDataSourceInputStream();
683 Element root
= getBeansRoot(inStream
);
687 Element xmlBean
= XmlHelp
.getFirstAttributedChild(root
, "bean", "id", getBeanName(strDataSourceName
));
688 if (xmlBean
== null){
690 logger
.debug("Unknown Element 'bean id=" +strDataSourceName
+ "' ");
696 // returns the directory containing the resources
697 private static String
getResourceDirectory(){
699 File f
= CdmApplicationUtils
.getWritableResourceDir();
701 } catch (IOException e
) {
703 throw new RuntimeException(e
);
707 static private FileInputStream
fileInputStream(File file
){
709 FileInputStream fis
= new FileInputStream(file
);
711 } catch (FileNotFoundException e
) {
712 logger
.warn("File " + file
== null?
"null":file
.getAbsolutePath() + " does not exist in the file system");
717 static private FileOutputStream
fileOutputStream(File file
){
719 FileOutputStream fos
= new FileOutputStream(file
);
721 } catch (FileNotFoundException e
) {
722 logger
.warn("File " + (file
== null?
"null":file
.getAbsolutePath()) + " does not exist in the file system");
727 public boolean equals(Object obj
){
730 }else if (! CdmPersistentDataSource
.class.isAssignableFrom(obj
.getClass())){
733 CdmPersistentDataSource dataSource
= (CdmPersistentDataSource
)obj
;
734 return (this.dataSourceName
== dataSource
.dataSourceName
);