Merge branch 'develop' into termSearch
[taxeditor.git] / eu.etaxonomy.taxeditor.store / src / main / java / eu / etaxonomy / taxeditor / model / MessagingUtils.java
1 package eu.etaxonomy.taxeditor.model;
2
3 import java.io.PrintWriter;
4 import java.io.StringWriter;
5 import java.util.ArrayList;
6 import java.util.List;
7
8 import org.apache.commons.lang.exception.ExceptionUtils;
9 import org.apache.log4j.Logger;
10 import org.eclipse.core.runtime.IStatus;
11 import org.eclipse.core.runtime.MultiStatus;
12 import org.eclipse.core.runtime.Platform;
13 import org.eclipse.core.runtime.Status;
14 import org.eclipse.jface.dialogs.MessageDialog;
15 import org.eclipse.swt.widgets.Display;
16
17 import eu.etaxonomy.cdm.common.CdmUtils;
18 import eu.etaxonomy.cdm.config.ICdmSource;
19 import eu.etaxonomy.cdm.test.integration.SecurityExceptionUtils;
20 import eu.etaxonomy.taxeditor.remoting.source.CdmRemoteSource;
21 import eu.etaxonomy.taxeditor.store.CdmStore;
22 import eu.etaxonomy.taxeditor.store.internal.TaxeditorStorePlugin;
23
24 /**
25 * Utility class which handles all the messaging information generated by the
26 * Editor.
27 *
28 * This includes logging as well as dialogs.
29 *
30 * @author cmathew
31 *
32 */
33 public class MessagingUtils {
34 public final static String UNEXPECTED_ERROR_MESSAGE = "This is an unexpected error.";
35 public final static String CONTACT_MESSAGE = System.getProperty("line.separator") + "Please contact EDIT Support (EditSupport@bgbm.org) with the error trace below (click on the 'Details' button).";
36 public final static String DEFAULT_MESSAGE = "Error thrown but no associated message";
37 public final static String CONNECTION_FAILURE_MESSAGE = "The connection to the remote server has been broken";
38 public final static String REMOTE_ACCESS_FAILURE_MESSAGE = "Maybe the server is currently not available. If the problem persists please contact the server admin with the error trace below.";
39 public static final String WIDGET_IS_DISPOSED_MESSAGE = "A widget was called, which was already disposed";
40 public static final String ACCESS_DENIED = "The connection to the server could not be established because the access was denied.";
41 /**
42 * Gets the Log4J logger for a given class
43 *
44 * @param clazz
45 * a {@link java.lang.Class} object.
46 * @return a {@link org.apache.log4j.Logger} object.
47 */
48 public static Logger getLog4JLogger(Class clazz) {
49 return Logger.getLogger(clazz);
50 }
51
52 /**
53 * Logs details from a given Status object
54 *
55 * @param status
56 * a {@link org.eclipse.core.runtime.IStatus} object.
57 */
58 private static void log(IStatus status) {
59 TaxeditorStorePlugin.getDefault().getLog().log(status);
60 }
61
62 /**
63 * Logs a status object as information.
64 *
65 * @param status
66 * a {@link org.eclipse.core.runtime.IStatus} object.
67 */
68 public static void info(IStatus status) {
69 log(status);
70 }
71
72 /**
73 * Logs a string as information.
74 *
75 * @param message
76 * a {@link java.lang.String} object.
77 */
78 public static void info(String message) {
79 IStatus status = new Status(IStatus.INFO, AbstractUtility.getPluginId(), message);
80 info(status);
81 }
82
83 /**
84 * Logs an exception from a given source as a warning.
85 *
86 * @param source
87 * @param t
88 */
89 public static void warn(Class source, Throwable t) {
90 IStatus status = new Status(IStatus.WARNING, AbstractUtility.getPluginId(), t.getMessage(), t);
91 MessagingUtils.getLog4JLogger(source).warn(t);
92 log(status);
93 }
94
95 /**
96 * Logs a status object from a given source as a warning.
97 *
98 * @param source
99 * @param status
100 */
101 public static void warn(Class source, IStatus status) {
102 MessagingUtils.getLog4JLogger(source).warn(status.getMessage(), status.getException());
103 log(status);
104 }
105
106 /**
107 * Logs a string from a given source as a warning.
108 *
109 *
110 * @param source
111 * a {@link java.lang.Class} object.
112 * @param message
113 * a {@link java.lang.String} object.
114 */
115 public static void warn(Class source, String message) {
116 IStatus status = new Status(IStatus.WARNING, AbstractUtility.getPluginId(), message);
117 MessagingUtils.getLog4JLogger(source).warn(message);
118 log(status);
119 }
120
121 /**
122 * Logs a status object from a given source as an error.
123 *
124 *
125 * @param source
126 * a {@link java.lang.Class} object.
127 * @param status
128 * a {@link org.eclipse.core.runtime.IStatus} object.
129 */
130 public static void error(Class source, IStatus status) {
131 getLog4JLogger(source)
132 .error(status.getMessage(), status.getException());
133 log(status);
134 }
135
136 /**
137 * Logs a string and exception from a given source as an error.
138 *
139 *
140 * @param source
141 * a {@link java.lang.Class} object.
142 * @param message
143 * a {@link java.lang.String} object.
144 * @param t
145 * a {@link java.lang.Throwable} object.
146 */
147 public static void error(Class source, String message, Throwable t) {
148 IStatus status = new Status(IStatus.ERROR, AbstractUtility.getPluginId(), message, t);
149 error(source, status);
150 }
151
152
153
154 /**
155 * Logs an exception from a given source as an error.
156 *
157 *
158 * @param source
159 * a {@link java.lang.Class} object.
160 * @param t
161 * a {@link java.lang.Throwable} object.
162 */
163 public static void error(Class source, Throwable t) {
164 error(source.getClass(), t.getMessage(), t);
165 }
166
167
168
169 /**
170 * Returns a list of strings, providing info on,
171 * - login
172 * - editor version
173 * - server (address + source name)
174 * - db schema version
175 *
176 * @return
177 */
178 public static List<String> getContextInfo() {
179 List<String> contextInfo = new ArrayList<String>();
180 String name = "";
181 String contextPath = "";
182 String schemaVersion = "";
183 String server = "";
184 String version = "";
185 String login = "";
186 try {
187 version = Platform.getBundle("eu.etaxonomy.taxeditor.application").getHeaders().get(org.osgi.framework.Constants.BUNDLE_VERSION);
188
189 ICdmSource activeCdmSource = CdmStore.getActiveCdmSource();
190 if(activeCdmSource != null ) {
191 login = CdmStore.getLoginManager().getAuthenticatedUser().getUsername();
192 name = activeCdmSource.getName();
193 schemaVersion = activeCdmSource.getDbSchemaVersion();
194 server = activeCdmSource.getServer();
195 if(activeCdmSource instanceof CdmRemoteSource){
196 contextPath = ((CdmRemoteSource) activeCdmSource).getContextPath();
197 if (contextPath != null && contextPath.startsWith("cdmserver/")){
198 contextPath = contextPath.substring("cdmserver/".length());
199 }
200 }
201 }
202
203 } catch (Exception e) {
204 // Nothing to do
205 }
206 contextInfo.add("login : " + login);
207 contextInfo.add("editor version : " + version);
208 contextInfo.add("server : " + server + " (" + name + ")" + (CdmUtils.isNotBlank(contextPath)?" / "+contextPath:""));
209 contextInfo.add("schema version : " + schemaVersion);
210 contextInfo.add("os : " + System.getProperty("os.name")+" "+System.getProperty("os.version")+" "+System.getProperty("os.arch"));
211 contextInfo.add("java : "+System.getProperty("java.version"));
212
213 return contextInfo;
214 }
215
216 public static String getStackTraceAndContextInfo(Throwable t, List<String> contextInfo) {
217 StringBuffer stackTraceAndContextInfo = new StringBuffer();
218 Throwable throwable = t;
219
220 for(String infoItem : contextInfo) {
221 stackTraceAndContextInfo.append(infoItem + System.getProperty("line.separator"));
222 }
223
224 StringWriter sw = new StringWriter();
225
226 if(throwable == null) {
227 throwable = getDefaultThrowable();
228 }
229 throwable.printStackTrace(new PrintWriter(sw));
230
231 stackTraceAndContextInfo.append(sw.toString());
232
233 return stackTraceAndContextInfo.toString();
234 }
235
236 public static String getContextInfo(List<String> contextInfo) {
237 StringBuffer scontextInfoStringBuffer = new StringBuffer();
238
239
240 for(String infoItem : contextInfo) {
241 scontextInfoStringBuffer.append(infoItem + System.getProperty("line.separator"));
242 }
243
244
245
246 return scontextInfoStringBuffer.toString();
247 }
248
249 private static Throwable getDefaultThrowable() {
250 return new Throwable("Error thrown but no associated exception");
251 }
252
253
254
255 /**
256 * Displays a {@link eu.etaxonomy.taxeditor.model.CdmErrorDialog}.
257 *
258 * @param title
259 * @param source
260 * @param t
261 * @param contextInfo
262 * @param message
263 * @param status
264 */
265 private static void errorDialog(final String title,
266 final Object source,
267 final Throwable t,
268 final List<String> contextInfo,
269 final String message,
270 final MultiStatus status,
271 final boolean showReason) {
272
273 Display.getDefault().asyncExec(new Runnable() {
274
275 @Override
276 public void run() {
277 String stackTraceWithContext = getStackTraceAndContextInfo(t, contextInfo);
278 CdmErrorDialog ced = new CdmErrorDialog(AbstractUtility.getShell(), title, message, status, stackTraceWithContext, showReason);
279 ced.open();
280 Class<? extends Object> clazz = source != null ? source.getClass() : this.getClass();
281
282
283 IStatus singleStatus = new Status(IStatus.ERROR,
284 status.getPlugin(),
285 message,
286 new Exception(stackTraceWithContext));
287
288 error(clazz, singleStatus);
289 }
290 });
291 }
292
293 public static void errorDialog(final String title,
294 final Object source,
295 final String message,
296 final String pluginId,
297 final Throwable t,
298 boolean addContactMesg) {
299 errorDialog(title, source, message, pluginId, t, addContactMesg, true);
300
301 }
302 /**
303 * Displays a {@link eu.etaxonomy.taxeditor.model.CdmErrorDialog}.
304 *
305 * @param title
306 * @param source
307 * @param message
308 * @param pluginId
309 * @param t
310 */
311 public static void errorDialog(final String title,
312 final Object source,
313 final String message,
314 final String pluginId,
315 final Throwable t,
316 boolean addContactMesg,
317 boolean showReason) {
318
319 Throwable throwable = t;
320 StringBuffer sbStackTrace = new StringBuffer();
321
322 // We need to build a MultiStatus object since the simple
323 // idea of writing out the stack trace as a single string
324 // leads to a single line on windows
325 List<Status> childStatuses = new ArrayList<Status>();
326
327 // add context info
328 List<String> contextInfo = getContextInfo();
329 for(String infoItem : contextInfo) {
330 childStatuses.add(new Status(IStatus.ERROR, pluginId, infoItem));
331 }
332
333 if(throwable == null) {
334 throwable = getDefaultThrowable();
335 }
336
337 int thCount = 0;
338 int maxTraces = 4;
339
340 for(Throwable th : ExceptionUtils.getThrowables(throwable)) {
341 // add main exception
342 if(thCount == 0) {
343 for (StackTraceElement ste : th.getStackTrace()) {
344 childStatuses.add(new Status(IStatus.ERROR, pluginId, " at " + ste.toString()));
345 }
346 } else {
347 // add recursive causes
348 if(th != null) {
349 childStatuses.add(new Status(IStatus.ERROR, pluginId, ""));
350 String msg = th.toString();
351 childStatuses.add(new Status(IStatus.ERROR, pluginId, "Caused by : " + msg));
352 int traceCount = 0;
353 for (StackTraceElement ste : th.getStackTrace()) {
354 // add only pre-defined number of trace elements
355 if(traceCount > maxTraces) {
356 childStatuses.add(new Status(IStatus.ERROR, pluginId, " ...."));
357 break;
358 }
359 // build & add status
360 childStatuses.add(new Status(IStatus.ERROR, pluginId, " at " + ste.toString()));
361 traceCount++;
362 }
363 }
364 }
365 thCount++;
366 }
367 String finalMessage = message;
368
369 if(finalMessage == null || finalMessage.isEmpty()) {
370 finalMessage = DEFAULT_MESSAGE;
371 }
372
373 if(addContactMesg) {
374 // add edit support contact info to message
375 finalMessage += MessagingUtils.CONTACT_MESSAGE;
376 }
377
378 MultiStatus ms = new MultiStatus(pluginId,
379 IStatus.ERROR,
380 childStatuses.toArray(new Status[] {}),
381 throwable.toString(),
382 throwable);
383
384 errorDialog(title, source, throwable, contextInfo, finalMessage, ms, showReason);
385 }
386
387 /**
388 * Displays a dialog for an exception occurring in an operation.
389 *
390 * This will be either a {@link eu.etaxonomy.taxeditor.model.CdmErrorDialog} in case of a
391 * security runtime exception or a warning {@link org.eclipse.jface.dialogs.MessageDialog} in
392 * case of any other exception.
393 *
394 * @param title
395 * a {@link java.lang.String} object.
396 * @param source
397 * a {@link java.lang.Object} object.
398 * @param status
399 * a {@link org.eclipse.core.runtime.IStatus} object.
400 */
401 public static void operationDialog(final Object source,
402 final Exception ex,
403 final String pluginId,
404 final String operationlabel,
405 final String hint) {
406
407 Display.getDefault().asyncExec(new Runnable() {
408
409 @Override
410 public void run() {
411 MultiStatus info = null;
412 String title = null;
413
414 // FIXME cannot access TaxonomicEditorPlugin.PLUGIN_ID from here
415 // String PID = TaxonomicEditorPlugin.PLUGIN_ID;
416 String PID = "eu.etaxonomy.taxeditor.application";
417
418 // checking security exceptions for every operation
419 RuntimeException securityRuntimeException = SecurityExceptionUtils.findSecurityRuntimeException(ex);
420
421 // in case of a security exception it is a warning, else it is an error
422 if(securityRuntimeException != null){
423 title = "Your changes could not be saved!";
424 warningDialog(title, source, String.format("You are missing sufficient permissions for the operation \"%s\". %s", operationlabel, hint));
425 } else {
426 title = "Error executing operation";
427 errorDialog(title, source, String.format("An error occurred while executing %s. %s", operationlabel, hint), pluginId, ex, true);
428
429 }
430
431
432 }
433 });
434 }
435
436
437
438
439 /**
440 * Displays a question {@link org.eclipse.jface.dialogs.MessageDialog}.
441 *
442 * @param title
443 * a {@link java.lang.String} object.
444 * @param message
445 * a {@link java.lang.String} object.
446 * @return a boolean.
447 */
448 public static boolean confirmDialog(String title, String message) {
449 return MessageDialog.openQuestion(AbstractUtility.getShell(), title, message);
450 }
451
452 public static int confirmDialog(String title, String message, String...labels){
453 MessageDialog dialog =new MessageDialog(AbstractUtility.getShell(), title, null, message, MessageDialog.QUESTION,labels, 0);
454 int result = dialog.open();
455 return result;
456 }
457
458 /**
459 * Displays a message {@link org.eclipse.jface.dialogs.MessageDialog}.
460 *
461 * @param title
462 * @param source
463 * @param message
464 */
465 public static void messageDialog(final String title, final Object source, final String message) {
466 MessagingUtils.messageDialog(title, source, message, null, true);
467 }
468
469
470
471 /**
472 * Displays an error {@link org.eclipse.jface.dialogs.MessageDialog}.
473 *
474 * @param title
475 * The dialogs title
476 * @param source
477 * The object where the warning was generated (used by log4j)
478 * @param message
479 * An informative String to be presented to the user
480 * @param title
481 * The dialogs title
482 * @param t
483 * a Throwable if one exists or null
484 */
485 public static void messageDialog(final String title,
486 final Object source,
487 final String message,
488 final Throwable t) {
489 MessagingUtils.messageDialog(title, source, message, t, true);
490 }
491
492 /**
493 * Displays an error {@link org.eclipse.jface.dialogs.MessageDialog}.
494 *
495 * @param title
496 * The dialogs title
497 * @param source
498 * The object where the warning was generated (used by log4j)
499 * @param message
500 * An informative String to be presented to the user
501 * @param title
502 * The dialogs title
503 * @param t
504 * a Throwable if one exists or null
505 */
506 public static void messageDialog(final String title,
507 final Object source,
508 final String message,
509 final Throwable t,
510 boolean async) {
511 if(async) {
512 Display.getDefault().asyncExec(new Runnable() {
513
514 @Override
515 public void run() {
516 MessageDialog.openError(AbstractUtility.getShell(), title, message + getCauseRecursively(t));
517 Class<? extends Object> clazz = source != null ? source
518 .getClass() : this.getClass();
519 error(clazz, message, t);
520 }
521
522
523 });
524 } else {
525 MessageDialog.openError(AbstractUtility.getShell(), title, message + getCauseRecursively(t));
526 Class<? extends Object> clazz = source != null ? source.getClass() : TaxeditorStorePlugin.class;
527 error(clazz, message, t);
528 }
529 }
530
531 public static String getCauseRecursively(Throwable t) {
532 if(t == null){
533 return "";
534 }
535
536 if(t.getCause() != null){
537 return getCauseRecursively(t.getCause());
538 }else{
539 return String.format("\n\nException: %s\nMessage: %s", t.getClass().getSimpleName(), t.getMessage());
540 }
541
542 }
543 /**
544 * Displays a warning {@link org.eclipse.jface.dialogs.MessageDialog}.
545 *
546 * @param title
547 * @param termBase
548 * @param status
549 */
550 public static void warningDialog(String title, Object source,
551 IStatus status) {
552 MessagingUtils.warningDialog(title, source, status.getMessage());
553 }
554
555 /**
556 * Standard warning dialog for the case when the application is not yet connected to the datasource
557 *
558 * @param source
559 */
560 public static void noDataSourceWarningDialog(Object source) {
561 MessagingUtils
562 .warningDialog(
563 "Application is not connected to a datastore",
564 source,
565 "The requested operation is only available when "
566 + "connected to a datasource. You may choose a datasource to connect to or create a new one in the datasource view.");
567 }
568
569 /**
570 * Standard warning dialog for the case when the datasource is not available
571 *
572 * @param source
573 */
574 public static void dataSourceNotAvailableWarningDialog(Object source) {
575 MessagingUtils
576 .warningDialog(
577 "The datasource is not available",
578 source,
579 "The editor is not connected to a datasource. Maybe the datasource is not available.");
580 }
581
582
583 /**
584 * Displays a warning {@link org.eclipse.jface.dialogs.MessageDialog}.
585 *
586 * @param title
587 * The dialogs title
588 * @param source
589 * The object where the warning was generated (used by log4j)
590 * @param message
591 * An informative String to be presented to the user
592 */
593 public static void warningDialog(final String title, final Object source, final String message) {
594 Display.getDefault().asyncExec(new Runnable() {
595
596 @Override
597 public void run() {
598 MessageDialog.openWarning(AbstractUtility.getShell(), title, message);
599 Class<? extends Object> clazz = source != null ? source
600 .getClass() : AbstractUtility.class;
601 warn(clazz, message);
602 }
603 });
604 }
605
606 /**
607 * Displays an information {@link org.eclipse.jface.dialogs.MessageDialog}.
608 *
609 * @param title
610 * @param status
611 */
612 public static void informationDialog(final String title, final IStatus status) {
613 MessagingUtils.informationDialog(title, status.getMessage());
614 }
615
616 /**
617 * Displays an information {@link org.eclipse.jface.dialogs.MessageDialog}.
618 *
619 * @param title
620 * a {@link java.lang.String} object.
621 * @param message
622 * a {@link java.lang.String} object.
623 */
624 public static void informationDialog(final String title,
625 final String message) {
626 Display.getDefault().asyncExec(new Runnable() {
627
628 @Override
629 public void run() {
630 MessageDialog.openInformation(AbstractUtility.getShell(), title, message);
631 }
632 });
633 }
634
635 /**
636 * Open a message box that informs the user about unimplemented
637 * functionality. This method is for developer convenience.
638 *
639 * @param source
640 * a {@link java.lang.Object} object.
641 */
642 public static void notImplementedMessage(Object source) {
643 warningDialog("Not yet implemented", source,
644 "This functionality is not yet implemented.");
645 }
646
647 }