Project

General

Profile

Download (15.4 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.remote.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.core.io.FileSystemResource;
33
import org.springframework.jndi.JndiObjectFactoryBean;
34

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

    
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

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

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

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

    
80
    protected static final String HIBERNATE_DIALECT = "hibernate.dialect";
81
    protected static final String HIBERNATE_SEARCH_DEFAULT_INDEX_BASE = "hibernate.search.default.indexBase";
82
    protected static final String CDM_BEAN_DEFINITION_FILE = "cdm.beanDefinitionFile";
83

    
84
    /**
85
     * Attribute to configure the name of the data source as set as bean name in the datasources.xml.
86
     * This name usually is used as the prefix for the webapplication root path.
87
     * <br>
88
     * <b>This is a required attribute!</b>
89
     *
90
     * @see AbstractWebApplicationConfigurer#findProperty(String, boolean)
91
     *
92
     * see also <code>eu.etaxonomy.cdm.server.instance.SharedAttributes</code>
93
     *
94
     */
95
    protected static final String ATTRIBUTE_DATASOURCE_NAME = "cdm.datasource";
96
    /**
97
     * see also <code>eu.etaxonomy.cdm.server.instance.SharedAttributes</code>
98
     */
99
    public static final String ATTRIBUTE_JDBC_JNDI_NAME = "cdm.jdbcJndiName";
100

    
101
    /**
102
     * Force a schema update when the cdmlib-remote-webapp instance is starting up
103
     * see also <code>eu.etaxonomy.cdm.server.instance.SharedAttributes.ATTRIBUTE_FORCE_SCHEMA_UPDATE</code>
104
     */
105
    public static final String ATTRIBUTE_FORCE_SCHEMA_UPDATE = "cdm.forceSchemaUpdate";
106

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

    
109
    protected static String beanDefinitionFile = DATASOURCE_BEANDEF_DEFAULT;
110

    
111

    
112
    private String cmdServerInstanceName = null;
113

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

    
124

    
125
    private String dataSourceId = null;
126

    
127
    private DataSource dataSource;
128

    
129
    private DataSourceProperties dataSourceProperties;
130

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

    
136

    
137

    
138
    @Bean
139
    public DataSource dataSource() {
140

    
141

    
142

    
143
        String beanName = findProperty(ATTRIBUTE_DATASOURCE_NAME, true);
144
        String jndiName = null;
145
        if(this.dataSource == null){
146
            jndiName = findProperty(ATTRIBUTE_JDBC_JNDI_NAME, false);
147

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

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

    
161
        // validate correct schema version
162
        try {
163

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

    
179
                connection.close();
180

    
181
                if(!CdmMetaData.isDbSchemaVersionCompatible(version)){
182
                    /*
183
                     * any exception thrown here would be nested into a spring
184
                     * BeanException which can not be caught in the servlet
185
                     * container, so we post the information into the
186
                     * ServletContext
187
                     */
188
                    String errorMessage = "Incompatible version [" + (beanName != null ? beanName : jndiName) + "] expected version: " + CdmMetaData.getDbSchemaVersion() + ",  data base version  " + version;
189
                    addErrorMessageToServletContextAttributes(errorMessage);
190
                }
191
            } else {
192
//            	throw new RuntimeException("database " + dataSource.toString() + " is empty or not a cdm database");
193
                logger.error("database " + dataSource.toString() + " is empty or not a cdm database");
194
            }
195

    
196

    
197
        } catch (SQLException e) {
198
            RuntimeException re =   new RuntimeException("Unable to connect or to retrieve version info from data source " + dataSource.toString() , e);
199
            addErrorMessageToServletContextAttributes(re.getMessage());
200
            throw re;
201

    
202
        }
203

    
204

    
205
        String forceSchemaUpdate = findProperty(ATTRIBUTE_FORCE_SCHEMA_UPDATE, false);
206
        if(forceSchemaUpdate != null){
207
            logger.info("Update of data source requested by property '" + ATTRIBUTE_FORCE_SCHEMA_UPDATE + "'");
208

    
209
            CdmUpdater updater = CdmUpdater.NewInstance();
210
            WrappedCdmDataSource cdmDataSource = new WrappedCdmDataSource(dataSource);
211
            updater.updateToCurrentVersion(cdmDataSource, null);
212
        }
213

    
214
        return dataSource;
215
    }
216

    
217
    @Bean
218
    public DataSourceProperties dataSourceProperties(){
219
        if(this.dataSourceProperties == null){
220
            dataSourceProperties = loadDataSourceProperties();
221
            if(dataSourceId == null){
222
                dataSource();
223
            }
224
            dataSourceProperties.setCurrentDataSourceId(dataSourceId);
225
        }
226
        return dataSourceProperties;
227
    }
228

    
229

    
230
    private DataSource useJndiDataSource(String jndiName) {
231
        logger.info("using jndi datasource '" + jndiName + "'");
232

    
233
        JndiObjectFactoryBean jndiFactory = new JndiObjectFactoryBean();
234
        /*
235
        JndiTemplate jndiTemplate = new JndiTemplate();
236
        jndiFactory.setJndiTemplate(jndiTemplate); no need to use a JndiTemplate
237
        if I try using JndiTemplate I get an org.hibernate.AnnotationException: "Unknown Id.generator: system-increment"
238
        when running multiple instances via the Bootloader
239
        */
240
        jndiFactory.setResourceRef(true);
241
        jndiFactory.setJndiName(jndiName);
242
        try {
243
            jndiFactory.afterPropertiesSet();
244
        } catch (IllegalArgumentException e) {
245
            logger.error(e, e);
246
        } catch (NamingException e) {
247
            logger.error(e, e);
248
        }
249
        Object obj = jndiFactory.getObject();
250
        return (DataSource)obj;
251
    }
252

    
253
    /**
254
     * Loads the {@link DataSource} bean from the cdm bean definition file.
255
     * This file is usually {@code ./.cdmLibrary/datasources.xml}
256
     *
257
     * @param beanName
258
     * @return
259
     */
260
    private DataSource loadDataSourceBean(String beanName) {
261

    
262
        File f = new File("./");
263
        System.err.println(f.getAbsolutePath());
264

    
265
        String beanDefinitionFileFromProperty = findProperty(CDM_BEAN_DEFINITION_FILE, false);
266
        String path = (beanDefinitionFileFromProperty != null ? beanDefinitionFileFromProperty : beanDefinitionFile);
267
        logger.info("loading DataSourceBean '" + beanName + "' from: " + path);
268
        FileSystemResource file = new FileSystemResource(path);
269
        XmlBeanFactory beanFactory  = new XmlBeanFactory(file);
270
        DataSource dataSource = beanFactory.getBean(beanName, DataSource.class);
271
        if(dataSource instanceof ComboPooledDataSource){
272
            logger.info("DataSourceBean '" + beanName + "' is a ComboPooledDataSource [URL:" + ((ComboPooledDataSource)dataSource).getJdbcUrl()+ "]");
273
        } else {
274
            logger.error("DataSourceBean '" + beanName + "' IS NOT a ComboPooledDataSource");
275
        }
276
        return dataSource;
277
    }
278

    
279

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

    
289
        String beanDefinitionFileFromProperty = findProperty(CDM_BEAN_DEFINITION_FILE, false);
290
        String path = (beanDefinitionFileFromProperty != null ? beanDefinitionFileFromProperty : beanDefinitionFile);
291
        logger.info("loading dataSourceProperties from: " + path);
292
        FileSystemResource file = new FileSystemResource(path);
293
        XmlBeanFactory beanFactory  = new XmlBeanFactory(file);
294
        DataSourceProperties properties = null;
295
        try {
296
            properties = beanFactory.getBean("dataSourceProperties", DataSourceProperties.class);
297
        } catch (BeansException e) {
298
            logger.warn("bean 'dataSourceProperties' not found");
299
            properties = new DataSourceProperties();
300
        }
301
        return properties;
302
    }
303

    
304
    @Bean
305
    public Properties hibernateProperties(){
306
        Properties props = getHibernateProperties();
307
        props.setProperty(HIBERNATE_DIALECT, inferHibernateDialectName());
308
        props.setProperty(HIBERNATE_SEARCH_DEFAULT_INDEX_BASE, CdmUtils.getCdmHomeDir().getPath() + "/remote-webapp/index/".replace("/", File.separator) + findProperty(ATTRIBUTE_DATASOURCE_NAME, true));
309
        logger.debug("hibernateProperties: " + props.toString());
310
        return props;
311
    }
312

    
313
    /**
314
     * Returns the full class name of the according {@link org.hibernate.dialect.Dialect} implementation
315
     *
316
     * @param ds the DataSource
317
     * @return the name
318
     */
319
    public String inferHibernateDialectName() {
320
        DataSource ds = dataSource();
321
        return inferHibernateDialectName(ds);
322
    }
323

    
324

    
325

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

    
360
        if(url != null){
361
            if(url.contains(":mysql:")){
362
                // TODO we should switch all databases to InnoDB !
363
                // TODO open jdbc connection to check engine and choose between
364
                // MySQL5MyISAMUtf8Dialect and MySQL5MyISAMUtf8Dialect
365
                // see #3371 (switch cdm to MySQL InnoDB)
366
                return MySQL5MyISAMUtf8Dialect.class.getName();
367
            }
368
            if(url.contains(":h2:")){
369
                return H2CorrectedDialect.class.getName();
370
            }
371
            if(url.contains(":postgresql:")){
372
                return PostgreSQL82Dialect.class.getName();
373
            }
374
        }
375

    
376
        logger.error("hibernate dialect mapping for "+url+ " not yet implemented or unavailable");
377
        return null;
378
    }
379

    
380
    public String cmdServerInstanceName(){
381
        // test for if this is an instance running in a cdmserver:
382
        if(findProperty(ATTRIBUTE_JDBC_JNDI_NAME, false) != null) {
383
            String beanName = findProperty(ATTRIBUTE_DATASOURCE_NAME, true);
384
            cmdServerInstanceName =  beanName;
385
        }
386
        return cmdServerInstanceName;
387
    }
388

    
389
}
(2-2/4)