3 * Copyright (C) 2009 EDIT
4 * European Distributed Institute of Taxonomy
5 * http://www.e-taxonomy.eu
7 * The contents of this file are subject to the Mozilla Public License Version 1.1
8 * See LICENSE.TXT at the top of this package for the full license terms.
11 package eu
.etaxonomy
.cdm
.remote
.config
;
14 import java
.lang
.reflect
.InvocationTargetException
;
15 import java
.lang
.reflect
.Method
;
16 import java
.sql
.Connection
;
17 import java
.sql
.ResultSet
;
18 import java
.sql
.SQLException
;
19 import java
.util
.Properties
;
21 import javax
.naming
.NamingException
;
22 import javax
.sql
.DataSource
;
24 import org
.apache
.commons
.io
.FilenameUtils
;
25 import org
.apache
.log4j
.Logger
;
26 import org
.hibernate
.dialect
.H2CorrectedDialect
;
27 import org
.hibernate
.dialect
.MySQL5MyISAMUtf8Dialect
;
28 import org
.hibernate
.dialect
.PostgreSQL82Dialect
;
29 import org
.springframework
.beans
.BeansException
;
30 import org
.springframework
.beans
.factory
.xml
.XmlBeanFactory
;
31 import org
.springframework
.context
.annotation
.Bean
;
32 import org
.springframework
.context
.annotation
.Configuration
;
33 import org
.springframework
.core
.io
.FileSystemResource
;
34 import org
.springframework
.jndi
.JndiObjectFactoryBean
;
36 import com
.mchange
.v2
.c3p0
.ComboPooledDataSource
;
38 import eu
.etaxonomy
.cdm
.common
.CdmUtils
;
39 import eu
.etaxonomy
.cdm
.model
.metadata
.CdmMetaData
;
40 import eu
.etaxonomy
.cdm
.model
.metadata
.CdmMetaData
.MetaDataPropertyName
;
43 * The <code>DataSourceConfigurer</code> can be used as a replacement for a xml configuration in the application context.
44 * Enter the following in your application context configuration in order to enable the <code>DataSourceConfigurer</code>:
47 <!-- enable processing of annotations such as @Autowired and @Configuration -->
48 <context:annotation-config/>
50 <bean class="eu.etaxonomy.cdm.remote.config.DataSourceConfigurer" >
53 * The <code>DataSourceConfigurer</code> allows alternative ways to specify a data source:
56 * <li>Specify the data source bean to use in the Java environment properties:
57 * <code>-Dcdm.datasource={dataSourceName}</code> ({@link #ATTRIBUTE_DATASOURCE_NAME}).
58 * The data source bean with the given name will then be loaded from the <code>cdm.beanDefinitionFile</code>
59 * ({@link #CDM_BEAN_DEFINITION_FILE}), which must be a valid Spring bean definition file.
62 * Use a JDBC data source which is bound into the JNDI context. In this case the JNDI name is specified
63 * via the {@link #ATTRIBUTE_JDBC_JNDI_NAME} as attribute to the ServletContext.
64 * This scenario usually being used by the cdm-server application.
67 * The attributes used in (1) and (2) are in a first step being searched in the ServletContext
68 * if not found search in a second step in the environment variables of the OS, see:{@link #findProperty(String, boolean)}.
70 * @author a.kohlbecker
75 public class DataSourceConfigurer
extends AbstractWebApplicationConfigurer
{
77 public static final Logger logger
= Logger
.getLogger(DataSourceConfigurer
.class);
79 protected static final String HIBERNATE_DIALECT
= "hibernate.dialect";
80 protected static final String HIBERNATE_SEARCH_DEFAULT_INDEX_BASE
= "hibernate.search.default.indexBase";
81 protected static final String CDM_BEAN_DEFINITION_FILE
= "cdm.beanDefinitionFile";
84 * Attribute to configure the name of the data source as set as bean name in the datasources.xml.
85 * This name usually is used as the prefix for the webapplication root path.
87 * <b>This is a required attribute!</b>
89 * @see AbstractWebApplicationConfigurer#findProperty(String, boolean)
91 * see also <code>eu.etaxonomy.cdm.server.instance.SharedAttributes</code>
94 protected static final String ATTRIBUTE_DATASOURCE_NAME
= "cdm.datasource";
96 * see also <code>eu.etaxonomy.cdm.server.instance.SharedAttributes</code>
98 public static final String ATTRIBUTE_JDBC_JNDI_NAME
= "cdm.jdbcJndiName";
101 * Force a schema update when the cdmlib-remote-webapp instance is starting up
102 * see also <code>eu.etaxonomy.cdm.server.instance.SharedAttributes.ATTRIBUTE_FORCE_SCHEMA_UPDATE</code>
104 public static final String ATTRIBUTE_FORCE_SCHEMA_UPDATE
= "cdm.forceSchemaUpdate";
106 protected static final String DATASOURCE_BEANDEF_DEFAULT
= CdmUtils
.getCdmHomeDir().getPath() + File
.separator
+ "datasources.xml";
108 protected static String beanDefinitionFile
= DATASOURCE_BEANDEF_DEFAULT
;
111 private String cmdServerInstanceName
= null;
114 * The file to load the {@link DataSource} beans from.
115 * This file is usually {@code ./.cdmLibrary/datasources.xml}
119 public void setBeanDefinitionFile(String filename
){
120 beanDefinitionFile
= filename
;
124 private String dataSourceId
= null;
126 private DataSource dataSource
;
128 private DataSourceProperties dataSourceProperties
;
130 private Properties
getHibernateProperties() {
131 Properties hibernateProperties
= webApplicationContext
.getBean("jndiHibernateProperties", Properties
.class);
132 return hibernateProperties
;
138 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 ResultSet tables
= connection
.getMetaData().getTables(connection
.getCatalog(), null, "CdmMetaData", null);
166 ResultSet resultSet
= connection
.createStatement().executeQuery(MetaDataPropertyName
.DB_SCHEMA_VERSION
.getSqlQuery());
167 String version
= null;
168 if(resultSet
.next()){
169 version
= resultSet
.getString(1);
171 throw new RuntimeException("Unable to retrieve version info from data source " + dataSource
.toString());
176 if(!CdmMetaData
.isDbSchemaVersionCompatible(version
)){
178 * any exception thrown here would be nested into a spring
179 * BeanException which can not be caught in the servlet
180 * container, so we post the information into the
183 String errorMessage
= "Incompatible version [" + (beanName
!= null ? beanName
: jndiName
) + "] expected version: " + CdmMetaData
.getDbSchemaVersion() + ", data base version " + version
;
184 addErrorMessageToServletContextAttributes(errorMessage
);
187 // throw new RuntimeException("database " + dataSource.toString() + " is empty or not a cdm database");
188 logger
.error("database " + dataSource
.toString() + " is empty or not a cdm database");
192 } catch (SQLException e
) {
193 RuntimeException re
= new RuntimeException("Unable to connect or to retrieve version info from data source " + dataSource
.toString() , e
);
194 addErrorMessageToServletContextAttributes(re
.getMessage());
200 String forceSchemaUpdate
= findProperty(ATTRIBUTE_FORCE_SCHEMA_UPDATE
, false);
201 if(forceSchemaUpdate
!= null){
202 logger
.error("Unable to update data source due to #3910 (eu.etaxonomy.cdm.database.ICdmDataSource is not compatible to javax.sql.DataSource)");
204 * DISABLED due to #3910 (eu.etaxonomy.cdm.database.ICdmDataSource is not compatible to javax.sql.DataSource)
205 * CdmUpdater updater = CdmUpdater.NewInstance();
206 String hiernateDialectName = inferHibernateDialectName(dataSource);
207 DatabaseTypeEnum dbType = null;
208 if(hiernateDialectName.equals(MySQL5MyISAMUtf8Dialect.class.getName())) {
209 dbType = DatabaseTypeEnum.MySQL;
211 RuntimeException re = new RuntimeException("Unable to update data source, CdmUpdater.updateToCurrentVersion() cannot use javax.sql.DataSource or javax.sql.DataSource");
213 CdmDataSource cdmDataSource = CdmDataSource.NewInstance(dbType, dataSource.ge, database, username, password)
214 updater.updateToCurrentVersion(dataSource, null);
223 public DataSourceProperties
dataSourceProperties(){
224 if(this.dataSourceProperties
== null){
225 dataSourceProperties
= loadDataSourceProperties();
226 if(dataSourceId
== null){
229 dataSourceProperties
.setCurrentDataSourceId(dataSourceId
);
231 return dataSourceProperties
;
235 private DataSource
useJndiDataSource(String jndiName
) {
236 logger
.info("using jndi datasource '" + jndiName
+ "'");
238 JndiObjectFactoryBean jndiFactory
= new JndiObjectFactoryBean();
240 JndiTemplate jndiTemplate = new JndiTemplate();
241 jndiFactory.setJndiTemplate(jndiTemplate); no need to use a JndiTemplate
242 if I try using JndiTemplate I get an org.hibernate.AnnotationException: "Unknown Id.generator: system-increment"
243 when running multiple instances via the Bootloader
245 jndiFactory
.setResourceRef(true);
246 jndiFactory
.setJndiName(jndiName
);
248 jndiFactory
.afterPropertiesSet();
249 } catch (IllegalArgumentException e
) {
251 } catch (NamingException e
) {
254 Object obj
= jndiFactory
.getObject();
255 return (DataSource
)obj
;
259 * Loads the {@link DataSource} bean from the cdm bean definition file.
260 * This file is usually {@code ./.cdmLibrary/datasources.xml}
265 private DataSource
loadDataSourceBean(String beanName
) {
267 File f
= new File("./");
268 System
.err
.println(f
.getAbsolutePath());
270 String beanDefinitionFileFromProperty
= findProperty(CDM_BEAN_DEFINITION_FILE
, false);
271 String path
= (beanDefinitionFileFromProperty
!= null ? beanDefinitionFileFromProperty
: beanDefinitionFile
);
272 logger
.info("loading DataSourceBean '" + beanName
+ "' from: " + path
);
273 FileSystemResource file
= new FileSystemResource(path
);
274 XmlBeanFactory beanFactory
= new XmlBeanFactory(file
);
275 DataSource dataSource
= beanFactory
.getBean(beanName
, DataSource
.class);
276 if(dataSource
instanceof ComboPooledDataSource
){
277 logger
.info("DataSourceBean '" + beanName
+ "' is a ComboPooledDataSource [URL:" + ((ComboPooledDataSource
)dataSource
).getJdbcUrl()+ "]");
279 logger
.error("DataSourceBean '" + beanName
+ "' IS NOT a ComboPooledDataSource");
286 * Loads the <code>dataSourceProperties</code> bean from the cdm bean
288 * This file is usually {@code ./.cdmLibrary/datasources.xml}
290 * @return the DataSourceProperties bean or an empty instance if the bean is not found
292 private DataSourceProperties
loadDataSourceProperties() {
294 String beanDefinitionFileFromProperty
= findProperty(CDM_BEAN_DEFINITION_FILE
, false);
295 String path
= (beanDefinitionFileFromProperty
!= null ? beanDefinitionFileFromProperty
: beanDefinitionFile
);
296 logger
.info("loading dataSourceProperties from: " + path
);
297 FileSystemResource file
= new FileSystemResource(path
);
298 XmlBeanFactory beanFactory
= new XmlBeanFactory(file
);
299 DataSourceProperties properties
= null;
301 properties
= beanFactory
.getBean("dataSourceProperties", DataSourceProperties
.class);
302 } catch (BeansException e
) {
303 logger
.warn("bean 'dataSourceProperties' not found");
304 properties
= new DataSourceProperties();
310 public Properties
hibernateProperties(){
311 Properties props
= getHibernateProperties();
312 props
.setProperty(HIBERNATE_DIALECT
, inferHibernateDialectName());
313 props
.setProperty(HIBERNATE_SEARCH_DEFAULT_INDEX_BASE
, CdmUtils
.getCdmHomeDir().getPath() + "/remote-webapp/index/".replace("/", File
.separator
) + findProperty(ATTRIBUTE_DATASOURCE_NAME
, true));
314 logger
.debug("hibernateProperties: " + props
.toString());
319 * Returns the full class name of the according {@link org.hibernate.dialect.Dialect} implementation
321 * @param ds the DataSource
324 public String
inferHibernateDialectName() {
325 DataSource ds
= dataSource();
326 return inferHibernateDialectName(ds
);
332 * Returns the full class name of the according {@link org.hibernate.dialect.Dialect} implementation
334 * @param ds the DataSource
337 public String
inferHibernateDialectName(DataSource ds
) {
338 String url
= "<SEE PRIOR REFLECTION ERROR>";
341 m
= ds
.getClass().getMethod("getUrl");
342 } catch (SecurityException e
) {
344 } catch (NoSuchMethodException e
) {
346 m
= ds
.getClass().getMethod("getJdbcUrl");
347 } catch (SecurityException e2
) {
349 } catch (NoSuchMethodException e2
) {
354 url
= (String
)m
.invoke(ds
);
355 } catch (IllegalArgumentException e
) {
357 } catch (IllegalAccessException e
) {
359 } catch (InvocationTargetException e
) {
361 } catch (SecurityException e
) {
366 if(url
.contains(":mysql:")){
367 // TODO we should switch all databases to InnoDB !
368 // TODO open jdbc connection to check engine and choose between
369 // MySQL5MyISAMUtf8Dialect and MySQL5MyISAMUtf8Dialect
370 // see #3371 (switch cdm to MySQL InnoDB)
371 return MySQL5MyISAMUtf8Dialect
.class.getName();
373 if(url
.contains(":h2:")){
374 return H2CorrectedDialect
.class.getName();
376 if(url
.contains(":postgresql:")){
377 return PostgreSQL82Dialect
.class.getName();
381 logger
.error("hibernate dialect mapping for "+url
+ " not yet implemented or unavailable");
385 public String
cmdServerInstanceName(){
386 // test for if this is an instance running in a cdmserver:
387 if(findProperty(ATTRIBUTE_JDBC_JNDI_NAME
, false) != null) {
388 String beanName
= findProperty(ATTRIBUTE_DATASOURCE_NAME
, true);
389 cmdServerInstanceName
= beanName
;
391 return cmdServerInstanceName
;