Project

General

Profile

Download (15.4 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.database.WrappedCdmDataSource;
40
import eu.etaxonomy.cdm.database.update.CdmUpdater;
41
import eu.etaxonomy.cdm.model.metadata.CdmMetaData;
42
import eu.etaxonomy.cdm.model.metadata.CdmMetaData.MetaDataPropertyName;
43

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

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

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

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

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

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

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

    
110
    protected static String beanDefinitionFile = DATASOURCE_BEANDEF_DEFAULT;
111

    
112

    
113
    private String cmdServerInstanceName = null;
114

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

    
125

    
126
    private String dataSourceId = null;
127

    
128
    private DataSource dataSource;
129

    
130
    private DataSourceProperties dataSourceProperties;
131

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

    
137

    
138

    
139
    @Bean
140
    public DataSource dataSource() {
141

    
142

    
143

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

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

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

    
162
        // validate correct schema version
163
        try {
164

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

    
180
                connection.close();
181

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

    
197

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

    
203
        }
204

    
205

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

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

    
215
        return dataSource;
216
    }
217

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

    
230

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

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

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

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

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

    
280

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

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

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

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

    
325

    
326

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

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

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

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

    
390
}
(2-2/4)