2 * Copyright (C) 2009 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
.opt
.config
;
13 import java
.lang
.reflect
.InvocationTargetException
;
14 import java
.lang
.reflect
.Method
;
15 import java
.sql
.Connection
;
16 import java
.sql
.ResultSet
;
17 import java
.sql
.SQLException
;
18 import java
.util
.Properties
;
20 import javax
.naming
.NamingException
;
21 import javax
.sql
.DataSource
;
23 import org
.apache
.commons
.io
.FilenameUtils
;
24 import org
.apache
.log4j
.Logger
;
25 import org
.hibernate
.dialect
.H2CorrectedDialect
;
26 import org
.hibernate
.dialect
.MySQL5MyISAMUtf8Dialect
;
27 import org
.hibernate
.dialect
.PostgreSQL82Dialect
;
28 import org
.springframework
.beans
.BeansException
;
29 import org
.springframework
.beans
.factory
.xml
.XmlBeanFactory
;
30 import org
.springframework
.context
.annotation
.Bean
;
31 import org
.springframework
.context
.annotation
.Configuration
;
32 import org
.springframework
.core
.io
.FileSystemResource
;
33 import org
.springframework
.jndi
.JndiObjectFactoryBean
;
35 import com
.mchange
.v2
.c3p0
.ComboPooledDataSource
;
37 import eu
.etaxonomy
.cdm
.common
.CdmUtils
;
38 import eu
.etaxonomy
.cdm
.database
.WrappedCdmDataSource
;
39 import eu
.etaxonomy
.cdm
.database
.update
.CdmUpdater
;
40 import eu
.etaxonomy
.cdm
.model
.metadata
.CdmMetaData
;
41 import eu
.etaxonomy
.cdm
.model
.metadata
.CdmMetaData
.MetaDataPropertyName
;
42 import eu
.etaxonomy
.cdm
.remote
.config
.AbstractWebApplicationConfigurer
;
45 * The <code>DataSourceConfigurer</code> can be used as a replacement for a xml configuration in the application context.
46 * Enter the following in your application context configuration in order to enable the <code>DataSourceConfigurer</code>:
49 <!-- enable processing of annotations such as @Autowired and @Configuration -->
50 <context:annotation-config/>
52 <bean class="eu.etaxonomy.cdm.remote.config.DataSourceConfigurer" >
55 * The <code>DataSourceConfigurer</code> allows alternative ways to specify a data source:
58 * <li>Specify the data source bean to use in the Java environment properties:
59 * <code>-Dcdm.datasource={dataSourceName}</code> ({@link #ATTRIBUTE_DATASOURCE_NAME}).
60 * The data source bean with the given name will then be loaded from the <code>cdm.beanDefinitionFile</code>
61 * ({@link #CDM_BEAN_DEFINITION_FILE}), which must be a valid Spring bean definition file.
64 * Use a JDBC data source which is bound into the JNDI context. In this case the JNDI name is specified
65 * via the {@link #ATTRIBUTE_JDBC_JNDI_NAME} as attribute to the ServletContext.
66 * This scenario usually being used by the cdm-server application.
69 * The attributes used in (1) and (2) are in a first step being searched in the ServletContext
70 * if not found search in a second step in the environment variables of the OS, see:{@link #findProperty(String, boolean)}.
72 * @author a.kohlbecker
77 public class DataSourceConfigurer
extends AbstractWebApplicationConfigurer
{
79 public static final Logger logger
= Logger
.getLogger(DataSourceConfigurer
.class);
81 protected static final String HIBERNATE_DIALECT
= "hibernate.dialect";
82 protected static final String HIBERNATE_SEARCH_DEFAULT_INDEX_BASE
= "hibernate.search.default.indexBase";
83 protected static final String CDM_BEAN_DEFINITION_FILE
= "cdm.beanDefinitionFile";
86 * Attribute to configure the name of the data source as set as bean name in the datasources.xml.
87 * This name usually is used as the prefix for the webapplication root path.
89 * <b>This is a required attribute!</b>
91 * @see AbstractWebApplicationConfigurer#findProperty(String, boolean)
93 * see also <code>eu.etaxonomy.cdm.server.instance.SharedAttributes</code>
96 protected static final String ATTRIBUTE_DATASOURCE_NAME
= "cdm.datasource";
98 * see also <code>eu.etaxonomy.cdm.server.instance.SharedAttributes</code>
100 public static final String ATTRIBUTE_JDBC_JNDI_NAME
= "cdm.jdbcJndiName";
103 * Force a schema update when the cdmlib-remote-webapp instance is starting up
104 * see also <code>eu.etaxonomy.cdm.server.instance.SharedAttributes.ATTRIBUTE_FORCE_SCHEMA_UPDATE</code>
106 public static final String ATTRIBUTE_FORCE_SCHEMA_UPDATE
= "cdm.forceSchemaUpdate";
108 protected static final String DATASOURCE_BEANDEF_DEFAULT
= CdmUtils
.getCdmHomeDir().getPath() + File
.separator
+ "datasources.xml";
110 protected static String beanDefinitionFile
= DATASOURCE_BEANDEF_DEFAULT
;
113 private String cmdServerInstanceName
= null;
116 * The file to load the {@link DataSource} beans from.
117 * This file is usually {@code ./.cdmLibrary/datasources.xml}
121 public void setBeanDefinitionFile(String filename
){
122 beanDefinitionFile
= filename
;
126 private String dataSourceId
= null;
128 private DataSource dataSource
;
130 private DataSourceProperties dataSourceProperties
;
132 private Properties
getHibernateProperties() {
133 Properties hibernateProperties
= webApplicationContext
.getBean("jndiHibernateProperties", Properties
.class);
134 return hibernateProperties
;
140 public DataSource
dataSource() {
142 String beanName
= findProperty(ATTRIBUTE_DATASOURCE_NAME
, true);
143 String jndiName
= null;
144 if(this.dataSource
== null){
145 jndiName
= findProperty(ATTRIBUTE_JDBC_JNDI_NAME
, false);
147 if(jndiName
!= null){
148 dataSource
= useJndiDataSource(jndiName
);
149 dataSourceId
= FilenameUtils
.getName(jndiName
);
151 dataSource
= loadDataSourceBean(beanName
);
152 dataSourceId
= beanName
;
156 if(dataSource
== null){
160 // validate correct schema version
163 Connection connection
= dataSource
.getConnection();
164 String metadataTableName
= "CdmMetaData";
165 if(inferHibernateDialectName(dataSource
).equals(H2CorrectedDialect
.class.getName())){
166 metadataTableName
= metadataTableName
.toUpperCase();
168 ResultSet tables
= connection
.getMetaData().getTables(connection
.getCatalog(), null, metadataTableName
, null);
170 ResultSet resultSet
= connection
.createStatement().executeQuery(MetaDataPropertyName
.DB_SCHEMA_VERSION
.getSqlQuery());
171 String version
= null;
172 if(resultSet
.next()){
173 version
= resultSet
.getString(1);
175 throw new RuntimeException("Unable to retrieve version info from data source " + dataSource
.toString());
180 if(!CdmMetaData
.isDbSchemaVersionCompatible(version
)){
182 * any exception thrown here would be nested into a spring
183 * BeanException which can not be caught in the servlet
184 * container, so we post the information into the
187 String errorMessage
= "Incompatible version [" + (beanName
!= null ? beanName
: jndiName
) + "] expected version: " + CdmMetaData
.getDbSchemaVersion() + ", data base version " + version
;
188 addErrorMessageToServletContextAttributes(errorMessage
);
191 // throw new RuntimeException("database " + dataSource.toString() + " is empty or not a cdm database");
192 logger
.error("database " + dataSource
.toString() + " is empty or not a cdm database");
196 } catch (SQLException e
) {
197 RuntimeException re
= new RuntimeException("Unable to connect or to retrieve version info from data source " + dataSource
.toString() , e
);
198 addErrorMessageToServletContextAttributes(re
.getMessage());
204 String forceSchemaUpdate
= findProperty(ATTRIBUTE_FORCE_SCHEMA_UPDATE
, false);
205 if(forceSchemaUpdate
!= null){
206 logger
.info("Update of data source requested by property '" + ATTRIBUTE_FORCE_SCHEMA_UPDATE
+ "'");
208 CdmUpdater updater
= CdmUpdater
.NewInstance();
209 WrappedCdmDataSource cdmDataSource
= new WrappedCdmDataSource(dataSource
);
210 updater
.updateToCurrentVersion(cdmDataSource
, null);
217 public DataSourceProperties
dataSourceProperties(){
218 if(this.dataSourceProperties
== null){
219 dataSourceProperties
= loadDataSourceProperties();
220 if(dataSourceId
== null){
223 dataSourceProperties
.setCurrentDataSourceId(dataSourceId
);
225 return dataSourceProperties
;
229 private DataSource
useJndiDataSource(String jndiName
) {
230 logger
.info("using jndi datasource '" + jndiName
+ "'");
232 JndiObjectFactoryBean jndiFactory
= new JndiObjectFactoryBean();
234 JndiTemplate jndiTemplate = new JndiTemplate();
235 jndiFactory.setJndiTemplate(jndiTemplate); no need to use a JndiTemplate
236 if I try using JndiTemplate I get an org.hibernate.AnnotationException: "Unknown Id.generator: system-increment"
237 when running multiple instances via the Bootloader
239 jndiFactory
.setResourceRef(true);
240 jndiFactory
.setJndiName(jndiName
);
242 jndiFactory
.afterPropertiesSet();
243 } catch (IllegalArgumentException e
) {
245 } catch (NamingException e
) {
248 Object obj
= jndiFactory
.getObject();
249 return (DataSource
)obj
;
253 * Loads the {@link DataSource} bean from the cdm bean definition file.
254 * This file is usually {@code ./.cdmLibrary/datasources.xml}
259 private DataSource
loadDataSourceBean(String beanName
) {
261 File f
= new File("./");
262 System
.err
.println(f
.getAbsolutePath());
264 String beanDefinitionFileFromProperty
= findProperty(CDM_BEAN_DEFINITION_FILE
, false);
265 String path
= (beanDefinitionFileFromProperty
!= null ? beanDefinitionFileFromProperty
: beanDefinitionFile
);
266 logger
.info("loading DataSourceBean '" + beanName
+ "' from: " + path
);
267 FileSystemResource file
= new FileSystemResource(path
);
268 XmlBeanFactory beanFactory
= new XmlBeanFactory(file
);
269 DataSource dataSource
= beanFactory
.getBean(beanName
, DataSource
.class);
270 if(dataSource
instanceof ComboPooledDataSource
){
271 logger
.info("DataSourceBean '" + beanName
+ "' is a ComboPooledDataSource [URL:" + ((ComboPooledDataSource
)dataSource
).getJdbcUrl()+ "]");
273 logger
.error("DataSourceBean '" + beanName
+ "' IS NOT a ComboPooledDataSource");
280 * Loads the <code>dataSourceProperties</code> bean from the cdm bean
282 * This file is usually {@code ./.cdmLibrary/datasources.xml}
284 * @return the DataSourceProperties bean or an empty instance if the bean is not found
286 private DataSourceProperties
loadDataSourceProperties() {
288 String beanDefinitionFileFromProperty
= findProperty(CDM_BEAN_DEFINITION_FILE
, false);
289 String path
= (beanDefinitionFileFromProperty
!= null ? beanDefinitionFileFromProperty
: beanDefinitionFile
);
290 logger
.info("loading dataSourceProperties from: " + path
);
291 FileSystemResource file
= new FileSystemResource(path
);
292 XmlBeanFactory beanFactory
= new XmlBeanFactory(file
);
293 DataSourceProperties properties
= null;
295 properties
= beanFactory
.getBean("dataSourceProperties", DataSourceProperties
.class);
296 } catch (BeansException e
) {
297 logger
.warn("bean 'dataSourceProperties' not found");
298 properties
= new DataSourceProperties();
304 public Properties
hibernateProperties(){
305 Properties props
= getHibernateProperties();
306 props
.setProperty(HIBERNATE_DIALECT
, inferHibernateDialectName());
307 props
.setProperty(HIBERNATE_SEARCH_DEFAULT_INDEX_BASE
, CdmUtils
.getCdmHomeSubDir(CdmUtils
.SUBFOLDER_WEBAPP
).getPath() + "/index/".replace("/", File
.separator
) + findProperty(ATTRIBUTE_DATASOURCE_NAME
, true));
308 logger
.debug("hibernateProperties: " + props
.toString());
313 * Returns the full class name of the according {@link org.hibernate.dialect.Dialect} implementation
315 * @param ds the DataSource
318 public String
inferHibernateDialectName() {
319 DataSource ds
= dataSource();
320 return inferHibernateDialectName(ds
);
326 * Returns the full class name of the according {@link org.hibernate.dialect.Dialect} implementation
328 * @param ds the DataSource
331 public String
inferHibernateDialectName(DataSource ds
) {
332 String url
= "<SEE PRIOR REFLECTION ERROR>";
335 m
= ds
.getClass().getMethod("getUrl");
336 } catch (SecurityException e
) {
338 } catch (NoSuchMethodException e
) {
340 m
= ds
.getClass().getMethod("getJdbcUrl");
341 } catch (SecurityException e2
) {
343 } catch (NoSuchMethodException e2
) {
348 url
= (String
)m
.invoke(ds
);
349 } catch (IllegalArgumentException e
) {
351 } catch (IllegalAccessException e
) {
353 } catch (InvocationTargetException e
) {
355 } catch (SecurityException e
) {
360 if(url
.contains(":mysql:")){
361 // TODO we should switch all databases to InnoDB !
362 // TODO open jdbc connection to check engine and choose between
363 // MySQL5MyISAMUtf8Dialect and MySQL5MyISAMUtf8Dialect
364 // see #3371 (switch cdm to MySQL InnoDB)
365 return MySQL5MyISAMUtf8Dialect
.class.getName();
367 if(url
.contains(":h2:")){
368 return H2CorrectedDialect
.class.getName();
370 if(url
.contains(":postgresql:")){
371 return PostgreSQL82Dialect
.class.getName();
375 logger
.error("hibernate dialect mapping for "+url
+ " not yet implemented or unavailable");