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