Merge branch 'release/4.0.0'
[taxeditor.git] / eu.etaxonomy.taxeditor.store / src / main / java / eu / etaxonomy / taxeditor / store / CdmStoreConnector.java
1 // $Id$
2 /**
3 * Copyright (C) 2007 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.taxeditor.store;
12
13 import java.lang.reflect.Constructor;
14 import java.lang.reflect.InvocationTargetException;
15 import java.sql.SQLException;
16 import java.util.concurrent.CancellationException;
17
18 import org.eclipse.core.runtime.IProgressMonitor;
19 import org.eclipse.core.runtime.IStatus;
20 import org.eclipse.core.runtime.Status;
21 import org.eclipse.core.runtime.jobs.Job;
22 import org.eclipse.jface.dialogs.ProgressMonitorDialog;
23 import org.eclipse.jface.operation.IRunnableWithProgress;
24 import org.eclipse.swt.widgets.Display;
25 import org.springframework.core.io.Resource;
26
27 import eu.etaxonomy.cdm.api.application.CdmApplicationController;
28 import eu.etaxonomy.cdm.api.application.CdmApplicationRemoteController;
29 import eu.etaxonomy.cdm.api.application.ICdmApplicationConfiguration;
30 import eu.etaxonomy.cdm.config.CdmSourceException;
31 import eu.etaxonomy.cdm.config.ICdmSource;
32 import eu.etaxonomy.cdm.database.DbSchemaValidation;
33 import eu.etaxonomy.cdm.database.ICdmDataSource;
34 import eu.etaxonomy.cdm.model.common.DefinedTermBase;
35 import eu.etaxonomy.cdm.model.metadata.CdmMetaData;
36 import eu.etaxonomy.cdm.model.name.NomenclaturalStatusType;
37 import eu.etaxonomy.cdm.model.name.Rank;
38 import eu.etaxonomy.taxeditor.model.CdmProgressMonitorAdapter;
39 import eu.etaxonomy.taxeditor.model.MessagingUtils;
40 import eu.etaxonomy.taxeditor.remoting.source.ICdmRemoteSource;
41 import eu.etaxonomy.taxeditor.ui.dialog.LoginDialog;
42 import eu.etaxonomy.taxeditor.ui.dialog.RemotingLoginDialog;
43 import eu.etaxonomy.taxeditor.view.datasource.CdmDataSourceViewPart;
44
45
46 /**
47 * @author n.hoffmann
48 * @created Dec 8, 2010
49 * @version 1.0
50 */
51 class CdmStoreConnector extends Job {
52 private final Display display;
53 private final ICdmSource cdmSource;
54 private DbSchemaValidation dbSchemaValidation;
55 private final Resource applicationContextBean;
56
57
58 /**
59 * @param datasource
60 * @param dbSchemaValidation
61 * @param applicationContextBean
62 */
63 public CdmStoreConnector(Display display,
64 ICdmSource cdmSource,
65 DbSchemaValidation dbSchemaValidation,
66 Resource applicationContextBean) {
67 super("Connecting to datasource: " + cdmSource);
68 this.display = display;
69 this.cdmSource = cdmSource;
70 this.dbSchemaValidation = dbSchemaValidation;
71 this.applicationContextBean = applicationContextBean;
72 }
73
74
75
76 @Override
77 public IStatus run(final IProgressMonitor monitor) {
78
79 monitor.beginTask(getConnectionMessage(), 10);
80
81 // check if database is up and running
82 checkDatabaseReachable(monitor);
83
84 if (!monitor.isCanceled()) {
85 // check if the datasource actually holds data
86 checkIsNonEmptyCdmDatabase(monitor);
87 }
88
89 if (dbSchemaValidation != DbSchemaValidation.CREATE
90 && !monitor.isCanceled()) {
91 // if we do not create the datasource, we want to check if the
92 // datasource is compatible with this editor
93 checkDbSchemaVersionCompatibility(monitor);
94 }
95
96 // we are done with our low level checking and will free resources now
97 cdmSource.closeOpenConnections();
98
99 if (!monitor.isCanceled()) {
100 CdmStore.close(monitor);
101 }
102
103 ICdmApplicationConfiguration applicationController = null;
104
105 if (!monitor.isCanceled()) {
106 CdmProgressMonitorAdapter subprogressMonitor = CdmProgressMonitorAdapter
107 .CreateSubMonitor(monitor, 7);
108 // This is where we instantiate the application controller
109 int oldPriority = Thread.currentThread().getPriority();
110 try {
111 Thread.currentThread().setPriority(10);
112 applicationController = getApplicationController(cdmSource,subprogressMonitor);
113 } catch (Exception e) {
114 if(! causeIsCancelationExceptionRecursive(e)){
115 return new Status(IStatus.ERROR, "Could not connect to CDM Store", "An error occurred while trying to connect to datasource: " + cdmSource.getName(), e);
116 }
117 } finally {
118 monitor.done();
119 Thread.currentThread().setPriority(oldPriority);
120 }
121 }
122
123
124
125 if (!monitor.isCanceled()) {
126 CdmStore.setInstance(applicationController, cdmSource);
127
128 display.asyncExec(new Runnable() {
129 /*
130 * (non-Javadoc)
131 *
132 * @see java.lang.Runnable#run()
133 */
134 @Override
135 public void run() {
136 authenticate();
137
138 startContext();
139 }
140 });
141
142 MessagingUtils.info("Application context initialized.");
143 return Status.OK_STATUS;
144 } else {
145 // Show datasource view if not shown yet
146 display.asyncExec(new Runnable() {
147 /*
148 * (non-Javadoc)
149 *
150 * @see java.lang.Runnable#run()
151 */
152 @Override
153 public void run() {
154 StoreUtil.showView(CdmDataSourceViewPart.ID);
155 }
156 });
157 return Status.CANCEL_STATUS;
158 }
159
160 }
161
162 public void start(final RemotingLoginDialog loginDialog) {
163 // hide login dialog and start connection dialog
164 loginDialog.setMessage(null);
165 loginDialog.hide(true);
166
167
168 ProgressMonitorDialog dialog = new ProgressMonitorDialog(StoreUtil.getShell());
169
170 try {
171 dialog.run(true, true, new IRunnableWithProgress() {
172 @Override
173 public void run(final IProgressMonitor monitor) {
174 try {
175 monitor.beginTask(getConnectionMessage(), 7);
176
177 // check if database is up and running
178 checkDatabaseReachable(monitor);
179
180 // check if the datasource actually holds data
181 checkIsNonEmptyCdmDatabase(monitor);
182
183 if (dbSchemaValidation != DbSchemaValidation.CREATE) {
184 // if we do not create the datasource, we want to check if the
185 // datasource is compatible with this editor
186 checkDbSchemaVersionCompatibility(monitor);
187 }
188
189 // we are done with our low level checking and will free resources now
190 cdmSource.closeOpenConnections();
191
192 display.syncExec(new Runnable() {
193 /*
194 * (non-Javadoc)
195 *
196 * @see java.lang.Runnable#run()
197 */
198 @Override
199 public void run() {
200 // close the current context
201 CdmStore.close(monitor, false);
202 }
203 });
204
205 ICdmApplicationConfiguration applicationController = null;
206
207 if (!monitor.isCanceled()) {
208 CdmProgressMonitorAdapter subprogressMonitor = CdmProgressMonitorAdapter
209 .CreateSubMonitor(monitor, 3);
210 // genrerate new application controller
211 applicationController = getApplicationController(cdmSource,subprogressMonitor);
212 }
213
214
215 if (!monitor.isCanceled()) {
216 CdmStore.setInstance(applicationController, cdmSource);
217 monitor.subTask("Authenticating user");
218 display.syncExec(new Runnable() {
219 /*
220 * (non-Javadoc)
221 *
222 * @see java.lang.Runnable#run()
223 */
224 @Override
225 public void run() {
226
227 try {
228 // create new security context
229 CdmStore.getLoginManager().doAuthenticate(loginDialog.getUsername(), loginDialog.getPassword());
230 loginDialog.onComplete();
231 CdmStore.getContextManager().notifyContextStart();
232 getInstance(Rank.class).resetTerms();
233 getInstance(NomenclaturalStatusType.class).resetTerms();
234 Rank.initDefaultTerms();
235 NomenclaturalStatusType.initDefaultTerms();
236 } catch(CdmAuthenticationException cae) {
237 loginDialog.hide(false);
238 loginDialog.setMessage(cae.getMessage());
239 }
240
241 }
242 });
243 } else {
244 throw new RuntimeException("Login cancelled");
245 }
246 } finally {
247 monitor.done();
248 }
249 }
250 });
251 } catch (InvocationTargetException e) {
252 loginDialog.hide(false);
253 loginDialog.setMessage(e.getMessage());
254 } catch (InterruptedException e) {
255 loginDialog.hide(false);
256 loginDialog.setMessage(e.getMessage());
257 }
258 }
259
260 /**
261 * Returns a new instance for the given class by using the default constructor.
262 * The constructor must be declared but can be unaccessible (e.g. private)
263 * @param termClass
264 * @return
265 */
266 private <T extends DefinedTermBase> T getInstance(Class<? extends DefinedTermBase> termClass) {
267 try {
268 Constructor<T> c = ((Class<T>)termClass).getDeclaredConstructor();
269 c.setAccessible(true);
270 T termInstance = c.newInstance();
271 return termInstance;
272 } catch (Exception e) {
273 throw new RuntimeException(e);
274 }
275 }
276
277
278 private ICdmApplicationConfiguration getApplicationController(ICdmSource cdmSource, CdmProgressMonitorAdapter subprogressMonitor) {
279 if(cdmSource instanceof ICdmDataSource) {
280 return CdmApplicationController.NewInstance(applicationContextBean,
281 (ICdmDataSource)cdmSource,
282 dbSchemaValidation,
283 false,
284 subprogressMonitor);
285 } else if(cdmSource instanceof ICdmRemoteSource) {
286 return CdmApplicationRemoteController.NewInstance((ICdmRemoteSource)cdmSource,
287 subprogressMonitor,
288 null);
289 } else {
290 throw new UnsupportedOperationException("Cannot create application controller for " + cdmSource.getName());
291 }
292 }
293
294 private void authenticate() {
295 LoginDialog saloginDialog = new LoginDialog(StoreUtil.getShell());
296 saloginDialog.open();
297 }
298
299 private void startContext() {
300 CdmStore.getContextManager().notifyContextStart();
301 }
302
303 /**
304 * @return
305 */
306 private String getConnectionMessage() {
307 return cdmSource.getConnectionMessage();
308 }
309
310 /**
311 * @return
312 * @throws SQLException
313 */
314 private void checkDbSchemaVersionCompatibility(IProgressMonitor monitor) {
315 monitor.subTask("Checking if datasource is compatible with this editor.");
316 String dbSchemaVersion;
317
318 String message = null;
319 try {
320 dbSchemaVersion = cdmSource.getDbSchemaVersion();
321 // we assume that empty dbSchemaVersion means an empty database and
322 // skip version checking
323
324 if(dbSchemaVersion != null) {
325 int compareVersion = CdmMetaData.compareVersion(dbSchemaVersion, CdmMetaData.getDbSchemaVersion(), 3, null);
326 // if the datasource version is greater than the taxeditor compatible version then the taxeditor needs to
327 // be updated else the datasource needs to be updated
328 if(compareVersion > 0) {
329 message = "Please update the Taxonomic Editor (Help->Check for Updates) or choose a compatible datasource";
330 } else if (compareVersion < 0) {
331 message = "Please update the chosen datasource or choose a new data source to connect to in the Datasource View.";
332 }
333 }
334 monitor.worked(1);
335 } catch (CdmSourceException e) {
336 //
337 }
338
339 if (message != null) {
340 // Show an error message
341 MessagingUtils
342 .messageDialog(
343 "Datasource Compatibility Check failed",
344 this,
345 "The database schema for the chosen "
346 + "datasource '"
347 + cdmSource
348 + "' \n is not compatible for this version of the taxonomic editor. \n\n"
349 + message,
350 null);
351
352 monitor.setCanceled(true);
353 }
354
355 }
356
357 private void checkIsNonEmptyCdmDatabase(IProgressMonitor monitor) {
358 monitor.subTask("Checking if datasource is a non empty CDM database.");
359 boolean isDbEmpty = false;
360 try {
361 isDbEmpty = cdmSource.isDbEmpty();
362 } catch (CdmSourceException e) {
363 isDbEmpty = true;
364 }
365 if(isDbEmpty) {
366 dbSchemaValidation = DbSchemaValidation.CREATE;
367 }
368 }
369
370 private boolean causeIsCancelationExceptionRecursive(Throwable throwable){
371 if(throwable == null){
372 return false;
373 }else if(throwable instanceof CancellationException){
374 return true;
375 }else{
376 return causeIsCancelationExceptionRecursive(throwable.getCause());
377 }
378 }
379
380 private void checkDatabaseReachable(IProgressMonitor monitor) {
381 try {
382 monitor.subTask("Checking if datasource is reachable.");
383 cdmSource.checkConnection();
384 monitor.worked(1);
385 } catch (CdmSourceException e) {
386 MessagingUtils.messageDialog("Could not connect to chosen datasource",
387 this, "Reason: " + e.getMessage(), e);
388 monitor.setCanceled(true);
389 }
390 }
391
392
393 }