Project

General

Profile

Download (15.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.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;
35

    
36
import com.mchange.v2.c3p0.ComboPooledDataSource;
37

    
38
import eu.etaxonomy.cdm.common.CdmUtils;
39
import eu.etaxonomy.cdm.model.metadata.CdmMetaData;
40
import eu.etaxonomy.cdm.model.metadata.CdmMetaData.MetaDataPropertyName;
41

    
42
/**
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>:
45
 *
46
<pre>
47
&lt;!-- enable processing of annotations such as @Autowired and @Configuration --&gt;
48
&lt;context:annotation-config/&gt;
49

    
50
&lt;bean class="eu.etaxonomy.cdm.remote.config.DataSourceConfigurer" &gt;
51
&lt;/bean&gt;
52
</pre>
53
 * The <code>DataSourceConfigurer</code> allows alternative ways to specify a data source:
54
 *
55
 * <ol>
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.
60
 * </li>
61
 * <li>
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.
65
 * </li>
66
 * </ol>
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)}.
69
 *
70
 * @author a.kohlbecker
71
 * @date 04.02.2011
72
 *
73
 */
74
@Configuration
75
public class DataSourceConfigurer extends AbstractWebApplicationConfigurer {
76

    
77
    public static final Logger logger = Logger.getLogger(DataSourceConfigurer.class);
78

    
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";
82

    
83
    /**
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.
86
     * <br>
87
     * <b>This is a required attribute!</b>
88
     *
89
     * @see AbstractWebApplicationConfigurer#findProperty(String, boolean)
90
     *
91
     * see also <code>eu.etaxonomy.cdm.server.instance.SharedAttributes</code>
92
     *
93
     */
94
    protected static final String ATTRIBUTE_DATASOURCE_NAME = "cdm.datasource";
95
    /**
96
     * see also <code>eu.etaxonomy.cdm.server.instance.SharedAttributes</code>
97
     */
98
    public static final String ATTRIBUTE_JDBC_JNDI_NAME = "cdm.jdbcJndiName";
99

    
100
    /**
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>
103
     */
104
    public static final String ATTRIBUTE_FORCE_SCHEMA_UPDATE = "cdm.forceSchemaUpdate";
105

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

    
108
    protected static String beanDefinitionFile = DATASOURCE_BEANDEF_DEFAULT;
109

    
110

    
111
    private String cmdServerInstanceName = null;
112

    
113
    /**
114
     * The file to load the {@link DataSource} beans from.
115
     * This file is usually {@code ./.cdmLibrary/datasources.xml}
116
     *
117
     * @param filename
118
     */
119
    public void setBeanDefinitionFile(String filename){
120
        beanDefinitionFile = filename;
121
    }
122

    
123

    
124
    private String dataSourceId = null;
125

    
126
    private DataSource dataSource;
127

    
128
    private DataSourceProperties dataSourceProperties;
129

    
130
    private Properties getHibernateProperties() {
131
        Properties hibernateProperties = webApplicationContext.getBean("jndiHibernateProperties", Properties.class);
132
        return hibernateProperties;
133
    }
134

    
135

    
136

    
137
    @Bean
138
    public DataSource dataSource() {
139

    
140

    
141

    
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);
146

    
147
            if(jndiName != null){
148
                dataSource = useJndiDataSource(jndiName);
149
                dataSourceId = FilenameUtils.getName(jndiName);
150
            } else {
151
                dataSource = loadDataSourceBean(beanName);
152
                dataSourceId = beanName;
153
            }
154
        }
155

    
156
        if(dataSource == null){
157
            return null;
158
        }
159

    
160
        // validate correct schema version
161
        try {
162

    
163
            Connection connection = dataSource.getConnection();
164
            ResultSet tables = connection.getMetaData().getTables(connection.getCatalog(), null, "CdmMetaData", null);
165
            if(tables.first()){
166
                ResultSet resultSet = connection.createStatement().executeQuery(MetaDataPropertyName.DB_SCHEMA_VERSION.getSqlQuery());
167
                String version = null;
168
                if(resultSet.next()){
169
                    version = resultSet.getString(1);
170
                } else {
171
                    throw new RuntimeException("Unable to retrieve version info from data source " + dataSource.toString());
172
                }
173

    
174
                connection.close();
175

    
176
                if(!CdmMetaData.isDbSchemaVersionCompatible(version)){
177
                    /*
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
181
                     * ServletContext
182
                     */
183
                    String errorMessage = "Incompatible version [" + (beanName != null ? beanName : jndiName) + "] expected version: " + CdmMetaData.getDbSchemaVersion() + ",  data base version  " + version;
184
                    addErrorMessageToServletContextAttributes(errorMessage);
185
                }
186
            } else {
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");
189
            }
190

    
191

    
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());
195
            throw re;
196

    
197
        }
198

    
199

    
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)");
203
/*
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;
210
            }
211
            RuntimeException re =   new RuntimeException("Unable to update data source, CdmUpdater.updateToCurrentVersion() cannot use javax.sql.DataSource or javax.sql.DataSource");
212

    
213
            CdmDataSource cdmDataSource = CdmDataSource.NewInstance(dbType, dataSource.ge, database, username, password)
214
            updater.updateToCurrentVersion(dataSource, null);
215
*/
216

    
217
        }
218

    
219
        return dataSource;
220
    }
221

    
222
    @Bean
223
    public DataSourceProperties dataSourceProperties(){
224
        if(this.dataSourceProperties == null){
225
            dataSourceProperties = loadDataSourceProperties();
226
            if(dataSourceId == null){
227
                dataSource();
228
            }
229
            dataSourceProperties.setCurrentDataSourceId(dataSourceId);
230
        }
231
        return dataSourceProperties;
232
    }
233

    
234

    
235
    private DataSource useJndiDataSource(String jndiName) {
236
        logger.info("using jndi datasource '" + jndiName + "'");
237

    
238
        JndiObjectFactoryBean jndiFactory = new JndiObjectFactoryBean();
239
        /*
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
244
        */
245
        jndiFactory.setResourceRef(true);
246
        jndiFactory.setJndiName(jndiName);
247
        try {
248
            jndiFactory.afterPropertiesSet();
249
        } catch (IllegalArgumentException e) {
250
            logger.error(e, e);
251
        } catch (NamingException e) {
252
            logger.error(e, e);
253
        }
254
        Object obj = jndiFactory.getObject();
255
        return (DataSource)obj;
256
    }
257

    
258
    /**
259
     * Loads the {@link DataSource} bean from the cdm bean definition file.
260
     * This file is usually {@code ./.cdmLibrary/datasources.xml}
261
     *
262
     * @param beanName
263
     * @return
264
     */
265
    private DataSource loadDataSourceBean(String beanName) {
266

    
267
        File f = new File("./");
268
        System.err.println(f.getAbsolutePath());
269

    
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()+ "]");
278
        } else {
279
            logger.error("DataSourceBean '" + beanName + "' IS NOT a ComboPooledDataSource");
280
        }
281
        return dataSource;
282
    }
283

    
284

    
285
    /**
286
     * Loads the <code>dataSourceProperties</code> bean from the cdm bean
287
     * definition file.
288
     * This file is usually {@code ./.cdmLibrary/datasources.xml}
289
     *
290
     * @return the DataSourceProperties bean or an empty instance if the bean is not found
291
     */
292
    private DataSourceProperties loadDataSourceProperties() {
293

    
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;
300
        try {
301
            properties = beanFactory.getBean("dataSourceProperties", DataSourceProperties.class);
302
        } catch (BeansException e) {
303
            logger.warn("bean 'dataSourceProperties' not found");
304
            properties = new DataSourceProperties();
305
        }
306
        return properties;
307
    }
308

    
309
    @Bean
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());
315
        return props;
316
    }
317

    
318
    /**
319
     * Returns the full class name of the according {@link org.hibernate.dialect.Dialect} implementation
320
     *
321
     * @param ds the DataSource
322
     * @return the name
323
     */
324
    public String inferHibernateDialectName() {
325
        DataSource ds = dataSource();
326
        return inferHibernateDialectName(ds);
327
    }
328

    
329

    
330

    
331
    /**
332
     * Returns the full class name of the according {@link org.hibernate.dialect.Dialect} implementation
333
     *
334
     * @param ds the DataSource
335
     * @return the name
336
     */
337
    public String inferHibernateDialectName(DataSource ds) {
338
        String url = "<SEE PRIOR REFLECTION ERROR>";
339
        Method m = null;
340
        try {
341
            m = ds.getClass().getMethod("getUrl");
342
        } catch (SecurityException e) {
343
            logger.error(e);
344
        } catch (NoSuchMethodException e) {
345
            try {
346
                m = ds.getClass().getMethod("getJdbcUrl");
347
            } catch (SecurityException e2) {
348
                logger.error(e2);
349
            } catch (NoSuchMethodException e2) {
350
                logger.error(e2);
351
            }
352
        }
353
        try {
354
            url = (String)m.invoke(ds);
355
        } catch (IllegalArgumentException e) {
356
            logger.error(e);
357
        } catch (IllegalAccessException e) {
358
            logger.error(e);
359
        } catch (InvocationTargetException e) {
360
            logger.error(e);
361
        } catch (SecurityException e) {
362
            logger.error(e);
363
        }
364

    
365
        if(url != null){
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();
372
            }
373
            if(url.contains(":h2:")){
374
                return H2CorrectedDialect.class.getName();
375
            }
376
            if(url.contains(":postgresql:")){
377
                return PostgreSQL82Dialect.class.getName();
378
            }
379
        }
380

    
381
        logger.error("hibernate dialect mapping for "+url+ " not yet implemented or unavailable");
382
        return null;
383
    }
384

    
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;
390
        }
391
        return cmdServerInstanceName;
392
    }
393

    
394
}
(2-2/4)