001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.gwt.client.ui;
029
030import org.opencms.gwt.CmsRpcException;
031import org.opencms.gwt.client.Messages;
032import org.opencms.gwt.client.rpc.CmsLog;
033import org.opencms.gwt.client.ui.css.I_CmsConstantsBundle;
034import org.opencms.gwt.client.ui.css.I_CmsLayoutBundle;
035import org.opencms.gwt.client.util.CmsClientStringUtil;
036import org.opencms.gwt.client.util.CmsDomUtil;
037
038import java.util.Date;
039import java.util.HashSet;
040import java.util.Set;
041
042import com.google.gwt.dom.client.Style.Unit;
043import com.google.gwt.event.dom.client.ClickEvent;
044import com.google.gwt.event.dom.client.ClickHandler;
045import com.google.gwt.event.logical.shared.OpenEvent;
046import com.google.gwt.event.logical.shared.OpenHandler;
047import com.google.gwt.user.client.ui.FlowPanel;
048import com.google.gwt.user.client.ui.HTML;
049import com.google.gwt.user.client.ui.Panel;
050
051/**
052 * Provides a generic error dialog.<p>
053 *
054 * @since 8.0.0
055 */
056public class CmsErrorDialog extends CmsPopup {
057
058    /** The active error dialog ids. */
059    private static Set<String> m_activeErrorDialogIds = new HashSet<String>();
060
061    /** The stack trace line break. */
062    private static final String LINE_BREAK = "\n";
063
064    /** The 'close' button. */
065    private CmsPushButton m_closeButton;
066
067    /** The details fieldset. */
068    private CmsFieldSet m_detailsFieldset;
069
070    /** String which identifies the error dialog. */
071    private String m_errorDialogId;
072
073    /** The message HTML. */
074    private CmsMessageWidget m_messageWidget;
075
076    /**
077     * Constructor.<p>
078     *
079     * @param message the error message
080     * @param details the error details, will be 'pre' formatted
081     */
082    public CmsErrorDialog(String message, String details) {
083
084        super(Messages.get().key(Messages.GUI_ERROR_0), WIDE_WIDTH);
085        m_errorDialogId = new Date() + " " + message;
086        setAutoHideEnabled(false);
087        setModal(true);
088        setGlassEnabled(true);
089        addDialogClose(null);
090        m_closeButton = new CmsPushButton();
091        m_closeButton.setText(Messages.get().key(Messages.GUI_CLOSE_0));
092        m_closeButton.setUseMinWidth(true);
093        m_closeButton.addClickHandler(new ClickHandler() {
094
095            /**
096             * @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent)
097             */
098            public void onClick(ClickEvent event) {
099
100                onClose();
101            }
102        });
103        addButton(m_closeButton);
104
105        Panel content = new FlowPanel();
106        m_messageWidget = createMessageWidget(message);
107        content.add(m_messageWidget);
108        if (details != null) {
109            // prepend the message
110            details = message + LINE_BREAK + LINE_BREAK + details;
111            m_detailsFieldset = createDetailsFieldSet(details);
112            m_detailsFieldset.addOpenHandler(new OpenHandler<CmsFieldSet>() {
113
114                /**
115                 * On open.<p>
116                 *
117                 * @param event the open event
118                 */
119                public void onOpen(OpenEvent<CmsFieldSet> event) {
120
121                    center();
122                }
123            });
124            content.add(m_detailsFieldset);
125            m_detailsFieldset.setVisible(false);
126            CmsPushButton detailsB = new CmsPushButton();
127            detailsB.setText(Messages.get().key(Messages.GUI_DETAILS_0));
128            detailsB.setUseMinWidth(true);
129            detailsB.addClickHandler(new ClickHandler() {
130
131                public void onClick(ClickEvent event) {
132
133                    toggleDetails();
134                }
135            });
136            addButton(detailsB, 0);
137            if (CmsDomUtil.isCopyToClipboardSupported()) {
138                final String id = "details" + CmsClientStringUtil.randomUUID();
139                m_detailsFieldset.getElement().setId(id);
140                CmsPushButton copy = new CmsPushButton();
141                copy.setText(Messages.get().key(Messages.GUI_COPY_TO_CLIPBOARD_0));
142                copy.setUseMinWidth(true);
143                copy.addClickHandler(new ClickHandler() {
144
145                    public void onClick(ClickEvent event) {
146
147                        CmsDomUtil.copyToClipboard("#" + id + " .gwt-HTML");
148                    }
149                });
150                copy.getElement().getStyle().setFloat(com.google.gwt.dom.client.Style.Float.LEFT);
151                copy.getElement().getStyle().setMarginLeft(0, Unit.PX);
152                copy.setTitle(Messages.get().key(Messages.GUI_COPY_TO_CLIPBOARD_DESCRIPTION_0));
153                addButton(copy, 0);
154            }
155        }
156        setMainContent(content);
157    }
158
159    /**
160     * Handles the exception by logging the exception to the server log and displaying an error dialog on the client.<p>
161     *
162     * @param message the error message
163     * @param t the throwable
164     */
165    public static void handleException(String message, Throwable t) {
166
167        StackTraceElement[] trace;
168        String className;
169        if (t instanceof CmsRpcException) {
170            CmsRpcException ex = (CmsRpcException)t;
171            trace = ex.getOriginalStackTrace();
172            className = ex.getOriginalClassName();
173        } else {
174            message = CmsClientStringUtil.getMessage(t);
175            trace = t.getStackTrace();
176            className = t.getClass().getName();
177        }
178        // send the ticket to the server
179        String ticket = CmsLog.log(message + LINE_BREAK + CmsClientStringUtil.getStackTraceAsString(trace, LINE_BREAK));
180
181        String errorMessage = message == null
182        ? className + ": " + Messages.get().key(Messages.GUI_NO_DESCIPTION_0)
183        : message;
184        errorMessage += LINE_BREAK + Messages.get().key(Messages.GUI_REASON_0) + ":" + t;
185
186        String details = Messages.get().key(Messages.GUI_TICKET_MESSAGE_3, ticket, className, message)
187            + CmsClientStringUtil.getStackTraceAsString(trace, LINE_BREAK);
188        new CmsErrorDialog(errorMessage, details).center();
189    }
190
191    /**
192     * Handles the exception by logging the exception to the server log and displaying an error dialog on the client.<p>
193     *
194     * @param t the throwable
195     */
196    public static void handleException(Throwable t) {
197
198        String message;
199        StackTraceElement[] trace;
200        String cause = null;
201        String className;
202        if (t instanceof CmsRpcException) {
203            CmsRpcException ex = (CmsRpcException)t;
204            message = ex.getOriginalMessage();
205            trace = ex.getOriginalStackTrace();
206            cause = ex.getOriginalCauseMessage();
207            className = ex.getOriginalClassName();
208        } else {
209            message = CmsClientStringUtil.getMessage(t);
210            trace = t.getStackTrace();
211            if (t.getCause() != null) {
212                cause = CmsClientStringUtil.getMessage(t.getCause());
213            }
214            className = t.getClass().getName();
215        }
216        // send the ticket to the server
217        String ticket = CmsLog.log(message + LINE_BREAK + CmsClientStringUtil.getStackTraceAsString(trace, LINE_BREAK));
218
219        String errorMessage = message == null
220        ? className + ": " + Messages.get().key(Messages.GUI_NO_DESCIPTION_0)
221        : message;
222        if (cause != null) {
223            errorMessage += LINE_BREAK + Messages.get().key(Messages.GUI_REASON_0) + ":" + cause;
224        }
225
226        String details = Messages.get().key(Messages.GUI_TICKET_MESSAGE_3, ticket, className, message)
227            + CmsClientStringUtil.getStackTraceAsString(trace, LINE_BREAK);
228        new CmsErrorDialog(errorMessage, details).center();
229    }
230
231    /**
232     * Checks if any error dialogs are showing.<p>
233     *
234     * @return true if any error dialogs are showing
235     */
236    public static boolean isShowingErrorDialogs() {
237
238        return m_activeErrorDialogIds.size() > 0;
239    }
240
241    /**
242     * @see org.opencms.gwt.client.ui.CmsPopup#center()
243     */
244    @Override
245    public void center() {
246
247        m_activeErrorDialogIds.add(m_errorDialogId);
248        show();
249        super.center();
250
251    }
252
253    /**
254     * @see org.opencms.gwt.client.ui.CmsPopup#hide()
255     */
256    @Override
257    public void hide() {
258
259        m_activeErrorDialogIds.remove(m_errorDialogId);
260        super.hide();
261    }
262
263    /**
264     * @see org.opencms.gwt.client.ui.CmsPopup#show()
265     */
266    @Override
267    public void show() {
268
269        m_activeErrorDialogIds.add(m_errorDialogId);
270        super.show();
271        onShow();
272    }
273
274    /**
275     * Executed on 'close' click. <p>
276     */
277    protected void onClose() {
278
279        m_closeButton.setEnabled(false);
280        hide();
281    }
282
283    /**
284     * Toggles the details visibility.<p>
285     */
286    void toggleDetails() {
287
288        if (m_detailsFieldset != null) {
289            m_detailsFieldset.setVisible(!m_detailsFieldset.isVisible());
290            center();
291        }
292    }
293
294    /**
295     * Creates a field-set containing the error details.<p>
296     *
297     * @param details the error details
298     *
299     * @return the field-set widget
300     */
301    private CmsFieldSet createDetailsFieldSet(String details) {
302
303        CmsFieldSet fieldset = new CmsFieldSet();
304        fieldset.addStyleName(I_CmsLayoutBundle.INSTANCE.errorDialogCss().details());
305        fieldset.setLegend(Messages.get().key(Messages.GUI_LABEL_STACKTRACE_0));
306        fieldset.addContent(new HTML("<pre>" + details + "</pre>"));
307        fieldset.setOpen(true);
308        return fieldset;
309    }
310
311    /**
312     * Creates the message HTML widget containing error icon and message.<p>
313     *
314     * @param message the message
315     *
316     * @return the HTML widget
317     */
318    private CmsMessageWidget createMessageWidget(String message) {
319
320        CmsMessageWidget widget = new CmsMessageWidget();
321        widget.setIcon(FontOpenCms.ERROR, I_CmsConstantsBundle.INSTANCE.css().colorError());
322        widget.setMessageHtml(message);
323        return widget;
324    }
325
326    /**
327     * Checks the available space and sets max-height to the details field-set.
328     */
329    private void onShow() {
330
331        if (m_detailsFieldset != null) {
332            m_detailsFieldset.getContentPanel().getElement().getStyle().setPropertyPx(
333                "maxHeight",
334                getAvailableHeight(m_messageWidget.getOffsetHeight()));
335        }
336    }
337}