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
.log4j
.Logger
;
31 import org
.hibernate
.cache
.internal
.NoCachingRegionFactory
;
32 import org
.hibernate
.cache
.spi
.RegionFactory
;
33 import org
.jdom
.Attribute
;
34 import org
.jdom
.Document
;
35 import org
.jdom
.Element
;
36 import org
.jdom
.output
.Format
;
37 import org
.springframework
.beans
.MutablePropertyValues
;
38 import org
.springframework
.beans
.factory
.config
.BeanDefinition
;
39 import org
.springframework
.beans
.factory
.config
.PropertiesFactoryBean
;
40 import org
.springframework
.beans
.factory
.support
.AbstractBeanDefinition
;
41 import org
.springframework
.beans
.factory
.support
.RootBeanDefinition
;
43 import com
.mchange
.v2
.c3p0
.ComboPooledDataSource
;
45 import eu
.etaxonomy
.cdm
.api
.application
.CdmApplicationUtils
;
46 import eu
.etaxonomy
.cdm
.common
.CdmUtils
;
47 import eu
.etaxonomy
.cdm
.common
.XmlHelp
;
48 import eu
.etaxonomy
.cdm
.database
.types
.IDatabaseType
;
49 import eu
.etaxonomy
.cdm
.model
.name
.NomenclaturalCode
;
53 * class to access an CdmDataSource
55 public class CdmPersistentDataSource
extends CdmDataSourceBase
{
56 private static final Logger logger
= Logger
.getLogger(CdmPersistentDataSource
.class);
58 public static final String DATASOURCE_BEAN_POSTFIX
= "DataSource";
59 public final static String DATASOURCE_FILE_NAME
= "cdm.datasources.xml";
60 public final static String DATASOURCE_PATH
= "/eu/etaxonomy/cdm/";
62 private final static Format format
= Format
.getPrettyFormat();
64 public enum DbProperties
{
77 public String
toString(){
80 return "driverClassName";
97 case NOMENCLATURAL_CODE
:
98 return "nomenclaturalCode";
100 throw new IllegalArgumentException( "Unknown enumeration type" );
106 * The Datasource class that Spring will use to set up the connection to the database
108 private static String dataSourceClassName
= ComboPooledDataSource
.class.getName();
109 // we used dbcps BasicDataSource before
110 // private static String dataSourceClassName = BasicDataSource.class.getName();
113 protected String dataSourceName
;
117 * Returns the default CdmDataSource
118 * @return the default CdmDataSource
119 * @throws DataSourceNotFoundException
121 public final static CdmPersistentDataSource
NewDefaultInstance() throws DataSourceNotFoundException
{
122 return NewInstance("default");
127 * Returns the default CdmDataSource
128 * @return the default CdmDataSource
129 * @throws DataSourceNotFoundException
131 public final static CdmPersistentDataSource
NewLocalHsqlInstance() throws DataSourceNotFoundException
{
132 return NewInstance("localDefaultHsql");
136 * Returns the CdmDataSource named by strDataSource
137 * @param strDataSource
140 public final static CdmPersistentDataSource
NewInstance(String dataSourceName
) throws DataSourceNotFoundException
{
141 if (exists(dataSourceName
)){
142 return new CdmPersistentDataSource(dataSourceName
);
144 throw new DataSourceNotFoundException("Datasource not found: " + dataSourceName
);
149 * Private Constructor. Use NewXXX factory methods for creating a new instance of CdmDataSource!
150 * @param strDataSource
152 private CdmPersistentDataSource(String strDataSource
){
153 dataSourceName
= strDataSource
;
157 * Returns the name of the bean.
160 public String
getName(){
161 return dataSourceName
;
166 * Returns the name of the bean Element in the xml config file.
169 private static String
getBeanName(String name
){
170 return name
== null?
null : name
+ DATASOURCE_BEAN_POSTFIX
;
175 public String
getDatabase() {
176 return getDatabaseProperty(DbProperties
.DATABASE
);
180 public String
getFilePath() {
182 return getDatabaseProperty(DbProperties
.FILEPATH
);
186 public H2Mode
getMode() {
188 return H2Mode
.fromString(getDatabaseProperty(DbProperties
.MODE
));
193 * @see eu.etaxonomy.cdm.database.ICdmDataSource#getNomenclaturalCode()
195 public NomenclaturalCode
getNomenclaturalCode() {
197 return NomenclaturalCode
.fromString(getDatabaseProperty(DbProperties
.NOMENCLATURAL_CODE
));
200 public int getPort() {
201 String port
= CdmUtils
.Nz(getDatabaseProperty(DbProperties
.PORT
));
202 if ("".equals(port
)){
205 //TODO exception if non integer
206 return Integer
.valueOf(port
);
211 public String
getServer() {
212 return getDatabaseProperty(DbProperties
.SERVER
);
216 * Returns the database type of the data source.
217 * @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.
219 public DatabaseTypeEnum
getDatabaseType(){
220 Element bean
= getDatasourceBeanXml(this.dataSourceName
);
224 Element driverProp
= XmlHelp
.getFirstAttributedChild(bean
, "property", "name", "driverClassName");
225 if (driverProp
== null){
226 logger
.warn("Unknown property driverClass");
229 String strDriverClass
= driverProp
.getAttributeValue("value");
230 DatabaseTypeEnum dbType
= DatabaseTypeEnum
.getDatabaseEnumByDriverClass(strDriverClass
);
238 * Returns the database type of the data source.
239 * @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.
241 protected String
getDatabaseProperty(DbProperties property
){
242 Element bean
= getDatasourceBeanXml(this.dataSourceName
);
244 String result
= null;
246 result
= getPropertyValue(bean
, property
.toString());
247 if (result
== null){ //test if property is database, server or port which are included in the url
248 url
= getPropertyValue(bean
, DbProperties
.URL
.toString());
249 DatabaseTypeEnum dbTypeEnum
= getDatabaseType();
250 if (dbTypeEnum
!= null){
251 IDatabaseType dbType
= dbTypeEnum
.getDatabaseType();
252 if (property
.equals(DbProperties
.DATABASE
)){
253 result
= dbType
.getDatabaseNameByConnectionString(url
);
254 }else if(property
.equals(DbProperties
.SERVER
)){
255 result
= dbType
.getServerNameByConnectionString(url
);
256 }else if(property
.equals(DbProperties
.PORT
)){
257 result
= String
.valueOf(dbType
.getPortByConnectionString(url
));
259 logger
.debug("Unknown property: " + property
);
267 private String
getPropertyValue(Element bean
, String property
){
268 Element driverProp
= XmlHelp
.getFirstAttributedChild(bean
, "property", "name", property
);
269 if (driverProp
== null){
270 logger
.debug("Unknown property: " + property
);
273 String strProperty
= driverProp
.getAttributeValue("value");
281 * Returns the list of properties that are defined in the datasource
284 @SuppressWarnings("unchecked")
285 public List
<Attribute
> getDatasourceAttributes(){
286 List
<Attribute
> result
= new ArrayList
<Attribute
>();
287 Element bean
= getDatasourceBeanXml(this.dataSourceName
);
291 result
= bean
.getAttributes();
297 * Returns a defined property of the datasource
298 * @return the property of the data source. NULL if the datasource bean or the property does not exist.
300 public String
getDatasourceProperty(DbProperties dbProp
){
301 Element bean
= getDatasourceBeanXml(this.dataSourceName
);
305 Element elProperty
= XmlHelp
.getFirstAttributedChild(bean
, "property", "name", dbProp
.toString());
306 if (elProperty
== null){
307 logger
.warn("Unknown property: " + dbProp
.toString());
310 String strValue
= elProperty
.getAttributeValue("value");
318 * Returns the list of properties that are defined in the datasource
321 public Properties
getDatasourceProperties(){
322 Properties result
= new Properties();
323 Element bean
= getDatasourceBeanXml(this.dataSourceName
);
327 List
<Element
> elProperties
= XmlHelp
.getAttributedChildList(bean
, "property", "name");
328 Iterator
<Element
> iterator
= elProperties
.iterator();
329 while(iterator
.hasNext()){
330 Element next
= iterator
.next();
331 String strName
= next
.getAttributeValue("name");
332 String strValue
= next
.getAttributeValue("value");
333 result
.put(strName
, strValue
);
340 * Returns a BeanDefinition object of type DataSource that contains
341 * datsource properties (url, username, password, ...)
344 @SuppressWarnings("unchecked")
345 public BeanDefinition
getDatasourceBean(){
346 DatabaseTypeEnum dbtype
= DatabaseTypeEnum
.getDatabaseEnumByDriverClass(getDatasourceProperty(DbProperties
.DRIVER_CLASS
));
348 AbstractBeanDefinition bd
= new RootBeanDefinition(dbtype
.getDataSourceClass());
350 Iterator
<Attribute
> iterator
= getDatasourceAttributes().iterator();
351 while(iterator
.hasNext()){
352 Attribute attribute
= iterator
.next();
353 if (attribute
.getName().equals("lazy-init")){
354 bd
.setLazyInit(Boolean
.valueOf(attribute
.getValue()));
356 if (attribute
.getName().equals("init-method")){
357 bd
.setInitMethodName(attribute
.getValue());
359 if (attribute
.getName().equals("destroy-method")){
360 bd
.setDestroyMethodName(attribute
.getValue());
362 //Attribute attribute = iterator.next();
363 //bd.setAttribute(attribute.getName(), attribute.getValue());
367 MutablePropertyValues props
= new MutablePropertyValues();
368 Properties persistentProperties
= getDatasourceProperties();
369 Enumeration
<String
> keys
= (Enumeration
)persistentProperties
.keys();
370 while (keys
.hasMoreElements()){
371 String key
= (String
)keys
.nextElement();
373 if (key
.equals("nomenclaturalCode") && persistentProperties
.getProperty(key
).equals("ICBN")){
374 //bugfix for old nomenclatural codes, remove if fixed elsewhere, see https://dev.e-taxonomy.eu/trac/ticket/3658
375 props
.addPropertyValue(key
, NomenclaturalCode
.ICNAFP
.name());
377 props
.addPropertyValue(key
, persistentProperties
.getProperty(key
));
381 bd
.setPropertyValues(props
);
390 public BeanDefinition
getHibernatePropertiesBean(DbSchemaValidation hbm2dll
){
391 boolean showSql
= false;
392 boolean formatSql
= false;
393 boolean registerSearchListener
= false;
394 Class
<?
extends RegionFactory
> cacheProviderClass
= NoCachingRegionFactory
.class;
395 return getHibernatePropertiesBean(hbm2dll
, showSql
, formatSql
, registerSearchListener
, cacheProviderClass
);
404 public BeanDefinition
getHibernatePropertiesBean(DbSchemaValidation hbm2dll
, Boolean showSql
, Boolean formatSql
, Boolean registerSearchListener
, Class
<?
extends RegionFactory
> cacheProviderClass
){
405 //Hibernate default values
406 if (hbm2dll
== null){
407 hbm2dll
= DbSchemaValidation
.VALIDATE
;
409 if (showSql
== null){
412 if (formatSql
== null){
415 if (cacheProviderClass
== null){
416 cacheProviderClass
= NoCachingRegionFactory
.class;
418 if(registerSearchListener
== null){
419 registerSearchListener
= false;
422 DatabaseTypeEnum dbtype
= getDatabaseType();
423 AbstractBeanDefinition bd
= new RootBeanDefinition(PropertiesFactoryBean
.class);
424 MutablePropertyValues hibernateProps
= new MutablePropertyValues();
426 Properties props
= new Properties();
427 props
.setProperty("hibernate.hbm2ddl.auto", hbm2dll
.toString());
428 props
.setProperty("hibernate.dialect", dbtype
.getHibernateDialectCanonicalName());
429 props
.setProperty("hibernate.cache.region.factory_class", cacheProviderClass
.getName());
430 props
.setProperty("hibernate.show_sql", String
.valueOf(showSql
));
431 props
.setProperty("hibernate.format_sql", String
.valueOf(formatSql
));
432 props
.setProperty("hibernate.search.autoregister_listeners", String
.valueOf(registerSearchListener
));
434 hibernateProps
.addPropertyValue("properties",props
);
435 bd
.setPropertyValues(hibernateProps
);
441 * Tests existing of the datsource in the according config file.
442 * @return true if a datasource with the given name exists in the according datasource config file.
444 public static boolean exists(String strDataSourceName
){
445 Element bean
= getDatasourceBeanXml(strDataSourceName
);
446 return (bean
!= null);
452 * @param strDataSourceName
453 * @param databaseTypeEnum
459 * @param dataSourceClass
461 * @param destroyMethod
468 private static CdmPersistentDataSource
save(String strDataSourceName
,
469 DatabaseTypeEnum databaseTypeEnum
,
475 Class
<?
extends DataSource
> dataSourceClass
,
477 String destroyMethod
,
482 NomenclaturalCode code
485 int portNumber
= "".equals(port
) ? databaseTypeEnum
.getDefaultPort() : Integer
.valueOf(port
);
487 ICdmDataSource dataSource
= new CdmDataSource(databaseTypeEnum
, server
, database
, portNumber
, username
, password
, filePath
, mode
, code
);
490 Element root
= getBeansRoot(getDataSourceInputStream());
495 Element bean
= XmlHelp
.getFirstAttributedChild(root
, "bean", "id", getBeanName(strDataSourceName
));
497 bean
.detach(); //delete old version if necessary
499 bean
= insertXmlBean(root
, getBeanName(strDataSourceName
), dataSourceClass
.getName());
501 bean
.setAttribute("lazy-init", "true");
502 if (initMethod
!= null) {bean
.setAttribute("init-method", initMethod
);}
503 if (destroyMethod
!= null) {bean
.setAttribute("destroy-method", destroyMethod
);}
506 insertXmlValueProperty(bean
, "driverClassName", databaseTypeEnum
.getDriverClassName());
508 insertXmlValueProperty(bean
, "url", databaseTypeEnum
.getConnectionString(dataSource
));
509 if (username
!= null) {insertXmlValueProperty(bean
, "username", username
);}
510 if (password
!= null) {insertXmlValueProperty(bean
, "password", password
);}
511 if (startSilent
!= null) {insertXmlValueProperty(bean
, "startSilent", startSilent
.toString() );}
512 if (startServer
!= null) {insertXmlValueProperty(bean
, "startServer", startServer
.toString() );}
513 if (filePath
!= null) {insertXmlValueProperty(bean
, "filePath", filePath
);}
514 if (mode
!= null) {insertXmlValueProperty(bean
, "mode", mode
.toString() );}
515 if (code
!= null) {insertXmlValueProperty(bean
, "nomenclaturalCode", code
.name());}
518 saveToXml(root
.getDocument(), getResourceDirectory(), DATASOURCE_FILE_NAME
, format
);
520 return NewInstance(strDataSourceName
) ;
521 } catch (DataSourceNotFoundException e
) {
522 logger
.error("Error when saving datasource");
528 * @param strDataSourceName
532 * the updated dataSource, null if not succesful
534 public static CdmPersistentDataSource
update(String strDataSourceName
,
535 ICdmDataSource dataSource
) throws DataSourceNotFoundException
, IllegalArgumentException
{
536 delete(CdmPersistentDataSource
.NewInstance(strDataSourceName
));
537 return save(strDataSourceName
, dataSource
);
541 * Saves a datasource to the datasource config file. If strDataSourceName differs a new dataSource
542 * will be created in config file. Use update() of real update functionality.
544 * @param strDataSourceName
548 public static CdmPersistentDataSource
save(String strDataSourceName
,
549 ICdmDataSource dataSource
) throws IllegalArgumentException
{
551 if(dataSource
.getDatabaseType() == null){
552 new IllegalArgumentException("Database type not specified");
555 if(dataSource
.getDatabaseType().equals(DatabaseTypeEnum
.H2
)){
556 Class
<?
extends DataSource
> dataSourceClass
= LocalH2
.class;
557 if(dataSource
.getMode() == null){
558 new IllegalArgumentException("H2 mode not specified");
562 dataSource
.getDatabaseType(),
564 getCheckedDataSourceParameter(dataSource
.getDatabase()),
565 dataSource
.getDatabaseType().getDefaultPort() + "",
566 getCheckedDataSourceParameter(dataSource
.getUsername()),
567 getCheckedDataSourceParameter(dataSource
.getPassword()),
569 null, null, null, null,
570 getCheckedDataSourceParameter(dataSource
.getFilePath()),
571 dataSource
.getMode(),
572 dataSource
.getNomenclaturalCode());
575 Class
<?
extends DataSource
> dataSourceClass
;
577 dataSourceClass
= (Class
<?
extends DataSource
>) Class
.forName(dataSourceClassName
);
579 CdmPersistentDataSource persistendDatasource
= save(
581 dataSource
.getDatabaseType(),
582 getCheckedDataSourceParameter(dataSource
.getServer()),
583 getCheckedDataSourceParameter(dataSource
.getDatabase()),
584 dataSource
.getPort() + "",
585 getCheckedDataSourceParameter(dataSource
.getUsername()),
586 getCheckedDataSourceParameter(dataSource
.getPassword()),
588 null, null, null, null, null, null,
589 dataSource
.getNomenclaturalCode());
591 return persistendDatasource
;
592 } catch (ClassNotFoundException e
) {
593 logger
.error("DataSourceClass not found - stopping application", e
);
596 // will never be reached
601 private static String
getCheckedDataSourceParameter(String parameter
) throws IllegalArgumentException
{
602 if(parameter
!= null){
605 new IllegalArgumentException("Non obsolete paramater was assigned a null value: " + parameter
);
611 * Deletes a dataSource
614 public static void delete (CdmPersistentDataSource dataSource
){
615 Element bean
= getDatasourceBeanXml(dataSource
.getName());
617 Document doc
= bean
.getDocument();
619 saveToXml(doc
, getDataSourceOutputStream(), format
);
625 * Returns a list of all datasources stored in the datasource config file
626 * @return all existing data sources
628 @SuppressWarnings("unchecked")
629 static public List
<CdmPersistentDataSource
> getAllDataSources(){
630 List
<CdmPersistentDataSource
> dataSources
= new ArrayList
<CdmPersistentDataSource
>();
632 Element root
= getBeansRoot(getDataSourceInputStream());
636 List
<Element
> lsChildren
= root
.getChildren("bean", root
.getNamespace());
638 for (Element elBean
: lsChildren
){
639 String strId
= elBean
.getAttributeValue("id");
640 if (strId
!= null && strId
.endsWith(DATASOURCE_BEAN_POSTFIX
)){
641 strId
= strId
.replace(DATASOURCE_BEAN_POSTFIX
, "");
642 dataSources
.add(new CdmPersistentDataSource(strId
));
649 public String
getUsername(){
650 return getDatasourceProperty(DbProperties
.USERNAME
);
653 public String
getPassword(){
654 return getDatasourceProperty(DbProperties
.PASSWORD
);
659 * @see java.lang.Object#toString()
661 public String
toString(){
662 if (this.dataSourceName
!= null){
663 return dataSourceName
;
672 * Returns the datasource config file input stream.
673 * @return data source config file input stream
675 static protected FileInputStream
getDataSourceInputStream(){
676 String dir
= getResourceDirectory();
677 File file
= new File(dir
+ File
.separator
+ DATASOURCE_FILE_NAME
);
678 return fileInputStream(file
);
683 * Returns the datasource config file outputStream.
684 * @return data source config file outputStream
686 static protected FileOutputStream
getDataSourceOutputStream(){
687 String dir
= getResourceDirectory();
688 File file
= new File(dir
+ File
.separator
+ DATASOURCE_FILE_NAME
);
689 return fileOutputStream(file
);
693 * Returns the jdom Element representing the data source bean in the config file.
696 private static Element
getDatasourceBeanXml(String strDataSourceName
){
697 FileInputStream inStream
= getDataSourceInputStream();
698 Element root
= getBeansRoot(inStream
);
702 Element xmlBean
= XmlHelp
.getFirstAttributedChild(root
, "bean", "id", getBeanName(strDataSourceName
));
703 if (xmlBean
== null){
705 logger
.debug("Unknown Element 'bean id=" +strDataSourceName
+ "' ");
711 // returns the directory containing the resources
712 private static String
getResourceDirectory(){
714 File f
= CdmApplicationUtils
.getWritableResourceDir();
716 } catch (IOException e
) {
718 throw new RuntimeException(e
);
722 static private FileInputStream
fileInputStream(File file
){
724 FileInputStream fis
= new FileInputStream(file
);
726 } catch (FileNotFoundException e
) {
727 logger
.warn("File " + file
== null?
"null":file
.getAbsolutePath() + " does not exist in the file system");
732 static private FileOutputStream
fileOutputStream(File file
){
734 FileOutputStream fos
= new FileOutputStream(file
);
736 } catch (FileNotFoundException e
) {
737 logger
.warn("File " + (file
== null?
"null":file
.getAbsolutePath()) + " does not exist in the file system");
742 public boolean equals(Object obj
){
745 }else if (! CdmPersistentDataSource
.class.isAssignableFrom(obj
.getClass())){
748 CdmPersistentDataSource dataSource
= (CdmPersistentDataSource
)obj
;
749 return (this.dataSourceName
== dataSource
.dataSourceName
);