merge-update from trunk
[cdmlib.git] / cdmlib-persistence / src / main / java / eu / etaxonomy / cdm / database / CdmPersistentDataSource.java
1 /**
2 * Copyright (C) 2007 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.database;
11
12 import static eu.etaxonomy.cdm.common.XmlHelp.getBeansRoot;
13 import static eu.etaxonomy.cdm.common.XmlHelp.insertXmlBean;
14 import static eu.etaxonomy.cdm.common.XmlHelp.insertXmlValueProperty;
15 import static eu.etaxonomy.cdm.common.XmlHelp.saveToXml;
16
17 import java.util.ArrayList;
18 import java.util.Enumeration;
19 import java.util.Iterator;
20 import java.util.List;
21 import java.util.Properties;
22
23 import javax.sql.DataSource;
24
25 import org.apache.log4j.Logger;
26 import org.hibernate.cache.internal.NoCachingRegionFactory;
27 import org.hibernate.cache.spi.RegionFactory;
28 import org.jdom.Attribute;
29 import org.jdom.Element;
30 import org.springframework.beans.MutablePropertyValues;
31 import org.springframework.beans.factory.config.BeanDefinition;
32 import org.springframework.beans.factory.config.PropertiesFactoryBean;
33 import org.springframework.beans.factory.support.AbstractBeanDefinition;
34 import org.springframework.beans.factory.support.RootBeanDefinition;
35
36 import com.mchange.v2.c3p0.ComboPooledDataSource;
37
38 import eu.etaxonomy.cdm.common.CdmUtils;
39 import eu.etaxonomy.cdm.common.XmlHelp;
40 import eu.etaxonomy.cdm.config.CdmPersistentSourceUtils;
41 import eu.etaxonomy.cdm.config.CdmPersistentXMLSource;
42 import eu.etaxonomy.cdm.config.CdmPersistentXMLSource.CdmSourceProperties;
43 import eu.etaxonomy.cdm.config.ICdmPersistentSource;
44 import eu.etaxonomy.cdm.database.types.IDatabaseType;
45 import eu.etaxonomy.cdm.model.name.NomenclaturalCode;
46
47
48 /**
49 * class to access an CdmDataSource
50 */
51 public class CdmPersistentDataSource extends CdmDataSourceBase implements ICdmPersistentSource {
52 private static final Logger logger = Logger.getLogger(CdmPersistentDataSource.class);
53
54 public static final String DATASOURCE_BEAN_POSTFIX = "DataSource";
55
56
57 private String beanName;
58
59 private String database;
60
61 /**
62 * This is strictly a <String, String> list of properties
63 */
64 private Properties cdmSourceProperties;
65
66 private List<Attribute> cdmSourceAttributes;
67
68
69 /**
70 * The Datasource class that Spring will use to set up the connection to the database
71 */
72 private static String dataSourceClassName = ComboPooledDataSource.class.getName();
73
74
75 /**
76 * Returns the default CdmDataSource
77 * @return the default CdmDataSource
78 * @throws DataSourceNotFoundException
79 */
80 public final static CdmPersistentDataSource NewDefaultInstance() throws DataSourceNotFoundException {
81 return NewInstance("default");
82 }
83
84
85 /**
86 * Returns the default CdmDataSource
87 * @return the default CdmDataSource
88 * @throws DataSourceNotFoundException
89 */
90 public final static CdmPersistentDataSource NewLocalHsqlInstance() throws DataSourceNotFoundException{
91 return NewInstance("localDefaultHsql");
92 }
93
94 /**
95 * Returns the CdmDataSource named by strDataSource
96 * @param strDataSource
97 * @return
98 */
99 public final static CdmPersistentDataSource NewInstance(String dataSourceName) throws DataSourceNotFoundException{
100 if (exists(dataSourceName)){
101 return new CdmPersistentDataSource(dataSourceName);
102 }else{
103 throw new DataSourceNotFoundException("Datasource not found: " + dataSourceName);
104 }
105 }
106
107 /**
108 * Private Constructor. Use NewXXX factory methods for creating a new instance of CdmDataSource!
109 * @param strDataSource
110 */
111 private CdmPersistentDataSource(String strDataSource){
112 setName(strDataSource);
113 loadSource(strDataSource);
114 }
115
116 private void loadSource(String strDataSource) {
117 CdmPersistentXMLSource cdmPersistentXMLSource = CdmPersistentXMLSource.NewInstance(strDataSource, DATASOURCE_BEAN_POSTFIX);
118 if(cdmPersistentXMLSource.getElement() != null) {
119 beanName = cdmPersistentXMLSource.getBeanName();
120 // properties from the persistent xml file
121 cdmSourceProperties = cdmPersistentXMLSource.getCdmSourceProperties();
122 cdmSourceAttributes = cdmPersistentXMLSource.getCdmSourceAttributes();
123
124 // added database specific properties if they are null
125 String url = getCdmSourceProperty(CdmSourceProperties.URL);
126 DatabaseTypeEnum dbTypeEnum = getDatabaseType();
127 if (dbTypeEnum != null && url != null){
128 IDatabaseType dbType = dbTypeEnum.getDatabaseType();
129 if (getCdmSourceProperty(CdmSourceProperties.DATABASE) == null){
130 String database = dbType.getDatabaseNameByConnectionString(url);
131 if(database != null) {
132 setDatabase(database);
133 }
134 }
135 if(getCdmSourceProperty(CdmSourceProperties.SERVER) == null){
136 String server = dbType.getServerNameByConnectionString(url);
137 if(server != null) {
138 setServer(server);
139 }
140 }
141 if(getCdmSourceProperty(CdmSourceProperties.PORT) == null){
142 int port = dbType.getPortByConnectionString(url);
143 if(port > 0) {
144 setPort(port);
145 } else {
146 setPort(NULL_PORT);
147 }
148 }
149 }
150 }
151 }
152
153 public String getBeanName() {
154 return beanName;
155 }
156
157 @Override
158 public String getDatabase() {
159 return database;
160 }
161
162
163 @Override
164 public void setDatabase(String database) {
165 this.database = database;
166 //update url string
167 cdmSourceProperties.put(CdmSourceProperties.URL.toString(), getDatabaseType().getConnectionString(this));
168
169 }
170
171 @Override
172 public void setServer(String server) {
173 super.setServer(server);
174 //update url string
175 cdmSourceProperties.put(CdmSourceProperties.URL.toString(), getDatabaseType().getConnectionString(this));
176 }
177
178 @Override
179 public void setPort(int port) {
180 super.setPort(port);
181 if(port != NULL_PORT) {
182 //update url string
183 cdmSourceProperties.put(CdmSourceProperties.URL.toString(), getDatabaseType().getConnectionString(this));
184 }
185 }
186 @Override
187 public String getFilePath() {
188 return getCdmSourceProperty(CdmSourceProperties.FILEPATH);
189 }
190
191
192 @Override
193 public H2Mode getMode() {
194 return H2Mode.fromString(getCdmSourceProperty(CdmSourceProperties.MODE));
195 }
196
197 @Override
198 public void setMode(H2Mode h2Mode) {
199 cdmSourceProperties.put(CdmSourceProperties.MODE.toString(), h2Mode.name());
200
201 }
202
203 @Override
204 public String getUsername(){
205 return getCdmSourceProperty(CdmSourceProperties.USERNAME);
206 }
207
208 @Override
209 public void setUsername(String username) {
210 cdmSourceProperties.put(CdmSourceProperties.USERNAME.toString(), username);
211
212 }
213
214 @Override
215 public String getPassword(){
216 return getCdmSourceProperty(CdmSourceProperties.PASSWORD);
217 }
218
219 @Override
220 public void setPassword(String password) {
221 cdmSourceProperties.put(CdmSourceProperties.PASSWORD.toString(), password);
222
223 }
224
225 @Override
226 public NomenclaturalCode getNomenclaturalCode() {
227 // TODO null
228 return NomenclaturalCode.fromString(getCdmSourceProperty(CdmSourceProperties.NOMENCLATURAL_CODE));
229 }
230
231 @Override
232 public void setNomenclaturalCode(NomenclaturalCode nomenclaturalCode) {
233 cdmSourceProperties.put(CdmSourceProperties.NOMENCLATURAL_CODE.toString(), nomenclaturalCode.name());
234 }
235
236
237
238 @Override
239 public DatabaseTypeEnum getDatabaseType(){
240 String strDriverClass = getCdmSourceProperty(CdmSourceProperties.DRIVER_CLASS);
241 DatabaseTypeEnum dbType = DatabaseTypeEnum.getDatabaseEnumByDriverClass(strDriverClass);
242 return dbType;
243 }
244
245
246 public String getCdmSourceProperty(CdmSourceProperties property){
247 return cdmSourceProperties.getProperty(property.toString(),null);
248 }
249
250 /**
251 * Returns a BeanDefinition object of type DataSource that contains
252 * datsource properties (url, username, password, ...)
253 * @return
254 */
255 @SuppressWarnings("unchecked")
256 @Override
257 public BeanDefinition getDatasourceBean(){
258 DatabaseTypeEnum dbtype =
259 DatabaseTypeEnum.getDatabaseEnumByDriverClass(getCdmSourceProperty(CdmSourceProperties.DRIVER_CLASS));
260
261 AbstractBeanDefinition bd = new RootBeanDefinition(dbtype.getDataSourceClass());
262 //attributes
263 Iterator<Attribute> iterator = cdmSourceAttributes.iterator();
264 while(iterator.hasNext()){
265 Attribute attribute = iterator.next();
266 if (attribute.getName().equals("lazy-init")){
267 bd.setLazyInit(Boolean.valueOf(attribute.getValue()));
268 }
269 if (attribute.getName().equals("init-method")){
270 bd.setInitMethodName(attribute.getValue());
271 }
272 if (attribute.getName().equals("destroy-method")){
273 bd.setDestroyMethodName(attribute.getValue());
274 }
275 //Attribute attribute = iterator.next();
276 //bd.setAttribute(attribute.getName(), attribute.getValue());
277 }
278
279 //properties
280 MutablePropertyValues props = new MutablePropertyValues();
281
282 Enumeration<String> keys = (Enumeration)cdmSourceProperties.keys();
283 while (keys.hasMoreElements()){
284 String key = (String)keys.nextElement();
285
286 if (key.equals("nomenclaturalCode") && cdmSourceProperties.getProperty(key).equals("ICBN")){
287 //bugfix for old nomenclatural codes, remove if fixed elsewhere, see https://dev.e-taxonomy.eu/trac/ticket/3658
288 props.addPropertyValue(key, NomenclaturalCode.ICNAFP.name());
289 }else{
290 props.addPropertyValue(key, cdmSourceProperties.getProperty(key));
291 }
292 }
293
294
295
296 bd.setPropertyValues(props);
297 return bd;
298 }
299
300 /**
301 * @param hbm2dll
302 * @param showSql
303 * @return
304 */
305 @Override
306 public BeanDefinition getHibernatePropertiesBean(DbSchemaValidation hbm2dll){
307 boolean showSql = false;
308 boolean formatSql = false;
309 boolean registerSearchListener = false;
310 Class<? extends RegionFactory> cacheProviderClass = NoCachingRegionFactory.class;
311 return getHibernatePropertiesBean(hbm2dll, showSql, formatSql, registerSearchListener, cacheProviderClass);
312 }
313
314
315 /**
316 * @param hbm2dll
317 * @param showSql
318 * @return
319 */
320 @Override
321 public BeanDefinition getHibernatePropertiesBean(DbSchemaValidation hbm2dll, Boolean showSql, Boolean formatSql, Boolean registerSearchListener, Class<? extends RegionFactory> cacheProviderClass){
322 //Hibernate default values
323 if (hbm2dll == null){
324 hbm2dll = DbSchemaValidation.VALIDATE;
325 }
326 if (showSql == null){
327 showSql = false;
328 }
329 if (formatSql == null){
330 formatSql = false;
331 }
332 if (cacheProviderClass == null){
333 cacheProviderClass = NoCachingRegionFactory.class;
334 }
335 if(registerSearchListener == null){
336 registerSearchListener = false;
337 }
338
339 DatabaseTypeEnum dbtype = getDatabaseType();
340 AbstractBeanDefinition bd = new RootBeanDefinition(PropertiesFactoryBean.class);
341 MutablePropertyValues hibernateProps = new MutablePropertyValues();
342
343 Properties props = new Properties();
344 props.setProperty("hibernate.hbm2ddl.auto", hbm2dll.toString());
345 props.setProperty("hibernate.dialect", dbtype.getHibernateDialectCanonicalName());
346 props.setProperty("hibernate.cache.region.factory_class", cacheProviderClass.getName());
347 props.setProperty("hibernate.show_sql", String.valueOf(showSql));
348 props.setProperty("hibernate.format_sql", String.valueOf(formatSql));
349 props.setProperty("hibernate.search.autoregister_listeners", String.valueOf(registerSearchListener));
350
351 hibernateProps.addPropertyValue("properties",props);
352 bd.setPropertyValues(hibernateProps);
353 return bd;
354 }
355
356
357 /**
358 * Tests existing of the datsource in the according config file.
359 * @return true if a datasource with the given name exists in the according datasource config file.
360 */
361 public static boolean exists(String strDataSourceName){
362 Element bean = CdmPersistentSourceUtils.getCdmSourceBeanXml(strDataSourceName, DATASOURCE_BEAN_POSTFIX);
363 return (bean != null);
364 }
365
366 /**
367 * @param strDataSourceName
368 * @param dataSource
369 * @param code
370 * @return
371 * the updated dataSource, null if not succesful
372 */
373 public static CdmPersistentDataSource update(String strDataSourceName,
374 ICdmDataSource dataSource) throws DataSourceNotFoundException, IllegalArgumentException{
375 CdmPersistentSourceUtils.delete(CdmPersistentSourceUtils.getBeanName(strDataSourceName,DATASOURCE_BEAN_POSTFIX));
376 return save(strDataSourceName, dataSource);
377 }
378
379 /**
380 * Replace the persisted datasource with another one.
381 * Used primarily for renaming a datasource.
382 *
383 * @param strDataSourceName
384 * @param dataSource
385 * @return
386 * @throws DataSourceNotFoundException
387 * @throws IllegalArgumentException
388 */
389 public static CdmPersistentDataSource replace(String strDataSourceName,
390 ICdmDataSource dataSource) throws DataSourceNotFoundException, IllegalArgumentException{
391 CdmPersistentSourceUtils.delete(CdmPersistentSourceUtils.getBeanName(strDataSourceName,DATASOURCE_BEAN_POSTFIX));
392 return save(dataSource);
393 }
394
395 /**
396 * @param dataSource
397 * @return
398 * @throws IllegalArgumentException
399 */
400 public static CdmPersistentDataSource save(ICdmDataSource dataSource) throws IllegalArgumentException {
401 return save(dataSource.getName(),dataSource);
402 }
403
404 /**
405 *
406 * @param strDataSourceName
407 * @param databaseTypeEnum
408 * @param server
409 * @param database
410 * @param port
411 * @param username
412 * @param password
413 * @param dataSourceClass
414 * @param initMethod
415 * @param destroyMethod
416 * @param startSilent
417 * @param startServer
418 * @param filePath
419 * @param mode
420 * @return
421 */
422 private static CdmPersistentDataSource save(String strDataSourceName,
423 DatabaseTypeEnum databaseTypeEnum,
424 String server,
425 String database,
426 String port,
427 String username,
428 String password,
429 Class<? extends DataSource> dataSourceClass,
430 String initMethod,
431 String destroyMethod,
432 Boolean startSilent,
433 Boolean startServer,
434 String filePath,
435 H2Mode mode,
436 NomenclaturalCode code
437 ){
438
439 int portNumber = "".equals(port) ? databaseTypeEnum.getDefaultPort() : Integer.valueOf(port);
440
441 ICdmDataSource dataSource = new CdmDataSource(databaseTypeEnum, server, database, portNumber, username, password, filePath, mode, code);
442
443 //root
444 Element root = getBeansRoot(CdmPersistentSourceUtils.getCdmSourceInputStream());
445 if (root == null){
446 return null;
447 }
448 //bean
449 Element bean = XmlHelp.getFirstAttributedChild(root, "bean", "id", CdmPersistentSourceUtils.getBeanName(strDataSourceName, DATASOURCE_BEAN_POSTFIX));
450 if (bean != null){
451 bean.detach(); //delete old version if necessary
452 }
453 bean = insertXmlBean(root, CdmPersistentSourceUtils.getBeanName(strDataSourceName, DATASOURCE_BEAN_POSTFIX), dataSourceClass.getName());
454 //attributes
455 bean.setAttribute("lazy-init", "true");
456 if (initMethod != null) {bean.setAttribute("init-method", initMethod);}
457 if (destroyMethod != null) {bean.setAttribute("destroy-method", destroyMethod);}
458
459 //set properties
460 insertXmlValueProperty(bean, "driverClassName", databaseTypeEnum.getDriverClassName());
461
462 insertXmlValueProperty(bean, "url", databaseTypeEnum.getConnectionString(dataSource));
463 if (username != null) {insertXmlValueProperty(bean, "username", username );}
464 if (password != null) {insertXmlValueProperty(bean, "password", password );}
465 if (startSilent != null) {insertXmlValueProperty(bean, "startSilent", startSilent.toString() );}
466 if (startServer != null) {insertXmlValueProperty(bean, "startServer", startServer.toString() );}
467 if (filePath != null) {insertXmlValueProperty(bean, "filePath", filePath );}
468 if (mode != null) {insertXmlValueProperty(bean, "mode", mode.toString() );}
469 if (code != null) {insertXmlValueProperty(bean, "nomenclaturalCode", code.name());}
470
471 //save
472 saveToXml(root.getDocument(),
473 CdmPersistentSourceUtils.getResourceDirectory(),
474 CdmPersistentXMLSource.CDMSOURCE_FILE_NAME,
475 XmlHelp.prettyFormat );
476 try {
477 return NewInstance(strDataSourceName) ;
478 } catch (DataSourceNotFoundException e) {
479 logger.error("Error when saving datasource");
480 return null;
481 }
482 }
483
484
485 /**
486 * Saves a datasource to the datasource config file. If strDataSourceName differs a new dataSource
487 * will be created in config file. Use update() of real update functionality.
488 *
489 * @param strDataSourceName
490 * @param dataSource
491 * @return
492 */
493 public static CdmPersistentDataSource save(String strDataSourceName,
494 ICdmDataSource dataSource) throws IllegalArgumentException{
495
496 if(dataSource.getDatabaseType() == null){
497 new IllegalArgumentException("Database type not specified");
498 }
499
500 if(dataSource.getDatabaseType().equals(DatabaseTypeEnum.H2)){
501 Class<? extends DataSource> dataSourceClass = LocalH2.class;
502 if(dataSource.getMode() == null) {
503 new IllegalArgumentException("H2 mode not specified");
504 }
505 return save(
506 strDataSourceName,
507 dataSource.getDatabaseType(),
508 "localhost",
509 getCheckedDataSourceParameter(dataSource.getDatabase()),
510 dataSource.getDatabaseType().getDefaultPort() + "",
511 getCheckedDataSourceParameter(dataSource.getUsername()),
512 getCheckedDataSourceParameter(dataSource.getPassword()),
513 dataSourceClass,
514 null, null, null, null,
515 dataSource.getFilePath(),
516 dataSource.getMode(),
517 dataSource.getNomenclaturalCode());
518 }else{
519
520 Class<? extends DataSource> dataSourceClass;
521 try {
522 dataSourceClass = (Class<? extends DataSource>) Class.forName(dataSourceClassName);
523 String server = getCheckedDataSourceParameter(dataSource.getServer());
524 CdmPersistentDataSource persistendDatasource = save(
525 strDataSourceName,
526 dataSource.getDatabaseType(),
527 getCheckedDataSourceParameter(dataSource.getServer()),
528 getCheckedDataSourceParameter(dataSource.getDatabase()),
529 dataSource.getPort() + "",
530 getCheckedDataSourceParameter(dataSource.getUsername()),
531 getCheckedDataSourceParameter(dataSource.getPassword()),
532 dataSourceClass,
533 null, null, null, null, null, null,
534 dataSource.getNomenclaturalCode());
535
536 return persistendDatasource;
537 } catch (ClassNotFoundException e) {
538 logger.error("DataSourceClass not found - stopping application", e);
539 System.exit(-1);
540 }
541 // will never be reached
542 return null;
543 }
544 }
545
546 private static String getCheckedDataSourceParameter(String parameter) throws IllegalArgumentException{
547 if(parameter != null) {
548 return parameter;
549 } else {
550 throw new IllegalArgumentException("Non obsolete paramater was assigned a null value: " + parameter);
551 }
552 }
553
554
555 /**
556 * Returns a list of all datasources stored in the datasource config file
557 * @return all existing data sources
558 */
559 @SuppressWarnings("unchecked")
560 static public List<CdmPersistentDataSource> getAllDataSources(){
561 List<CdmPersistentDataSource> dataSources = new ArrayList<CdmPersistentDataSource>();
562
563 Element root = getBeansRoot(CdmPersistentSourceUtils.getCdmSourceInputStream());
564 if (root == null){
565 return null;
566 }else{
567 List<Element> lsChildren = root.getChildren("bean", root.getNamespace());
568
569 for (Element elBean : lsChildren){
570 String strId = elBean.getAttributeValue("id");
571 if (strId != null && strId.endsWith(DATASOURCE_BEAN_POSTFIX)){
572 strId = strId.replace(DATASOURCE_BEAN_POSTFIX, "");
573 dataSources.add(new CdmPersistentDataSource(strId));
574 }
575 }
576 }
577 return dataSources;
578 }
579
580
581 @Override
582 public boolean equals(Object obj){
583 if (obj == null){
584 return false;
585 }else if (! CdmPersistentDataSource.class.isAssignableFrom(obj.getClass())){
586 return false;
587 }else{
588 CdmPersistentDataSource dataSource = (CdmPersistentDataSource)obj;
589 return (getName() == dataSource.getName());
590 }
591
592 }
593
594 @Override
595 public String toString(){
596 if (getName() != null){
597 return getName();
598 }else{
599 return null;
600 }
601 }
602
603
604
605
606
607
608
609
610
611 }