OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

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