Project

General

Profile

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

    
10
package eu.etaxonomy.cdm.opt.config;
11

    
12
import java.io.File;
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;
19

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

    
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.context.annotation.PropertySource;
33
import org.springframework.core.Ordered;
34
import org.springframework.core.annotation.Order;
35
import org.springframework.core.env.MutablePropertySources;
36
import org.springframework.core.env.PropertiesPropertySource;
37
import org.springframework.core.io.FileSystemResource;
38
import org.springframework.jndi.JndiObjectFactoryBean;
39

    
40
import com.mchange.v2.c3p0.ComboPooledDataSource;
41

    
42
import eu.etaxonomy.cdm.api.config.CdmConfigurationKeys;
43
import eu.etaxonomy.cdm.common.ConfigFileUtil;
44
import eu.etaxonomy.cdm.database.WrappedCdmDataSource;
45
import eu.etaxonomy.cdm.database.update.CdmUpdater;
46
import eu.etaxonomy.cdm.model.metadata.CdmMetaData;
47
import eu.etaxonomy.cdm.model.metadata.CdmMetaDataPropertyName;
48
import eu.etaxonomy.cdm.remote.config.AbstractWebApplicationConfigurer;
49

    
50
/**
51
 * The <code>DataSourceConfigurer</code> can be used as a replacement for a xml configuration in the application context.
52
 * <p>
53
 * The id of the loaded data source bean aka the <b>cdm instance name</b> is put into the <b>Spring environment</b> from where it can be retrieved using the
54
 * key {@link CDM_DATA_SOURCE_ID}.
55
 * <p>
56
 * Enter the following in your application context configuration in order to enable the <code>DataSourceConfigurer</code>:
57
 *
58
<pre>
59
&lt;!-- enable processing of annotations such as @Autowired and @Configuration --&gt;
60
&lt;context:annotation-config/&gt;
61

    
62
&lt;bean class="eu.etaxonomy.cdm.remote.config.DataSourceConfigurer" &gt;
63
&lt;/bean&gt;
64
</pre>
65
 * The <code>DataSourceConfigurer</code> allows alternative ways to specify a data source:
66
 *
67
 * <ol>
68
 * <li>Specify the data source bean to use in the Java environment properties:
69
 * <code>-Dcdm.datasource={dataSourceName}</code> ({@link #ATTRIBUTE_DATASOURCE_NAME}).
70
 * The data source bean with the given name will then be loaded from the <code>cdm.beanDefinitionFile</code>
71
 * ({@link #CDM_BEAN_DEFINITION_FILE}), which must be a valid Spring bean definition file.
72
 * </li>
73
 * <li>
74
 * Use a JDBC data source which is bound into the JNDI context. In this case the JNDI name is specified
75
 * via the {@link #ATTRIBUTE_JDBC_JNDI_NAME} as attribute to the ServletContext.
76
 * This scenario usually being used by the cdm-server application.
77
 * </li>
78
 * </ol>
79
 * The attributes used in (1) and (2) are in a first step being searched in the ServletContext
80
 * if not found search in a second step in the environment variables of the OS, see:{@link #findProperty(String, boolean)}.
81
 *
82
 * @author a.kohlbecker
83
 * @since 04.02.2011
84
 *
85
 */
86
@Configuration
87
// cdmlib-remote.properties is used by developers to define the datasource bean to use from
88
// the datasources.xml. It is not relevant for production systems.
89
@PropertySource("file:${user.home}/.cdmLibrary/cdmlib-remote.properties")
90
public class DataSourceConfigurer extends AbstractWebApplicationConfigurer {
91

    
92
    public static final Logger logger = Logger.getLogger(DataSourceConfigurer.class);
93

    
94
    protected static final String HIBERNATE_DIALECT = "hibernate.dialect";
95
    protected static final String HIBERNATE_SEARCH_DEFAULT_INDEX_BASE = "hibernate.search.default.indexBase";
96
    protected static final String CDM_BEAN_DEFINITION_FILE = "cdm.beanDefinitionFile";
97

    
98
    /**
99
     * key for the spring environment to the datasource bean id aka instance name
100
     * @deprecated use CdmConfigurationKeys.CDM_DATA_SOURCE_ID instead
101
     */
102
    @Deprecated
103
    public static final String CDM_DATA_SOURCE_ID = CdmConfigurationKeys.CDM_DATA_SOURCE_ID;
104

    
105
    /**
106
     * Attribute to configure the name of the data source as set as bean name in the datasources.xml.
107
     * This name usually is used as the prefix for the webapplication root path.
108
     * <br>
109
     * <b>This is a required attribute!</b>
110
     *
111
     * @see AbstractWebApplicationConfigurer#findProperty(String, boolean)
112
     *
113
     * see also <code>eu.etaxonomy.cdm.server.instance.SharedAttributes</code>
114
     *
115
     */
116
    protected static final String ATTRIBUTE_DATASOURCE_NAME = "cdm.datasource";
117
    /**
118
     * see also <code>eu.etaxonomy.cdm.server.instance.SharedAttributes</code>
119
     */
120
    public static final String ATTRIBUTE_JDBC_JNDI_NAME = "cdm.jdbcJndiName";
121

    
122
    /**
123
     * Force a schema update when the cdmlib-remote-webapp instance is starting up
124
     * see also <code>eu.etaxonomy.cdm.server.instance.SharedAttributes.ATTRIBUTE_FORCE_SCHEMA_UPDATE</code>
125
     */
126
    public static final String ATTRIBUTE_FORCE_SCHEMA_UPDATE = "cdm.forceSchemaUpdate";
127

    
128
    protected static final String DATASOURCE_BEANDEF_DEFAULT = ConfigFileUtil.getCdmHomeDir().getPath() + File.separator + "datasources.xml";
129

    
130
    protected static String beanDefinitionFile = DATASOURCE_BEANDEF_DEFAULT;
131

    
132

    
133
    /**
134
     * The file to load the {@link DataSource} beans from.
135
     * This file is usually {@code ./.cdmLibrary/datasources.xml}
136
     *
137
     * @param filename
138
     */
139
    public void setBeanDefinitionFile(String filename){
140
        beanDefinitionFile = filename;
141
    }
142

    
143
    private String dataSourceId = null;
144

    
145
    private DataSource dataSource;
146

    
147
    private DataSourceProperties dataSourceProperties;
148

    
149
    private Properties getHibernateProperties() {
150
        Properties hibernateProperties = webApplicationContext.getBean("jndiHibernateProperties", Properties.class);
151
        return hibernateProperties;
152
    }
153

    
154

    
155

    
156
    @Bean
157
    @Order(Ordered.HIGHEST_PRECEDENCE)
158
    public DataSource dataSource() {
159

    
160
        String beanName = findProperty(ATTRIBUTE_DATASOURCE_NAME, true);
161
        String jndiName = null;
162
        if(this.dataSource == null){
163
            jndiName = findProperty(ATTRIBUTE_JDBC_JNDI_NAME, false);
164

    
165
            if(jndiName != null){
166
                dataSource = useJndiDataSource(jndiName);
167
                dataSourceId = FilenameUtils.getName(jndiName);
168
            } else {
169
                dataSource = loadDataSourceBean(beanName);
170
                dataSourceId = beanName;
171
            }
172
        }
173

    
174
        if(dataSource == null){
175
            return null;
176
        }
177

    
178
        MutablePropertySources sources = env.getPropertySources();
179
        Properties props = new Properties();
180
        props.setProperty(CDM_DATA_SOURCE_ID, dataSourceId);
181
        sources.addFirst(new PropertiesPropertySource("cdm-datasource",  props));
182

    
183
        // validate correct schema version
184
        Connection connection  = null;
185
        try {
186

    
187
            connection = dataSource.getConnection();
188
            String metadataTableName = "CdmMetaData";
189
            if(inferHibernateDialectName(dataSource).equals(H2CorrectedDialect.class.getName())){
190
                metadataTableName = metadataTableName.toUpperCase();
191
            }
192
            ResultSet tables = connection.getMetaData().getTables(connection.getCatalog(), null, metadataTableName, null);
193
            if(tables.first()){
194
                ResultSet resultSet;
195
                try {
196
                    resultSet = connection.createStatement().executeQuery(CdmMetaDataPropertyName.DB_SCHEMA_VERSION.getSqlQuery());
197
                } catch (Exception e) {
198
                    try {
199
                        resultSet = connection.createStatement().executeQuery(CdmMetaDataPropertyName.DB_SCHEMA_VERSION.getSqlQueryOld());
200
                    } catch (Exception e1) {
201
                        throw e1;
202
                    }
203
                }
204
                String version = null;
205
                if(resultSet.next()){
206
                    version = resultSet.getString(1);
207
                } else {
208
                    throw new RuntimeException("Unable to retrieve version info from data source " + dataSource.toString());
209
                }
210

    
211
                connection.close();
212

    
213
                if(!CdmMetaData.isDbSchemaVersionCompatible(version)){
214
                    /*
215
                     * any exception thrown here would be nested into a spring
216
                     * BeanException which can not be caught in the servlet
217
                     * container, so we post the information into the
218
                     * ServletContext
219
                     */
220
                    String errorMessage = "Incompatible version [" + (beanName != null ? beanName : jndiName) + "] expected version: " + CdmMetaData.getDbSchemaVersion() + ",  data base version  " + version;
221
                    addErrorMessageToServletContextAttributes(errorMessage);
222
                }
223
            } else {
224
//            	throw new RuntimeException("database " + dataSource.toString() + " is empty or not a cdm database");
225
                logger.error("database " + dataSource.toString() + " is empty or not a cdm database");
226
            }
227

    
228

    
229
        } catch (SQLException e) {
230
            RuntimeException re =   new RuntimeException("Unable to connect or to retrieve version info from data source " + dataSource.toString() , e);
231
            addErrorMessageToServletContextAttributes(re.getMessage());
232
            throw re;
233
        } finally {
234
            if(connection != null){
235
                try {
236
                    connection.close();
237
                } catch (SQLException e) {
238
                    // IGNORE //
239
                }
240
            }
241
        }
242

    
243

    
244
        String forceSchemaUpdate = findProperty(ATTRIBUTE_FORCE_SCHEMA_UPDATE, false);
245
        if(forceSchemaUpdate != null){
246
            logger.info("Update of data source requested by property '" + ATTRIBUTE_FORCE_SCHEMA_UPDATE + "'");
247

    
248
            CdmUpdater updater = CdmUpdater.NewInstance();
249
            WrappedCdmDataSource cdmDataSource = new WrappedCdmDataSource(dataSource);
250
            updater.updateToCurrentVersion(cdmDataSource, null);
251
            cdmDataSource.closeOpenConnections();
252
        }
253

    
254
        return dataSource;
255
    }
256

    
257
    @Bean
258
    public DataSourceProperties dataSourceProperties(){
259
        if(this.dataSourceProperties == null){
260
            dataSourceProperties = loadDataSourceProperties();
261
            if(dataSourceId == null){
262
                dataSource();
263
            }
264
            dataSourceProperties.setCurrentDataSourceId(dataSourceId);
265
        }
266
        return dataSourceProperties;
267
    }
268

    
269

    
270
    private DataSource useJndiDataSource(String jndiName) {
271
        logger.info("using jndi datasource '" + jndiName + "'");
272

    
273
        JndiObjectFactoryBean jndiFactory = new JndiObjectFactoryBean();
274
        /*
275
        JndiTemplate jndiTemplate = new JndiTemplate();
276
        jndiFactory.setJndiTemplate(jndiTemplate); no need to use a JndiTemplate
277
        if I try using JndiTemplate I get an org.hibernate.AnnotationException: "Unknown Id.generator: system-increment"
278
        when running multiple instances via the Bootloader
279
        */
280
        jndiFactory.setResourceRef(true);
281
        jndiFactory.setJndiName(jndiName);
282
        try {
283
            jndiFactory.afterPropertiesSet();
284
        } catch (IllegalArgumentException e) {
285
            logger.error(e, e);
286
        } catch (NamingException e) {
287
            logger.error(e, e);
288
        }
289
        Object obj = jndiFactory.getObject();
290
        return (DataSource)obj;
291
    }
292

    
293
    /**
294
     * Loads the {@link DataSource} bean from the cdm bean definition file.
295
     * This file is usually {@code ./.cdmLibrary/datasources.xml}
296
     *
297
     * @param beanName
298
     * @return
299
     */
300
    private DataSource loadDataSourceBean(String beanName) {
301

    
302
        File f = new File("./");
303
        System.err.println(f.getAbsolutePath());
304

    
305
        String beanDefinitionFileFromProperty = findProperty(CDM_BEAN_DEFINITION_FILE, false);
306
        String path = (beanDefinitionFileFromProperty != null ? beanDefinitionFileFromProperty : beanDefinitionFile);
307
        logger.info("loading DataSourceBean '" + beanName + "' from: " + path);
308
        FileSystemResource file = new FileSystemResource(path);
309
        XmlBeanFactory beanFactory  = new XmlBeanFactory(file);
310
        DataSource dataSource = beanFactory.getBean(beanName, DataSource.class);
311
        if(dataSource instanceof ComboPooledDataSource){
312
            logger.info("DataSourceBean '" + beanName + "' is a ComboPooledDataSource [URL:" + ((ComboPooledDataSource)dataSource).getJdbcUrl()+ "]");
313
        } else {
314
            logger.error("DataSourceBean '" + beanName + "' IS NOT a ComboPooledDataSource");
315
        }
316
        return dataSource;
317
    }
318

    
319

    
320
    /**
321
     * Loads the <code>dataSourceProperties</code> bean from the cdm bean
322
     * definition file.
323
     * This file is usually {@code ./.cdmLibrary/datasources.xml}
324
     *
325
     * @return the DataSourceProperties bean or an empty instance if the bean is not found
326
     */
327
    private DataSourceProperties loadDataSourceProperties() {
328

    
329
        String beanDefinitionFileFromProperty = findProperty(CDM_BEAN_DEFINITION_FILE, false);
330
        String path = (beanDefinitionFileFromProperty != null ? beanDefinitionFileFromProperty : beanDefinitionFile);
331
        logger.info("loading dataSourceProperties from: " + path);
332
        FileSystemResource file = new FileSystemResource(path);
333
        XmlBeanFactory beanFactory  = new XmlBeanFactory(file);
334
        DataSourceProperties properties = null;
335
        try {
336
            properties = beanFactory.getBean("dataSourceProperties", DataSourceProperties.class);
337
        } catch (BeansException e) {
338
            logger.warn("bean 'dataSourceProperties' not found");
339
            properties = new DataSourceProperties();
340
        }
341
        return properties;
342
    }
343

    
344
    @Bean
345
    public Properties hibernateProperties(){
346
        Properties props = getHibernateProperties();
347
        props.setProperty(HIBERNATE_DIALECT, inferHibernateDialectName());
348
        String searchPath = ConfigFileUtil.getCdmHomeSubDir(ConfigFileUtil.SUBFOLDER_WEBAPP).getPath();
349
        props.setProperty(HIBERNATE_SEARCH_DEFAULT_INDEX_BASE,
350
                searchPath +
351
                "/index/".replace("/", File.separator) +
352
                findProperty(ATTRIBUTE_DATASOURCE_NAME, true));
353
        logger.debug("hibernateProperties: " + props.toString());
354
        return props;
355
    }
356

    
357
    /**
358
     * Returns the full class name of the according {@link org.hibernate.dialect.Dialect} implementation
359
     *
360
     * @param ds the DataSource
361
     * @return the name
362
     */
363
    public String inferHibernateDialectName() {
364
        DataSource ds = dataSource();
365
        return inferHibernateDialectName(ds);
366
    }
367

    
368

    
369

    
370
    /**
371
     * Returns the full class name of the according {@link org.hibernate.dialect.Dialect} implementation
372
     *
373
     * @param ds the DataSource
374
     * @return the name
375
     */
376
    public String inferHibernateDialectName(DataSource ds) {
377
        String url = "<SEE PRIOR REFLECTION ERROR>";
378
        Method m = null;
379
        try {
380
            m = ds.getClass().getMethod("getUrl");
381
        } catch (SecurityException e) {
382
            logger.error(e);
383
        } catch (NoSuchMethodException e) {
384
            try {
385
                m = ds.getClass().getMethod("getJdbcUrl");
386
            } catch (SecurityException e2) {
387
                logger.error(e2);
388
            } catch (NoSuchMethodException e2) {
389
                logger.error(e2);
390
            }
391
        }
392
        try {
393
            url = (String)m.invoke(ds);
394
        } catch (IllegalArgumentException e) {
395
            logger.error(e);
396
        } catch (IllegalAccessException e) {
397
            logger.error(e);
398
        } catch (InvocationTargetException e) {
399
            logger.error(e);
400
        } catch (SecurityException e) {
401
            logger.error(e);
402
        }
403

    
404
        if(url != null){
405
            if(url.contains(":mysql:")){
406
                // TODO we should switch all databases to InnoDB !
407
                // TODO open jdbc connection to check engine and choose between
408
                // MySQL5MyISAMUtf8Dialect and MySQL5MyISAMUtf8Dialect
409
                // see #3371 (switch cdm to MySQL InnoDB)
410
                return MySQL5MyISAMUtf8Dialect.class.getName();
411
            }
412
            if(url.contains(":h2:")){
413
                return H2CorrectedDialect.class.getName();
414
            }
415
            if(url.contains(":postgresql:")){
416
                return PostgreSQL82Dialect.class.getName();
417
            }
418
        }
419

    
420
        logger.error("hibernate dialect mapping for "+url+ " not yet implemented or unavailable");
421
        return null;
422
    }
423

    
424
}
(1-1/5)