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