1 package eu
.etaxonomy
.taxeditor
.model
;
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
;
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
;
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
;
28 * Utility class which handles all the messaging information generated by the
31 * This includes logging as well as dialogs.
35 public class MessagingUtils
{
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.";
46 * Gets the Log4J logger for a given class
49 * a {@link java.lang.Class} object.
50 * @return a {@link org.apache.log4j.Logger} object.
52 public static Logger
getLog4JLogger(Class
<?
> clazz
) {
53 return Logger
.getLogger(clazz
);
57 * Logs details from a given Status object
60 * a {@link org.eclipse.core.runtime.IStatus} object.
62 private static void log(IStatus status
) {
63 TaxeditorStorePlugin
.getDefault().getLog().log(status
);
67 * Logs a status object as information.
70 * a {@link org.eclipse.core.runtime.IStatus} object.
72 public static void info(IStatus status
) {
77 * Logs a string as information.
80 * a {@link java.lang.String} object.
82 public static void info(String message
) {
83 IStatus status
= new Status(IStatus
.INFO
, AbstractUtility
.getPluginId(), message
);
88 * Logs an exception from a given source as a warning.
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
);
97 * Logs a status object from a given source as a warning.
99 public static void warn(Class
<?
> source
, IStatus status
) {
100 MessagingUtils
.getLog4JLogger(source
).warn(status
.getMessage(), status
.getException());
105 * Logs a string from a given source as a warning.
109 * a {@link java.lang.Class} object.
111 * a {@link java.lang.String} object.
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
);
120 * Logs a status object from a given source as an error.
124 * a {@link java.lang.Class} object.
126 * a {@link org.eclipse.core.runtime.IStatus} object.
128 public static void error(Class
<?
> source
, IStatus status
) {
129 getLog4JLogger(source
)
130 .error(status
.getMessage(), status
.getException());
135 * Logs a string and exception from a given source as an error.
139 * a {@link java.lang.Class} object.
141 * a {@link java.lang.String} object.
143 * a {@link java.lang.Throwable} object.
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
);
151 * Logs an exception from a given source as an error.
155 * a {@link java.lang.Class} object.
157 * a {@link java.lang.Throwable} object.
159 public static void error(Class
<?
> source
, Throwable t
) {
160 error(source
.getClass(), t
.getMessage(), t
);
164 * Returns a list of strings, providing info on,
167 * - server (address + source name)
168 * - db schema version
172 public static List
<String
> getContextInfo() {
173 List
<String
> contextInfo
= new ArrayList
<>();
175 String contextPath
= "";
176 String schemaVersion
= "";
181 version
= Platform
.getBundle("eu.etaxonomy.taxeditor.application").getHeaders().get(org
.osgi
.framework
.Constants
.BUNDLE_VERSION
);
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());
197 } catch (Exception e
) {
202 LocalDateTime date
= LocalDateTime
.now();
203 String dateTimeStr
= date
.format(DateTimeFormatter
.ISO_DATE_TIME
);
205 if(RemoteExecutionTimestampsUtil
.getLastServiceMethod() != null){
206 contextInfo
.add("last remote method : " + RemoteExecutionTimestampsUtil
.getLastServiceMethod());
208 if(RemoteExecutionTimestampsUtil
.getLastRequestClientTime() != null){
209 contextInfo
.add("last remote request client time : " + RemoteExecutionTimestampsUtil
.getLastRequestClientTime());
211 if(RemoteExecutionTimestampsUtil
.getLastResponseHttpHeaderTime()!= null){
212 contextInfo
.add("last remote request response header time : " + RemoteExecutionTimestampsUtil
.getLastResponseHttpHeaderTime());
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"));
225 public static String
getStackTraceAndContextInfo(Throwable t
, List
<String
> contextInfo
) {
226 StringBuffer stackTraceAndContextInfo
= new StringBuffer();
227 Throwable throwable
= t
;
229 for(String infoItem
: contextInfo
) {
230 stackTraceAndContextInfo
.append(infoItem
+ System
.getProperty("line.separator"));
233 StringWriter sw
= new StringWriter();
235 if(throwable
== null) {
236 throwable
= getDefaultThrowable();
238 throwable
.printStackTrace(new PrintWriter(sw
));
240 stackTraceAndContextInfo
.append(sw
.toString());
242 return stackTraceAndContextInfo
.toString();
245 public static String
getContextInfo(List
<String
> contextInfo
) {
246 StringBuffer scontextInfoStringBuffer
= new StringBuffer();
248 for(String infoItem
: contextInfo
) {
249 scontextInfoStringBuffer
.append(infoItem
+ System
.getProperty("line.separator"));
252 return scontextInfoStringBuffer
.toString();
255 private static Throwable
getDefaultThrowable() {
256 return new Throwable("Error thrown but no associated exception");
260 * Displays a {@link eu.etaxonomy.taxeditor.model.CdmErrorDialog}.
263 private static void errorDialog(final String title
,
266 final List
<String
> contextInfo
,
267 final String message
,
268 final MultiStatus status
,
269 final boolean showReason
) {
271 Display
.getDefault().asyncExec(new Runnable() {
275 String stackTraceWithContext
= getStackTraceAndContextInfo(t
, contextInfo
);
276 CdmErrorDialog ced
= new CdmErrorDialog(AbstractUtility
.getShell(), title
, message
, status
, stackTraceWithContext
, showReason
);
278 Class
<?
extends Object
> clazz
= source
!= null ? source
.getClass() : this.getClass();
281 IStatus singleStatus
= new Status(IStatus
.ERROR
,
284 new Exception(stackTraceWithContext
));
286 error(clazz
, singleStatus
);
291 public static void errorDialog(final String title
,
293 final String message
,
294 final String pluginId
,
296 boolean addContactMesg
) {
297 errorDialog(title
, source
, message
, pluginId
, t
, addContactMesg
, true);
302 * Displays a {@link eu.etaxonomy.taxeditor.model.CdmErrorDialog}.
304 public static void errorDialog(final String title
,
306 final String message
,
307 final String pluginId
,
309 boolean addContactMesg
,
310 boolean showReason
) {
312 Throwable throwable
= t
;
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
>();
320 List
<String
> contextInfo
= getContextInfo();
321 for(String infoItem
: contextInfo
) {
322 childStatuses
.add(new Status(IStatus
.ERROR
, pluginId
, infoItem
));
325 if(throwable
== null) {
326 throwable
= getDefaultThrowable();
332 for(Throwable th
: ExceptionUtils
.getThrowables(throwable
)) {
333 // add main exception
335 for (StackTraceElement ste
: th
.getStackTrace()) {
336 childStatuses
.add(new Status(IStatus
.ERROR
, pluginId
, " at " + ste
.toString()));
339 // add recursive causes
341 childStatuses
.add(new Status(IStatus
.ERROR
, pluginId
, ""));
342 String msg
= th
.toString();
343 childStatuses
.add(new Status(IStatus
.ERROR
, pluginId
, "Caused by : " + msg
));
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
, " ...."));
351 // build & add status
352 childStatuses
.add(new Status(IStatus
.ERROR
, pluginId
, " at " + ste
.toString()));
359 String finalMessage
= message
;
361 if(finalMessage
== null || finalMessage
.isEmpty()) {
362 finalMessage
= DEFAULT_MESSAGE
;
366 // add edit support contact info to message
367 finalMessage
+= MessagingUtils
.CONTACT_MESSAGE
;
370 MultiStatus ms
= new MultiStatus(pluginId
,
372 childStatuses
.toArray(new Status
[] {}),
373 throwable
.toString(),
376 errorDialog(title
, source
, throwable
, contextInfo
, finalMessage
, ms
, showReason
);
380 * Displays a dialog for an exception occurring in an operation.
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.
387 * a {@link java.lang.String} object.
389 * a {@link java.lang.Object} object.
391 * a {@link org.eclipse.core.runtime.IStatus} object.
393 public static void operationDialog(final Object source
,
395 final String pluginId
,
396 final String operationlabel
,
399 Display
.getDefault().asyncExec(new Runnable() {
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";
411 // checking security exceptions for every operation
412 RuntimeException securityRuntimeException
= SecurityExceptionUtils
.findSecurityRuntimeException(ex
);
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
));
419 title
= "Error executing operation";
420 errorDialog(title
, source
, String
.format("An error occurred while executing %s. %s", operationlabel
, hint
), pluginId
, ex
, true);
427 * Displays a question {@link org.eclipse.jface.dialogs.MessageDialog}.
430 * a {@link java.lang.String} object.
432 * a {@link java.lang.String} object.
435 public static boolean confirmDialog(String title
, String message
) {
436 return MessageDialog
.openQuestion(AbstractUtility
.getShell(), title
, message
);
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();
446 * Displays a message {@link org.eclipse.jface.dialogs.MessageDialog}.
452 public static void messageDialog(final String title
, final Object source
, final String message
) {
453 MessagingUtils
.messageDialog(title
, source
, message
, null, true);
457 * Displays an error {@link org.eclipse.jface.dialogs.MessageDialog}.
462 * The object where the warning was generated (used by log4j)
464 * An informative String to be presented to the user
468 * a Throwable if one exists or null
470 public static void messageDialog(final String title
,
472 final String message
,
474 MessagingUtils
.messageDialog(title
, source
, message
, t
, true);
478 * Displays an error {@link org.eclipse.jface.dialogs.MessageDialog}.
483 * The object where the warning was generated (used by log4j)
485 * An informative String to be presented to the user
489 * a Throwable if one exists or null
491 public static void messageDialog(final String title
,
493 final String message
,
497 Display
.getDefault().asyncExec(new Runnable() {
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
);
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
);
516 public static String
getCauseRecursively(Throwable t
) {
521 if(t
.getCause() != null){
522 return getCauseRecursively(t
.getCause());
524 return String
.format("\n\nException: %s\nMessage: %s", t
.getClass().getSimpleName(), t
.getMessage());
530 * Displays a warning {@link org.eclipse.jface.dialogs.MessageDialog}.
536 public static void warningDialog(String title
, Object source
,
538 MessagingUtils
.warningDialog(title
, source
, status
.getMessage());
542 * Standard warning dialog for the case when the application is not yet connected to the datasource.
546 public static void noDataSourceWarningDialog(Object source
) {
549 "Application is not connected to a datastore",
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.");
556 * Standard warning dialog for the case when the datasource is not available
560 public static void dataSourceNotAvailableWarningDialog(Object source
) {
563 "The datasource is not available",
565 "The editor is not connected to a datasource. Maybe the datasource is not available.");
569 * Displays a warning {@link org.eclipse.jface.dialogs.MessageDialog}.
574 * The object where the warning was generated (used by log4j)
576 * An informative String to be presented to the user
578 public static void warningDialog(final String title
, final Object source
, final String message
) {
579 Display
.getDefault().asyncExec(new Runnable() {
583 MessageDialog
.openWarning(AbstractUtility
.getShell(), title
, message
);
584 Class
<?
extends Object
> clazz
= source
!= null ? source
585 .getClass() : AbstractUtility
.class;
586 warn(clazz
, message
);
592 * Displays an information {@link org.eclipse.jface.dialogs.MessageDialog}.
594 public static void informationDialog(final String title
, final IStatus status
) {
595 MessagingUtils
.informationDialog(title
, status
.getMessage());
599 * Displays an information {@link org.eclipse.jface.dialogs.MessageDialog}.
602 * a {@link java.lang.String} object.
604 * a {@link java.lang.String} object.
606 public static void informationDialog(final String title
,
607 final String message
) {
608 Display
.getDefault().asyncExec(new Runnable() {
612 MessageDialog
.openInformation(AbstractUtility
.getShell(), title
, message
);
618 * Open a message box that informs the user about unimplemented
619 * functionality. This method is for developer convenience.
622 * a {@link java.lang.Object} object.
624 public static void notImplementedMessage(Object source
) {
625 warningDialog("Not yet implemented", source
,
626 "This functionality is not yet implemented.");