OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 182 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
17 ilm 1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
182 ilm 4
 * Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
17 ilm 5
 *
6
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
7
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
8
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
9
 * language governing permissions and limitations under the License.
10
 *
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
13
 
14
 /*
15
 * ExceptionHandler created on 7 mai 2004
16
 */
17
package org.openconcerto.utils;
18
 
83 ilm 19
import org.openconcerto.utils.SystemInfo.Info;
20
import org.openconcerto.utils.cc.IFactory;
73 ilm 21
import org.openconcerto.utils.io.PercentEncoder;
22
 
17 ilm 23
import java.awt.Component;
24
import java.awt.Desktop;
25
import java.awt.Desktop.Action;
26
import java.awt.Dialog;
27
import java.awt.Dimension;
156 ilm 28
import java.awt.FlowLayout;
17 ilm 29
import java.awt.Font;
30
import java.awt.Frame;
31
import java.awt.GraphicsEnvironment;
32
import java.awt.GridBagConstraints;
33
import java.awt.GridBagLayout;
34
import java.awt.Insets;
35
import java.awt.Toolkit;
36
import java.awt.Window;
37
import java.awt.datatransfer.Clipboard;
38
import java.awt.datatransfer.StringSelection;
39
import java.awt.event.ActionEvent;
40
import java.awt.event.ActionListener;
41
import java.awt.event.WindowAdapter;
42
import java.awt.event.WindowEvent;
73 ilm 43
import java.io.BufferedReader;
44
import java.io.InputStreamReader;
45
import java.io.OutputStream;
46
import java.net.HttpURLConnection;
17 ilm 47
import java.net.URI;
73 ilm 48
import java.net.URL;
49
import java.nio.charset.Charset;
177 ilm 50
import java.util.Locale;
83 ilm 51
import java.util.Map;
156 ilm 52
import java.util.concurrent.CompletableFuture;
65 ilm 53
import java.util.concurrent.Future;
54
import java.util.concurrent.FutureTask;
156 ilm 55
import java.util.concurrent.atomic.AtomicInteger;
65 ilm 56
import java.util.logging.Level;
17 ilm 57
import java.util.logging.Logger;
73 ilm 58
import java.util.regex.Pattern;
17 ilm 59
 
60
import javax.swing.AbstractAction;
61
import javax.swing.ImageIcon;
62
import javax.swing.JButton;
63
import javax.swing.JDialog;
64
import javax.swing.JLabel;
73 ilm 65
import javax.swing.JOptionPane;
17 ilm 66
import javax.swing.JPanel;
67
import javax.swing.JScrollPane;
68
import javax.swing.JSeparator;
69
import javax.swing.JTextArea;
70
import javax.swing.ScrollPaneConstants;
71
import javax.swing.SwingUtilities;
72
import javax.swing.UIManager;
73
 
74
/**
75
 * Allow to display an exception both on the GUI and on the console.
76
 *
77
 * @author ILM Informatique 7 mai 2004
78
 */
79
public class ExceptionHandler extends RuntimeException {
80
 
73 ilm 81
    private static final Pattern NL_PATTERN = Pattern.compile("\r?\n");
17 ilm 82
    private static final String ILM_CONTACT = "http://www.ilm-informatique.fr/contact";
144 ilm 83
    private static String forumURL = null;
132 ilm 84
    private static boolean showProbably = false;
144 ilm 85
    private static IFactory<String> softwareInfos = null;
86
    private static ThrowableHandler tHandler = null;
156 ilm 87
    private static boolean safeToExit = false;
88
    private static final CompletableFuture<Boolean> FALSE_FUTURE = CompletableFuture.completedFuture(Boolean.FALSE);
89
    private static final CompletableFuture<Boolean> TRUE_FUTURE = CompletableFuture.completedFuture(Boolean.TRUE);
17 ilm 90
 
144 ilm 91
    public static void setThrowableHandler(ThrowableHandler handler) {
92
        tHandler = handler;
93
    }
94
 
17 ilm 95
    public static void setForumURL(String url) {
144 ilm 96
        forumURL = url;
17 ilm 97
    }
98
 
156 ilm 99
    public static void setSafeToExit(boolean b) {
100
        safeToExit = b;
101
    }
102
 
103
    public static void setSubmitErrorAutoEnabled(boolean b) {
104
        submitErrorAuto = b;
105
    }
106
 
107
    public static synchronized void setShowProbably(boolean showProbably) {
132 ilm 108
        ExceptionHandler.showProbably = showProbably;
109
    }
110
 
156 ilm 111
    public static synchronized boolean isShowProbably() {
132 ilm 112
        return ExceptionHandler.showProbably;
113
    }
114
 
83 ilm 115
    public synchronized static void setSoftwareInformations(final IFactory<String> f) {
144 ilm 116
        softwareInfos = f;
83 ilm 117
    }
118
 
119
    public synchronized static String computeSoftwareInformations() {
144 ilm 120
        if (softwareInfos == null)
83 ilm 121
            return "";
144 ilm 122
        return softwareInfos.createChecked();
83 ilm 123
    }
124
 
17 ilm 125
    static private void copyToClipboard(final String s) {
126
        final Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
127
        final StringSelection data = new StringSelection(s);
128
        clipboard.setContents(data, data);
129
    }
130
 
182 ilm 131
    static public Future<Boolean> handle(Component comp, String msg, Throwable originalExn) {
132
        return handle(comp, msg, null, originalExn);
133
    }
134
 
25 ilm 135
    /**
136
     * Display the passed message. Note: this method doesn't block.
137
     *
138
     * @param comp the modal parent of the error window.
139
     * @param msg the message to display.
182 ilm 140
     * @param title the title, can be <code>null</code>.
25 ilm 141
     * @param originalExn the cause, can be <code>null</code>.
156 ilm 142
     * @return a future completed when the error is handled (e.g. the user clicked on the dialog),
143
     *         <code>false</code> if the error couldn't be displayed to the user.
25 ilm 144
     */
182 ilm 145
    static public Future<Boolean> handle(Component comp, String msg, String title, Throwable originalExn) {
156 ilm 146
        final Future<Boolean> res;
144 ilm 147
        if (tHandler != null && tHandler.handle(msg, originalExn)) {
156 ilm 148
            res = TRUE_FUTURE;
149
        } else {
182 ilm 150
            res = new ExceptionHandler(comp, msg, title, originalExn, false).display();
144 ilm 151
        }
156 ilm 152
        assert res != null;
153
        return res;
17 ilm 154
    }
155
 
185 ilm 156
    @Deprecated
156 ilm 157
    static public Future<Boolean> handle(String msg, Throwable originalExn) {
17 ilm 158
        return handle(null, msg, originalExn);
159
    }
160
 
185 ilm 161
    @Deprecated
156 ilm 162
    static public Future<Boolean> handle(String msg) {
17 ilm 163
        return handle(msg, null);
164
    }
165
 
25 ilm 166
    /**
167
     * Display the passed message and quit. Note: this method blocks until the user closes the
168
     * window (then exits).
169
     *
170
     * @param msg the message to display.
171
     * @param originalExn the cause, can be <code>null</code>.
172
     * @return an exception.
173
     */
17 ilm 174
    static public RuntimeException die(String msg, Throwable originalExn) {
182 ilm 175
        final ExceptionHandler res = new ExceptionHandler(null, msg, null, originalExn);
156 ilm 176
        res.display();
177
        return res;
17 ilm 178
    }
179
 
180
    static public RuntimeException die(String msg) {
181
        return die(msg, null);
182
    }
183
 
184
    private static Logger getLogger() {
185
        return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
186
    }
187
 
188
    // the comp on which to display the popup, may be null
189
    private final Component comp;
182 ilm 190
    private final String title;
156 ilm 191
    private final boolean quit;
192
    protected static AtomicInteger openedWindows = new AtomicInteger(0);
17 ilm 193
    private static boolean forceUI;
156 ilm 194
    private static boolean submitErrorAuto = false;
17 ilm 195
 
196
    public static void setForceUI(boolean forceUI) {
197
        ExceptionHandler.forceUI = forceUI;
198
    }
199
 
156 ilm 200
    private Future<Boolean> display() {
201
        final boolean error = this.quit;
17 ilm 202
        final String msg = this.getMessage();
80 ilm 203
        // write out the message as soon as possible
204
        getLogger().log(error ? Level.SEVERE : Level.INFO, null, this);
205
        // then show it to the user
17 ilm 206
        if (!GraphicsEnvironment.isHeadless() || forceUI) {
156 ilm 207
            if (openedWindows.get() > 3) {
208
                return FALSE_FUTURE;
209
            }
210
            final FutureTask<Boolean> run = new FutureTask<>(() -> {
211
                return showMsgHardened(msg, error);
212
            });
17 ilm 213
            if (SwingUtilities.isEventDispatchThread()) {
156 ilm 214
                run.run();
25 ilm 215
            } else {
216
                if (error) {
217
                    try {
218
                        SwingUtilities.invokeAndWait(run);
219
                    } catch (Exception e) {
220
                        e.printStackTrace();
221
                        System.exit(1);
222
                    }
223
                } else {
224
                    SwingUtilities.invokeLater(run);
225
                }
226
            }
156 ilm 227
            return run;
17 ilm 228
        }
156 ilm 229
        return TRUE_FUTURE;
17 ilm 230
    }
231
 
156 ilm 232
    protected final Boolean showMsgHardened(final String msg, final boolean error) {
80 ilm 233
        try {
234
            showMsg(msg, error);
156 ilm 235
            return Boolean.TRUE;
80 ilm 236
        } catch (Throwable e) {
237
            // sometimes the VM cannot display the dialog, in that case don't crash the EDT as the
238
            // message has already been logged. Further if this class is used in
239
            // Thread.setDefaultUncaughtExceptionHandler(), it will create an infinite loop.
240
            e = new Exception("Couldn't display message", e);
241
            e.printStackTrace();
242
            try {
243
                // last ditch effort
244
                JOptionPane.showMessageDialog(null, e.getMessage() + " : " + msg);
245
            } catch (Throwable e2) {
144 ilm 246
                // nothing
80 ilm 247
            }
248
        }
156 ilm 249
        return Boolean.FALSE;
80 ilm 250
    }
251
 
182 ilm 252
    protected final void showMsg(String msg, final boolean quit) {
253
        if (msg == null) {
254
            msg = "";
255
        }
17 ilm 256
        final JPanel p = new JPanel();
257
        p.setLayout(new GridBagLayout());
258
        final GridBagConstraints c = new GridBagConstraints();
259
        c.insets = new Insets(10, 10, 10, 10);
260
        c.gridx = 0;
261
        c.gridy = 0;
262
        c.fill = GridBagConstraints.BOTH;
80 ilm 263
        final JImage im = new JImage(new ImageIcon(ExceptionHandler.class.getResource("error.png")));
17 ilm 264
        final JLabel l = new JLabel("Une erreur est survenue");
265
        l.setFont(l.getFont().deriveFont(Font.BOLD));
266
 
267
        final JTextArea textArea = new JTextArea();
268
        textArea.setFont(textArea.getFont().deriveFont(11f));
269
 
270
        c.gridheight = 3;
271
        p.add(im, c);
272
        c.insets = new Insets(2, 4, 2, 4);
273
        c.gridheight = 1;
274
        c.gridx++;
275
        c.weightx = 1;
132 ilm 276
        c.gridwidth = 1;
17 ilm 277
        p.add(l, c);
278
        c.gridy++;
73 ilm 279
 
280
        final JLabel lError = new JLabel("<html>" + NL_PATTERN.matcher(msg).replaceAll("<br>") + "</html>");
17 ilm 281
        p.add(lError, c);
73 ilm 282
        c.gridy++;
17 ilm 283
 
132 ilm 284
        if (isShowProbably()) {
285
            p.add(new JLabel("Il s'agit probablement d'une mauvaise configuration ou installation du logiciel."), c);
286
            c.gridy++;
287
        }
17 ilm 288
 
289
        c.gridx = 0;
290
        c.weighty = 0;
132 ilm 291
        c.gridwidth = 2;
17 ilm 292
 
132 ilm 293
        final JPanel btnPanel = new JPanel();
17 ilm 294
        final Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null;
295
        final boolean browseSupported = desktop != null && desktop.isSupported(Action.BROWSE);
144 ilm 296
        if (forumURL != null) {
17 ilm 297
            final javax.swing.Action communityAction;
298
            if (browseSupported) {
299
                communityAction = new AbstractAction("Consulter le forum") {
300
                    @Override
301
                    public void actionPerformed(ActionEvent e) {
73 ilm 302
                        if (desktop != null) {
303
                            try {
144 ilm 304
                                desktop.browse(new URI(forumURL));
73 ilm 305
                            } catch (Exception e1) {
306
                                e1.printStackTrace();
307
                            }
17 ilm 308
                        }
309
                    }
310
                };
311
            } else {
312
                communityAction = new AbstractAction("Copier l'adresse du forum") {
144 ilm 313
 
17 ilm 314
                    @Override
315
                    public void actionPerformed(ActionEvent e) {
144 ilm 316
                        copyToClipboard(forumURL);
17 ilm 317
                    }
318
                };
319
            }
132 ilm 320
            btnPanel.add(new JButton(communityAction));
17 ilm 321
        }
322
 
323
        final javax.swing.Action supportAction;
132 ilm 324
        if (browseSupported) {
17 ilm 325
            supportAction = new AbstractAction("Contacter l'assistance") {
326
                @Override
327
                public void actionPerformed(ActionEvent e) {
73 ilm 328
                    if (desktop != null) {
329
                        try {
330
                            desktop.browse(URI.create(ILM_CONTACT));
331
                        } catch (Exception e1) {
332
                            e1.printStackTrace();
333
                        }
17 ilm 334
                    }
335
 
336
                }
337
            };
132 ilm 338
        } else {
17 ilm 339
            supportAction = new AbstractAction("Copier l'adresse de l'assistance") {
340
                @Override
341
                public void actionPerformed(ActionEvent e) {
342
                    copyToClipboard(ILM_CONTACT);
343
                }
344
            };
132 ilm 345
        }
346
        btnPanel.add(new JButton(supportAction));
17 ilm 347
 
132 ilm 348
        btnPanel.add(new JButton(new AbstractAction("Copier l'erreur") {
144 ilm 349
 
132 ilm 350
            @Override
351
            public void actionPerformed(ActionEvent e) {
352
                copyToClipboard(textArea.getText());
353
            }
354
        }));
73 ilm 355
 
132 ilm 356
        c.fill = GridBagConstraints.NONE;
357
        c.anchor = GridBagConstraints.EAST;
358
        p.add(btnPanel, c);
359
 
17 ilm 360
        c.gridy++;
361
        c.gridx = 0;
132 ilm 362
        c.gridwidth = 2;
17 ilm 363
        c.fill = GridBagConstraints.BOTH;
364
        c.insets = new Insets(0, 0, 0, 0);
365
        p.add(new JSeparator(), c);
366
 
367
        c.gridx = 0;
368
        c.gridy++;
369
        c.insets = new Insets(2, 4, 2, 4);
370
        p.add(new JLabel("Détails de l'erreur:"), c);
371
        c.insets = new Insets(0, 0, 0, 0);
372
        c.gridy++;
373
        String message = this.getCause() == null ? null : this.getCause().getMessage();
374
        if (message == null) {
375
            message = msg;
376
        } else {
377
            message = msg + "\n\n" + message;
378
        }
379
        message += "\n";
156 ilm 380
        message += getTrace();
381
        if (submitErrorAuto) {
382
            submitError(message);
383
        }
17 ilm 384
        textArea.setText(message);
385
        textArea.setEditable(false);
144 ilm 386
 
17 ilm 387
        // Scroll
388
        JScrollPane scroll = new JScrollPane(textArea);
389
        scroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
390
        scroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
391
        scroll.getViewport().setMinimumSize(new Dimension(200, 300));
392
        c.weighty = 1;
393
        c.gridx = 0;
394
        c.gridy++;
395
        p.add(scroll, c);
396
 
397
        c.gridy++;
398
        c.fill = GridBagConstraints.NONE;
399
        c.weighty = 0;
400
        c.insets = new Insets(2, 4, 2, 4);
156 ilm 401
        JPanel closePanel = new JPanel();
402
        closePanel.setLayout(new FlowLayout());
403
        if (safeToExit) {
404
            closePanel.add(new JButton(new AbstractAction("Quitter") {
405
 
406
                @Override
407
                public void actionPerformed(ActionEvent e) {
408
                    System.exit(2);
409
                }
410
            }));
411
        }
17 ilm 412
        final JButton buttonClose = new JButton("Fermer");
156 ilm 413
        closePanel.add(buttonClose);
17 ilm 414
 
156 ilm 415
        p.add(closePanel, c);
416
 
17 ilm 417
        final Window window = this.comp == null ? null : SwingUtilities.getWindowAncestor(this.comp);
418
        final JDialog f;
419
        if (window instanceof Frame) {
182 ilm 420
            f = new JDialog((Frame) window, this.getTitle(), true);
17 ilm 421
        } else {
182 ilm 422
            f = new JDialog((Dialog) window, this.getTitle(), true);
17 ilm 423
        }
424
        f.setContentPane(p);
425
        f.pack();
156 ilm 426
        f.setSize(780, 580);
17 ilm 427
        f.setMinimumSize(new Dimension(380, 380));
428
        f.setLocationRelativeTo(this.comp);
429
        final ActionListener al = new ActionListener() {
430
 
431
            @Override
432
            public void actionPerformed(ActionEvent e) {
156 ilm 433
                openedWindows.decrementAndGet();
17 ilm 434
                if (quit) {
435
                    System.exit(1);
436
                } else {
437
                    f.dispose();
438
                }
439
            }
440
        };
441
        buttonClose.addActionListener(al);
442
        // cannot set EXIT_ON_CLOSE on JDialog
443
        f.addWindowListener(new WindowAdapter() {
444
            @Override
445
            public void windowClosing(WindowEvent e) {
446
                al.actionPerformed(null);
156 ilm 447
 
17 ilm 448
            }
449
        });
156 ilm 450
        openedWindows.incrementAndGet();
90 ilm 451
 
452
        f.setVisible(true);
156 ilm 453
 
17 ilm 454
    }
455
 
456
    private String getTrace() {
457
        return ExceptionUtils.getStackTrace(this);
458
    }
459
 
460
    /**
461
     * Affiche l'erreur et quitte.
462
     *
463
     * @param comp the component upon which to display the popup.
464
     * @param msg le message d'erreur à afficher.
182 ilm 465
     * @param title the title, can be <code>null</code>.
17 ilm 466
     * @param cause la cause de l'exception (peut être <code>null</code>).
467
     */
182 ilm 468
    private ExceptionHandler(Component comp, String msg, String title, Throwable cause) {
469
        this(comp, msg, title, cause, true);
17 ilm 470
    }
471
 
472
    /**
473
     * Affiche l'erreur et quitte suivant l'option passée.
474
     *
475
     * @param comp the component upon which to display the popup.
476
     * @param msg the error message to display.
182 ilm 477
     * @param title the title, can be <code>null</code>.
17 ilm 478
     * @param cause the cause of the exception (maybe <code>null</code>).
479
     * @param quit if the VM must exit.
480
     */
182 ilm 481
    private ExceptionHandler(Component comp, String msg, String title, Throwable cause, boolean quit) {
17 ilm 482
        super(msg, cause);
483
        this.comp = comp;
182 ilm 484
        this.title = title;
156 ilm 485
        this.quit = quit;
17 ilm 486
    }
487
 
182 ilm 488
    public String getTitle() {
489
        if (this.title != null)
490
            return this.title;
491
        return this.quit ? "Erreur fatale" : "Erreur";
492
    }
493
 
156 ilm 494
    private void submitError(String error) {
495
        final Charset cs = StringUtils.UTF8;
496
        try {
497
            ProductInfo productInfo = ProductInfo.getInstance();
498
 
499
            String name = "", version = "";
500
            if (productInfo != null) {
501
                name = productInfo.getName();
502
                version = productInfo.getProperty(ProductInfo.VERSION, version);
503
            }
504
 
177 ilm 505
            final Map<Info, String> systemInfos = SystemInfo.get(false, Locale.ENGLISH);
156 ilm 506
            final String os = systemInfos.remove(Info.OS);
507
            final String java = systemInfos.toString();
508
            final String encodedData = "java=" + PercentEncoder.encode(java, cs) + "&os=" + PercentEncoder.encode(os, cs) + "&software=" + PercentEncoder.encode(name + version, cs) + "&stack="
509
                    + PercentEncoder.encode(computeSoftwareInformations() + "\n\n" + error, cs);
510
            Thread t = new Thread(new Runnable() {
511
 
512
                @Override
513
                public void run() {
514
                    final String request = "http://bugreport.ilm-informatique.fr:5000/bugreport";
515
                    try {
516
                        System.err.println("ExceptionHandler.submitError");
517
                        final URL url = new URL(request);
518
                        final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
519
                        connection.setDoOutput(true);
520
                        connection.setRequestMethod("POST");
521
                        connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
522
                        connection.setRequestProperty("charset", cs.name());
523
                        final byte[] bytes = encodedData.getBytes(cs);
524
                        connection.setRequestProperty("Content-Length", String.valueOf(bytes.length));
525
 
526
                        final OutputStream outputStream = connection.getOutputStream();
527
                        outputStream.write(bytes);
528
                        outputStream.flush();
529
 
530
                        // Get the response
531
                        final StringBuilder answer = new StringBuilder();
532
                        BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
533
                        String line;
534
                        while ((line = reader.readLine()) != null) {
535
                            answer.append(line);
536
                        }
537
                        outputStream.close();
538
                        reader.close();
539
                        connection.disconnect();
540
                    } catch (Exception e) {
541
                        e.printStackTrace();
542
                    }
543
                }
544
            });
545
            t.start();
546
 
547
        } catch (Exception ex) {
548
            ex.printStackTrace();
549
        }
144 ilm 550
    }
551
 
17 ilm 552
    public static void main(String[] args) throws Exception {
553
        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
73 ilm 554
        ExceptionHandler.handle("Fichier de configuration corrompu\n\nmulti\nline", new IllegalStateException("Id manquant"));
17 ilm 555
    }
156 ilm 556
 
17 ilm 557
}