Project

General

Profile

Download (12.7 KB) Statistics
| Branch: | Tag: | Revision:
1
// $Id$
2
/**
3
 * Copyright (C) 2009 EDIT
4
 * European Distributed Institute of Taxonomy
5
 * http://www.e-taxonomy.eu
6
 *
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.
9
 */
10

    
11
package eu.etaxonomy.cdm.remote.config;
12

    
13
import java.io.File;
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;
20

    
21
import javax.naming.NamingException;
22
import javax.sql.DataSource;
23

    
24
import org.apache.commons.io.FilenameUtils;
25
import org.apache.log4j.Logger;
26
import org.hibernate.dialect.MySQL5MyISAMUtf8Dialect;
27
import org.springframework.beans.BeansException;
28
import org.springframework.beans.factory.xml.XmlBeanFactory;
29
import org.springframework.context.annotation.Bean;
30
import org.springframework.context.annotation.Configuration;
31
import org.springframework.core.io.FileSystemResource;
32
import org.springframework.jndi.JndiObjectFactoryBean;
33

    
34
import com.mchange.v2.c3p0.ComboPooledDataSource;
35

    
36
import eu.etaxonomy.cdm.common.CdmUtils;
37
import eu.etaxonomy.cdm.model.common.CdmMetaData;
38
import eu.etaxonomy.cdm.model.common.CdmMetaData.MetaDataPropertyName;
39

    
40
/**
41
 * The <code>DataSourceConfigurer</code> can be used as a replacement for a xml configuration in the application context.
42
 * Enter the following in your application context configuration in order to enable the <code>DataSourceConfigurer</code>:
43
 *
44
<pre>
45
&lt;!-- enable processing of annotations such as @Autowired and @Configuration --&gt;
46
&lt;context:annotation-config/&gt;
47

    
48
&lt;bean class="eu.etaxonomy.cdm.remote.config.DataSourceConfigurer" &gt;
49
&lt;/bean&gt;
50
</pre>
51
 * The <code>DataSourceConfigurer</code> allows alternative ways to specify a data source:
52
 *
53
 * <ol>
54
 * <li>Specify the data source bean to use in the Java environment properties:
55
 * <code>-Dcdm.datasource={dataSourceName}</code> ({@link #ATTRIBUTE_DATASOURCE_NAME}).
56
 * The data source bean with the given name will then be loaded from the <code>cdm.beanDefinitionFile</code>
57
 * ({@link #CDM_BEAN_DEFINITION_FILE}), which must be a valid Spring bean definition file.
58
 * </li>
59
 * <li>
60
 * Use a JDBC data source which is bound into the JNDI context. In this case the JNDI name is specified
61
 * via the {@link #ATTRIBUTE_JDBC_JNDI_NAME} as attribute to the ServletContext.
62
 * This scenario usually being used by the cdm-server application.
63
 * </li>
64
 * </ol>
65
 * The attributes used in (1) and (2) are in a first step being searched in the ServletContext
66
 * if not found search in a second step in the environment variables of the OS, see:{@link #findProperty(String, boolean)}.
67
 *
68
 * @author a.kohlbecker
69
 * @date 04.02.2011
70
 *
71
 */
72
@Configuration
73
public class DataSourceConfigurer extends AbstractWebApplicationConfigurer {
74

    
75
    public static final Logger logger = Logger.getLogger(DataSourceConfigurer.class);
76

    
77
    protected static final String ATTRIBUTE_JDBC_JNDI_NAME = "cdm.jdbcJndiName";
78
    protected static final String HIBERNATE_DIALECT = "hibernate.dialect";
79
    protected static final String HIBERNATE_SEARCH_DEFAULT_INDEX_BASE = "hibernate.search.default.indexBase";
80
    protected static final String CDM_BEAN_DEFINITION_FILE = "cdm.beanDefinitionFile";
81
    /**
82
     * Attribute to configure the name of the data source as set as bean name in the datasources.xml.
83
     * This name usually is used as the prefix for the webapplication root path.
84
     * <br>
85
     * <b>This is a required attribute!</b>
86
     *
87
     * @see AbstractWebApplicationConfigurer#findProperty(String, boolean)
88
     */
89
    protected static final String ATTRIBUTE_DATASOURCE_NAME = "cdm.datasource";
90

    
91
    protected static final String DATASOURCE_BEANDEF_DEFAULT = CdmUtils.getCdmHomeDir().getPath() + File.separator + "datasources.xml";
92

    
93
    protected static String beanDefinitionFile = DATASOURCE_BEANDEF_DEFAULT;
94

    
95
    /**
96
     * The file to load the {@link DataSource} beans from.
97
     * This file is usually {@code ./.cdmLibrary/datasources.xml}
98
     *
99
     * @param filename
100
     */
101
    public void setBeanDefinitionFile(String filename){
102
        beanDefinitionFile = filename;
103
    }
104

    
105

    
106
    private String dataSourceId = null;
107

    
108
    private DataSource dataSource;
109

    
110
    private DataSourceProperties dataSourceProperties;
111

    
112
    private Properties getHibernateProperties() {
113
        Properties hibernateProperties = webApplicationContext.getBean("jndiHibernateProperties", Properties.class);
114
        return hibernateProperties;
115
    }
116

    
117

    
118

    
119
    @Bean
120
    public DataSource dataSource() {
121

    
122
        String beanName = findProperty(ATTRIBUTE_DATASOURCE_NAME, true);
123
        String jndiName = null;
124
        if(this.dataSource == null){
125
            jndiName = findProperty(ATTRIBUTE_JDBC_JNDI_NAME, false);
126

    
127
            if(jndiName != null){
128
                dataSource = useJndiDataSource(jndiName);
129
                dataSourceId = FilenameUtils.getName(jndiName);
130
            } else {
131
                dataSource = loadDataSourceBean(beanName);
132
                dataSourceId = beanName;
133
            }
134
        }
135

    
136
        if(dataSource == null){
137
            return null;
138
        }
139

    
140
        // validate correct schema version
141
        try {
142

    
143
            Connection connection = dataSource.getConnection();
144
            ResultSet tables = connection.getMetaData().getTables(connection.getCatalog(), null, "CdmMetaData", null);
145
            if(tables.first()){
146
                ResultSet resultSet = connection.createStatement().executeQuery(MetaDataPropertyName.DB_SCHEMA_VERSION.getSqlQuery());
147
                String version = null;
148
                if(resultSet.next()){
149
                    version = resultSet.getString(1);
150
                } else {
151
                    throw new RuntimeException("Unable to retrieve version info from data source " + dataSource.toString());
152
                }
153

    
154
                connection.close();
155

    
156
                if(!CdmMetaData.isDbSchemaVersionCompatible(version)){
157
                    /*
158
                     * any exception thrown here would be nested into a spring
159
                     * BeanException which can not be caught in the servlet
160
                     * container, so we post the information into the
161
                     * ServletContext
162
                     */
163
                    String errorMessage = "Incompatible version [" + (beanName != null ? beanName : jndiName) + "] expected version: " + CdmMetaData.getDbSchemaVersion() + ",  data base version  " + version;
164
                    addErrorMessageToServletContextAttributes(errorMessage);
165
                }
166
            } else {
167
//            	throw new RuntimeException("database " + dataSource.toString() + " is empty or not a cdm database");
168
                logger.error("database " + dataSource.toString() + " is empty or not a cdm database");
169
            }
170

    
171

    
172
        } catch (SQLException e) {
173
            RuntimeException re =   new RuntimeException("Unable to connect or to retrieve version info from data source " + dataSource.toString() , e);
174
            addErrorMessageToServletContextAttributes(re.getMessage());
175
            throw re;
176

    
177
        }
178
        return dataSource;
179
    }
180

    
181
    @Bean
182
    public DataSourceProperties dataSourceProperties(){
183
        if(this.dataSourceProperties == null){
184
            dataSourceProperties = loadDataSourceProperties();
185
            if(dataSourceId == null){
186
                dataSource();
187
            }
188
            dataSourceProperties.setCurrentDataSourceId(dataSourceId);
189
        }
190
        return dataSourceProperties;
191
    }
192

    
193

    
194
    private DataSource useJndiDataSource(String jndiName) {
195
        logger.info("using jndi datasource '" + jndiName + "'");
196

    
197
        JndiObjectFactoryBean jndiFactory = new JndiObjectFactoryBean();
198
        /*
199
        JndiTemplate jndiTemplate = new JndiTemplate();
200
        jndiFactory.setJndiTemplate(jndiTemplate); no need to use a JndiTemplate
201
        if I try using JndiTemplate I get an org.hibernate.AnnotationException: "Unknown Id.generator: system-increment"
202
        when running multiple instances via the Bootloader
203
        */
204
        jndiFactory.setResourceRef(true);
205
        jndiFactory.setJndiName(jndiName);
206
        try {
207
            jndiFactory.afterPropertiesSet();
208
        } catch (IllegalArgumentException e) {
209
            logger.error(e);
210
        } catch (NamingException e) {
211
            logger.error(e);
212
        }
213
        Object obj = jndiFactory.getObject();
214
        return (DataSource)obj;
215
    }
216

    
217
    /**
218
     * Loads the {@link DataSource} bean from the cdm bean definition file.
219
     * This file is usually {@code ./.cdmLibrary/datasources.xml}
220
     *
221
     * @param beanName
222
     * @return
223
     */
224
    private DataSource loadDataSourceBean(String beanName) {
225

    
226
        String beanDefinitionFileFromProperty = findProperty(CDM_BEAN_DEFINITION_FILE, false);
227
        String path = (beanDefinitionFileFromProperty != null ? beanDefinitionFileFromProperty : beanDefinitionFile);
228
        logger.info("loading DataSourceBean '" + beanName + "' from: " + path);
229
        FileSystemResource file = new FileSystemResource(path);
230
        XmlBeanFactory beanFactory  = new XmlBeanFactory(file);
231
        DataSource dataSource = beanFactory.getBean(beanName, DataSource.class);
232
        if(dataSource instanceof ComboPooledDataSource){
233
            logger.info("DataSourceBean '" + beanName + "' is a ComboPooledDataSource [URL:" + ((ComboPooledDataSource)dataSource).getJdbcUrl()+ "]");
234
        } else {
235
            logger.error("DataSourceBean '" + beanName + "' IS NOT a ComboPooledDataSource");
236
        }
237
        return dataSource;
238
    }
239

    
240

    
241
    /**
242
     * Loads the <code>dataSourceProperties</code> bean from the cdm bean
243
     * definition file.
244
     * This file is usually {@code ./.cdmLibrary/datasources.xml}
245
     *
246
     * @return the DataSourceProperties bean or an empty instance if the bean is not found
247
     */
248
    private DataSourceProperties loadDataSourceProperties() {
249

    
250
        String beanDefinitionFileFromProperty = findProperty(CDM_BEAN_DEFINITION_FILE, false);
251
        String path = (beanDefinitionFileFromProperty != null ? beanDefinitionFileFromProperty : beanDefinitionFile);
252
        logger.info("loading dataSourceProperties from: " + path);
253
        FileSystemResource file = new FileSystemResource(path);
254
        XmlBeanFactory beanFactory  = new XmlBeanFactory(file);
255
        DataSourceProperties properties = null;
256
        try {
257
            properties = beanFactory.getBean("dataSourceProperties", DataSourceProperties.class);
258
        } catch (BeansException e) {
259
            logger.warn("bean 'dataSourceProperties' not found");
260
            properties = new DataSourceProperties();
261
        }
262
        return properties;
263
    }
264

    
265
    @Bean
266
    public Properties hibernateProperties(){
267
        Properties props = getHibernateProperties();
268
        props.setProperty(HIBERNATE_DIALECT, inferHibernateDialectName());
269
        props.setProperty(HIBERNATE_SEARCH_DEFAULT_INDEX_BASE, CdmUtils.getCdmHomeDir().getPath() + "/remote-webapp/index/".replace("/", File.separator) + findProperty(ATTRIBUTE_DATASOURCE_NAME, true));
270
        logger.debug("hibernateProperties: " + props.toString());
271
        return props;
272
    }
273

    
274
    public String inferHibernateDialectName() {
275
        DataSource ds = dataSource();
276
        String url = "<SEE PRIOR REFLECTION ERROR>";
277
        Method m = null;
278
        try {
279
            m = ds.getClass().getMethod("getUrl");
280
        } catch (SecurityException e) {
281
            logger.error(e);
282
        } catch (NoSuchMethodException e) {
283
            try {
284
                m = ds.getClass().getMethod("getJdbcUrl");
285
            } catch (SecurityException e2) {
286
                logger.error(e2);
287
            } catch (NoSuchMethodException e2) {
288
                logger.error(e2);
289
            }
290
        }
291
        try {
292
            url = (String)m.invoke(ds);
293
        } catch (IllegalArgumentException e) {
294
            logger.error(e);
295
        } catch (IllegalAccessException e) {
296
            logger.error(e);
297
        } catch (InvocationTargetException e) {
298
            logger.error(e);
299
        } catch (SecurityException e) {
300
            logger.error(e);
301
        }
302

    
303
        if(url != null && url.contains("mysql")){
304
            // TODO we should switch all databases to InnoDB !
305
            // TODO open jdbc connection to check engine and choose between
306
            // MySQL5MyISAMUtf8Dialect and MySQL5MyISAMUtf8Dialect
307
            // see #3371 (switch cdm to MySQL InnoDB)
308
            return MySQL5MyISAMUtf8Dialect.class.getName();
309
        }
310

    
311
        logger.error("hibernate dialect mapping for "+url+ " not jet implemented or unavailable");
312
        return null;
313
    }
314

    
315
}
(2-2/4)