OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

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