(no commit message)
[taxeditor.git] / taxeditor-store / src / main / java / eu / etaxonomy / taxeditor / store / CdmStore.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.taxeditor.store;
11
12 import java.lang.reflect.InvocationTargetException;
13 import java.sql.DatabaseMetaData;
14 import java.sql.ResultSet;
15 import java.sql.SQLException;
16 import java.util.ArrayList;
17 import java.util.List;
18
19 import org.apache.log4j.Logger;
20 import org.eclipse.core.runtime.IProgressMonitor;
21 import org.eclipse.core.runtime.ListenerList;
22 import org.eclipse.core.runtime.NullProgressMonitor;
23 import org.eclipse.jface.operation.IRunnableWithProgress;
24 import org.springframework.core.io.ClassPathResource;
25 import org.springframework.core.io.Resource;
26 import org.springframework.security.authentication.ProviderManager;
27
28 import eu.etaxonomy.cdm.api.application.CdmApplicationController;
29 import eu.etaxonomy.cdm.api.conversation.ConversationHolder;
30 import eu.etaxonomy.cdm.api.service.IAgentService;
31 import eu.etaxonomy.cdm.api.service.ICommonService;
32 import eu.etaxonomy.cdm.api.service.IFeatureNodeService;
33 import eu.etaxonomy.cdm.api.service.IFeatureTreeService;
34 import eu.etaxonomy.cdm.api.service.ILocationService;
35 import eu.etaxonomy.cdm.api.service.IMediaService;
36 import eu.etaxonomy.cdm.api.service.INameService;
37 import eu.etaxonomy.cdm.api.service.IOccurrenceService;
38 import eu.etaxonomy.cdm.api.service.IReferenceService;
39 import eu.etaxonomy.cdm.api.service.ITaxonNodeService;
40 import eu.etaxonomy.cdm.api.service.ITaxonService;
41 import eu.etaxonomy.cdm.api.service.ITaxonTreeService;
42 import eu.etaxonomy.cdm.api.service.ITermService;
43 import eu.etaxonomy.cdm.api.service.IUserService;
44 import eu.etaxonomy.cdm.api.service.IVocabularyService;
45 import eu.etaxonomy.cdm.api.service.config.IIdentifiableEntityServiceConfigurator;
46 import eu.etaxonomy.cdm.api.service.config.ITaxonServiceConfigurator;
47 import eu.etaxonomy.cdm.database.DataSourceNotFoundException;
48 import eu.etaxonomy.cdm.database.DatabaseTypeEnum;
49 import eu.etaxonomy.cdm.database.DbSchemaValidation;
50 import eu.etaxonomy.cdm.database.ICdmDataSource;
51 import eu.etaxonomy.cdm.ext.geo.IEditGeoService;
52 import eu.etaxonomy.cdm.model.agent.AgentBase;
53 import eu.etaxonomy.cdm.model.agent.TeamOrPersonBase;
54 import eu.etaxonomy.cdm.model.common.CdmMetaData;
55 import eu.etaxonomy.cdm.model.common.CdmMetaData.MetaDataPropertyName;
56 import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
57 import eu.etaxonomy.cdm.model.common.Language;
58 import eu.etaxonomy.cdm.model.name.TaxonNameBase;
59 import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
60 import eu.etaxonomy.cdm.model.reference.ReferenceBase;
61 import eu.etaxonomy.cdm.model.taxon.TaxonomicTree;
62 import eu.etaxonomy.taxeditor.datasource.CdmDataSourceRepository;
63 import eu.etaxonomy.taxeditor.datasource.view.CdmDataSourceViewPart;
64 import eu.etaxonomy.taxeditor.dialogs.LoginDialog;
65 import eu.etaxonomy.taxeditor.io.ExportHandler;
66 import eu.etaxonomy.taxeditor.io.ImportHandler;
67 import eu.etaxonomy.taxeditor.model.CdmProgressMonitorAdapter;
68 import eu.etaxonomy.taxeditor.model.IContextListener;
69 import eu.etaxonomy.taxeditor.model.IContextListener.EventType;
70 import eu.etaxonomy.taxeditor.store.internal.TaxeditorStorePlugin;
71
72 /**
73 * This implementation of ICdmDataRepository depends on hibernate sessions to store the data correctly
74 * for the current session. No state is held in this class.
75 *
76 * Only methods that either get or manipulate data are exposed here. So this class acts as a facade
77 * for the methods in cdmlib-service.
78 *
79 *
80 * @author n.hoffmann
81 * @created 17.03.2009
82 * @version 1.0
83 */
84 public class CdmStore{
85 private static final Logger logger = Logger.getLogger(CdmStore.class);
86
87 private static class CdmDataStoreConnector implements IRunnableWithProgress{
88 private ICdmDataSource datasource;
89 private DbSchemaValidation dbSchemaValidation;
90 private Resource applicationContextBean;
91 private IProgressMonitor monitor;
92
93 /**
94 * @param datasource
95 * @param dbSchemaValidation
96 * @param applicationContextBean
97 */
98 public CdmDataStoreConnector(ICdmDataSource datasource,
99 DbSchemaValidation dbSchemaValidation,
100 Resource applicationContextBean) {
101 this.datasource = datasource;
102 this.dbSchemaValidation = dbSchemaValidation;
103 this.applicationContextBean = applicationContextBean;
104 }
105
106
107
108 public void run(IProgressMonitor progressMonitor) throws InvocationTargetException, InterruptedException {
109
110 this.monitor = progressMonitor != null ? progressMonitor : new NullProgressMonitor();
111
112 monitor.beginTask(getConnectionMessage(), 10);
113
114 checkDatabaseReachable();
115
116 if(! monitor.isCanceled()){
117 checkDatabaseNotEmpty();
118 }
119
120 if(! monitor.isCanceled()){
121 checkDbSchemaVersionCompatibility();
122 }
123
124 // we are done with our low level checking and will free resources now
125 datasource.closeOpenConnections();
126
127 if(! monitor.isCanceled()){
128 instance = new CdmStore(datasource, dbSchemaValidation, applicationContextBean, new CdmProgressMonitorAdapter(monitor));
129 notifyContextStart();
130 logger.info("Application context initialized.");
131 }else{
132 // Show datasource view if not shown yet
133 StoreUtil.showView(CdmDataSourceViewPart.ID);
134 }
135
136 monitor.done();
137 }
138
139
140
141 /**
142 * @return
143 */
144 private String getConnectionMessage() {
145 String message = "";
146 if(datasource.getDatabaseType().equals(DatabaseTypeEnum.H2)){
147 message = " local CDM Store ";
148 }else{
149 message = " CDM Community Store ";
150 }
151 message += "'" + datasource.getName() + "'";
152
153 message = "Establishing connection to" + message + ". \nThis might take while.";
154
155 return message;
156 }
157
158 /**
159 * @return
160 * @throws SQLException
161 */
162 private void checkDbSchemaVersionCompatibility() {
163 String dbSchemaVersion;
164 boolean result = false;
165 try {
166 dbSchemaVersion = (String) datasource.getSingleValue(MetaDataPropertyName.DB_SCHEMA_VERSION.getSqlQuery());
167 // we assume that empty dbSchemaVersion means an empty database and skip version checking
168 result = dbSchemaVersion == null ? true : CdmMetaData.isDbSchemaVersionCompatible(dbSchemaVersion);
169 } catch (SQLException e) {
170 //
171 }
172
173 if(!result){
174 // Show an error message
175 StoreUtil.errorDialog("DatabaseCompatibilityCheck failed", "The database schema for the chosen " +
176 "datasource '" + datasource + "' \n is not valid for this version of the taxonomic editor. \n" +
177 "Please update the chosen datasource or choose a new data source to connect to in the Datasource View.");
178
179 monitor.setCanceled(true);
180 }
181
182 }
183
184 private void checkDatabaseNotEmpty() {
185 monitor.subTask("Checking if datasource is not empty.");
186 DatabaseMetaData metaData = datasource.getMetaData();
187 if(metaData != null){
188 try {
189 ResultSet resultSet = metaData.getTables(datasource.getDatabase(), null, null, null);
190 if (resultSet != null){
191 if(resultSet.next()){
192 return;
193 }else{
194 if(StoreUtil.confirmDialog("Database is empty", "Do you want to create the datasource?")){
195 dbSchemaValidation = DbSchemaValidation.CREATE;
196 return;
197 }
198 }
199 }
200 } catch (SQLException e) {
201 StoreUtil.errorDialog("Error while trying to retrieve database metadata", "Something is utterly wrong with your database.");
202 logger.error(e);
203 }
204 }
205
206 monitor.setCanceled(true);
207 }
208
209 private void checkDatabaseReachable(){
210 try {
211 monitor.subTask("Checking if datasource is reachable.");
212 datasource.testConnection();
213 monitor.worked(1);
214 } catch (DataSourceNotFoundException e) {
215 StoreUtil.errorDialog("Chosen Datasource is not available", "Could not connect to the chosen " +
216 "datasource '" + datasource + "'. Please check settings in datasources.xml. If the datasource " +
217 "is located on a remote machine, make sure you are connected to the network.");
218 monitor.setCanceled(true);
219 }
220
221 }
222 }
223
224 private static final Resource DEFAULT_APPLICATION_CONTEXT = new ClassPathResource("/eu/etaxonomy/cdm/editorApplicationContext.xml", TaxeditorStorePlugin.class.getClassLoader());
225 private static final DbSchemaValidation DEFAULT_DB_SCHEMA_VALIDATION = DbSchemaValidation.VALIDATE;
226
227 private static CdmStore instance;
228
229 private CdmApplicationController applicationController;
230
231 private static LoginManager loginManager;
232
233 private static ImportHandler importHandler;
234
235 private static ExportHandler exportHandler;
236
237 private Language language;
238
239 private static ListenerList contextListeners = new ListenerList();
240
241 private static ICdmDataSource cdmDatasource;
242
243 private boolean isConnected;
244
245 /**
246 *
247 * @param applicationContextBean
248 * @return
249 */
250 protected static CdmStore getDefault(){
251 if(instance != null && instance.isConnected){
252 return instance;
253 }else if(instance == null){
254
255 initialize();
256
257 return instance;
258 }else if(!instance.isConnected){
259
260 StoreUtil.warningDialog("No datasource connection", "Application is not connected to a datastore.");
261
262 // TODO open datasource view
263
264 }
265 return null;
266 }
267
268 /**
269 * Initialize the with the last edited datasource
270 */
271 public static void initialize() {
272
273 ICdmDataSource datasource = CdmDataSourceRepository.getCurrentDataSource();
274
275 initialize(datasource);
276 }
277
278 /**
279 * Initialize with a specific datasource
280 *
281 * @param datasource
282 */
283 public static void initialize(ICdmDataSource datasource) {
284 initialize(datasource, DEFAULT_DB_SCHEMA_VALIDATION, DEFAULT_APPLICATION_CONTEXT);
285 }
286
287 /**
288 * Initialize and provide
289 *
290 * @param datasource
291 * @param dbSchemaValidation
292 * @param applicationContextBean
293 */
294 private static void initialize(final ICdmDataSource datasource, final DbSchemaValidation dbSchemaValidation, final Resource applicationContextBean){
295 if(isActive()){
296 logger.error("Application context already initialized.");
297 return;
298 }
299
300 logger.info("Initializing application context ...");
301
302 try {
303
304 CdmDataStoreConnector runnable = new CdmDataStoreConnector(datasource, dbSchemaValidation, applicationContextBean);
305
306 StoreUtil.run(false, false, runnable);
307
308 } catch (Exception e){
309 StoreUtil.errorDialog("Error trying to connect to datasource", "An error occurred while connecting to the datasource." +
310 "Please refer to the error log.");
311 logger.error(e);
312 throw new RuntimeException(e);
313 }
314 }
315
316
317
318 /**
319 * Closes the current application context
320 */
321 public static void close(){
322 notifyContextAboutToStop();
323 if(isActive() && StoreUtil.closeAll()){
324 notifyContextStop();
325 instance.getApplicationController().close();
326 instance.isConnected = false;
327 cdmDatasource = null;
328
329 }
330 }
331
332 /**
333 *
334 */
335 private CdmStore(ICdmDataSource dataSource, DbSchemaValidation dbSchemaValidation, Resource applicationContextBean, eu.etaxonomy.cdm.common.IProgressMonitor progressMonitor) {
336 try {
337 // this should be more modulized
338 applicationController = CdmApplicationController.NewInstance(applicationContextBean, dataSource, dbSchemaValidation, false, progressMonitor);
339 //
340 isConnected = true;
341
342 cdmDatasource = dataSource;
343 } catch (Exception e) {
344 throw new RuntimeException(e);
345 }
346 }
347
348 /**
349 * All calls to the datastore require
350 *
351 * @return
352 */
353 private CdmApplicationController getApplicationController(){
354 try{
355 return applicationController;
356 }catch(Exception e){
357 logger.error("Exception thrown", e);
358 }
359 return null;
360 }
361
362 public static CdmApplicationController getCurrentApplicationController(){
363 return getDefault().getApplicationController();
364 }
365
366 /*
367 * CONVERSATIONS
368 */
369
370 /**
371 * Creates a new conversation, binds resources to the conversation and
372 * start a transaction for this conversation.
373 *
374 * @return
375 */
376 public static ConversationHolder createConversation() {
377 ConversationHolder conversation = getDefault().getApplicationController().NewConversation();
378
379 conversation.startTransaction();
380 return conversation;
381 }
382
383 /*
384 * EXPOSING SERVICES
385 */
386
387 public static ITaxonService getTaxonService(){ return getDefault().getApplicationController().getTaxonService();}
388
389 public static ITaxonTreeService getTaxonTreeService() { return getDefault().getApplicationController().getTaxonTreeService();}
390
391 public static ITaxonNodeService getTaxonNodeService() { return getDefault().getApplicationController().getTaxonNodeService();}
392
393 public static INameService getNameService(){ return getDefault().getApplicationController().getNameService();}
394
395 public static IReferenceService getReferenceService(){ return getDefault().getApplicationController().getReferenceService();}
396
397 public static ILocationService getLocationService(){ return getDefault().getApplicationController().getLocationService();}
398
399 public static ProviderManager getAuthenticationManager() { return getDefault().getApplicationController().getAuthenticationManager();}
400
401 public static IUserService getUserService() { return getDefault().getApplicationController().getUserService(); }
402
403 public static ICommonService getCommonService() { return getDefault().getApplicationController().getCommonService(); }
404
405 public static IAgentService getAgentService() { return getDefault().getApplicationController().getAgentService(); }
406
407 public static ITermService getTermService() { return getDefault().getApplicationController().getTermService(); }
408
409 public static IVocabularyService getVocabularyService() { return getDefault().getApplicationController().getVocabularyService(); }
410
411 public static IMediaService getMediaService() { return getDefault().getApplicationController().getMediaService(); }
412
413 public static IOccurrenceService getOccurrenceService() { return getDefault().getApplicationController().getOccurrenceService(); }
414
415 public static IFeatureTreeService getFeatureTreeService() { return getDefault().getApplicationController().getFeatureTreeService(); }
416
417 public static IFeatureNodeService getFeatureNodeService() { return getDefault().getApplicationController().getFeatureNodeService(); }
418
419 public static IEditGeoService getGeoService(){
420 return (IEditGeoService) getDefault().getApplicationController().getBean("editGeoService");
421 }
422
423 /*
424 * METHODS TO FIND ENTITIES
425 */
426
427 /**
428 *
429 * @param configurator
430 * @return
431 */
432 public static List<TaxonNameBase> findNames(IIdentifiableEntityServiceConfigurator configurator){
433 // TODO we want to use IIdentifiableEntityServiceConfigurator for all find methods
434 // unfortunately this is not consistently implemented in the library.
435 // FIXME use proper method once it is implemented in the library
436 String titleSearchString = configurator.getTitleSearchString().replace("*", "%");
437
438 return getNameService().getNamesByName(titleSearchString);
439 }
440
441 /**
442 *
443 * @param configurator
444 * @return
445 */
446 public static List<IdentifiableEntity> findTaxaAndNames(ITaxonServiceConfigurator configurator){
447 return getTaxonService().findTaxaAndNames(configurator).getRecords();
448 }
449
450 /**
451 *
452 * @param configurator
453 * @return
454 */
455 public static List<ReferenceBase> findReferences(IIdentifiableEntityServiceConfigurator configurator){
456 // TODO we want to use IIdentifiableEntityServiceConfigurator for all find methods
457 // unfortunately this is not consistently implemented in the library.
458 // FIXME use proper method once it is implemented in the library
459 String titleSearchString = configurator.getTitleSearchString().replace("*", "%");
460
461 return getReferenceService().findByTitle(null, titleSearchString, null, null, null, null, null, null).getRecords();
462 }
463
464 /**
465 *
466 * @param configurator
467 * @return
468 */
469 public static List<AgentBase> findAgents(IIdentifiableEntityServiceConfigurator configurator){
470 // TODO we want to use IIdentifiableEntityServiceConfigurator for all find methods
471 // unfortunately this is not consistently implemented in the library.
472 // FIXME use proper method once it is implemented in the library
473 String titleSearchString = configurator.getTitleSearchString().replace("*", "%");
474
475 return getAgentService().findByTitle(null, titleSearchString, null, null, null, null, null, null).getRecords();
476 }
477
478 public static List<TeamOrPersonBase> findTeamOrPersons(IIdentifiableEntityServiceConfigurator configurator){
479 // TODO move this to cdmlib
480 List<TeamOrPersonBase> result = new ArrayList<TeamOrPersonBase>();
481 for (AgentBase agent : findAgents(configurator)) {
482 if (agent instanceof TeamOrPersonBase) {
483 result.add((TeamOrPersonBase) agent);
484 }
485 }
486 return result;
487 }
488
489 public static List<SpecimenOrObservationBase> findOccurrences(IIdentifiableEntityServiceConfigurator configurator){
490 // TODO we want to use IIdentifiableEntityServiceConfigurator for all find methods
491 // unfortunately this is not consistently implemented in the library.
492 // FIXME use proper method once it is implemented in the library
493 String titleSearchString = configurator.getTitleSearchString().replace("*", "%");
494
495 return getOccurrenceService().findByTitle(SpecimenOrObservationBase.class, titleSearchString, null, null, null, null, null, null).getRecords();
496 }
497
498 /*
499 * LANGUAGE
500 */
501
502 /**
503 * @return
504 */
505 public static Language getDefaultLanguage(){
506 if(getDefault().getLanguage() == null){
507 getDefault().setLanguage(Language.DEFAULT());
508 }
509 return getDefault().getLanguage();
510 }
511
512 public static void setDefaultLanguage(Language language){
513 getDefault().setLanguage(language);
514 }
515
516 /**
517 * @return the language
518 */
519 private Language getLanguage() {
520 return language;
521 }
522
523 /**
524 * @param language the language to set
525 */
526 private void setLanguage(Language language) {
527 this.language = language;
528 }
529
530 /*
531 * LOGIN
532 */
533
534 /**
535 *
536 */
537 public static LoginManager getLoginManager(){
538 if(loginManager == null){
539 loginManager = new LoginManager();
540 }
541 return loginManager;
542 }
543
544 private int authenticate(){
545
546 LoginDialog loginDialog = new LoginDialog(StoreUtil.getShell());
547 return loginDialog.open();
548 }
549
550 /*
551 * IMPORT/EXPORT FACTORIES
552 */
553
554 /**
555 *
556 */
557 public static synchronized ImportHandler getImportHandler(){
558 if(importHandler == null){
559 importHandler = ImportHandler.NewInstance(getDefault().getApplicationController());
560 }
561 return importHandler;
562 }
563
564 /**
565 *
566 * @return
567 */
568 public static synchronized ExportHandler getExportHandler(){
569 if(exportHandler == null){
570 exportHandler = ExportHandler.NewInstance(getDefault().getApplicationController());
571 }
572 return exportHandler;
573 }
574
575 /**
576 * Whether this CdmStore is currently connected to a datasource
577 * @return
578 */
579 public static boolean isActive(){
580 return instance != null && instance.isConnected;
581 }
582
583 public static void addContextListener(IContextListener listener){
584 contextListeners.add(listener);
585 }
586
587 public static void removeContextListener(IContextListener listener) {
588 contextListeners.remove(listener);
589 }
590
591 public static void notifyContextStart() {
592 logger.info("Notifying context listeners, that the context has started.");
593
594 instance.authenticate();
595
596
597 for(Object listener : contextListeners.getListeners()){
598 ((IContextListener) listener).onContextEvent(EventType.START);
599 }
600 }
601
602 public static void notifyContextAboutToStop(){
603 for(Object listener : contextListeners.getListeners()){
604 ((IContextListener) listener).onContextEvent(EventType.ABOUT_TO_STOP);
605 }
606 }
607
608 public static void notifyContextStop() {
609 logger.warn("Application event occured");
610
611 for(Object listener : contextListeners.getListeners()){
612 ((IContextListener) listener).onContextEvent(EventType.STOP);
613 }
614 }
615
616 public static ICdmDataSource getDataSource(){
617 if(isActive()){
618 return cdmDatasource;
619 }
620 return null;
621 }
622
623 public static void createDefaultClassification(ConversationHolder conversation){
624 TaxonomicTree defaultClassification = TaxonomicTree.NewInstance("My Classification");
625 getTaxonTreeService().saveOrUpdate(defaultClassification);
626 conversation.commit(true);
627 }
628 }