/* * Copyright 2008-2012 Amazon Technologies, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://aws.amazon.com/apache2.0 * * This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES * OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and * limitations under the License. */ package com.amazonaws.eclipse.core.diagnostic.ui; import java.lang.reflect.Method; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.dialogs.ErrorSupportProvider; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.swt.SWT; import org.eclipse.swt.dnd.Clipboard; import org.eclipse.swt.dnd.TextTransfer; import org.eclipse.swt.dnd.Transfer; import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Link; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; import org.eclipse.ui.statushandlers.AbstractStatusAreaProvider; import org.eclipse.ui.statushandlers.StatusAdapter; import com.amazonaws.eclipse.core.AwsToolkitCore; import com.amazonaws.eclipse.core.diagnostic.model.ErrorReportDataModel; import com.amazonaws.eclipse.core.diagnostic.utils.AwsPortalFeedbackFormUtils; import com.amazonaws.eclipse.core.diagnostic.utils.EmailMessageLauncher; import com.amazonaws.eclipse.core.diagnostic.utils.PlatformEnvironmentDataCollector; import com.amazonaws.eclipse.core.preferences.PreferenceConstants; import com.amazonaws.eclipse.core.ui.EmailLinkListener; import com.amazonaws.eclipse.core.ui.overview.Toolkit; /** * A custom AbstractStatusAreaProvider implementation that provides additional * UI components for users to directly report errors that are associated with * "com.amazonaws.*" plugins. * * NOTE: this class won't work if it directly extends ErrorSupportProvider, * since the default workbench error dialog will only check the * {@link #validFor(StatusAdapter)} method if it finds an instance of * AbstractStatusAreaProvider. * * @see http://wiki.eclipse.org/Status_Handling_Best_Practices# * Developing_an_ErrorSupportProvider * * @see "org.eclipse.ui.internal.statushandlers.InternalDialog" * @see "org.eclipse.ui.internal.statushandlers.SupportTray" */ public class AwsToolkitErrorSupportProvider extends AbstractStatusAreaProvider { /** http://www.regular-expressions.info/email.html */ private static final String VALID_EMAIL_REGEX = "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}$"; private static final String COM_DOT_AMAZONAWS_DOT = "com.amazonaws."; /** * The ErrorSupportProvider that was previously configured in the JFace * Policy. Status events that don't match the criteria of * AwsToolkitErrorSupportProvider will be handled by this provider instead * (it it's not null). */ private final ErrorSupportProvider overriddenProvider; public AwsToolkitErrorSupportProvider(ErrorSupportProvider overriddenProvider) { this.overriddenProvider = overriddenProvider; } /** * Returns true if the given status should be processed by either the * AWS-specific error reporting support or the ErrorSupportProvider that was * overridden by this class. * <p> * The workbench's internal error dialog won't create the support area if * this method returns false. * * @see AbstractStatusAreaProvider#validFor(StatusAdapter) */ public boolean validFor(StatusAdapter statusAdapter) { IStatus status = statusAdapter.getStatus(); if (status == null) return false; return isAwsErrorStatus(status) || validForOverriddenProvider(status); } /** * Returns true if the status has ERROR-level severity and that it's * associated with "com.amazonaws.*" plugins. */ private static boolean isAwsErrorStatus(IStatus status) { return status != null && status.getSeverity() == Status.ERROR && status.getPlugin() != null && status.getPlugin().startsWith(COM_DOT_AMAZONAWS_DOT); } /** * Returns true if the status is valid for the error provider that was * overridden by this provider. */ private boolean validForOverriddenProvider(IStatus status) { if (overriddenProvider == null) return false; // ErrorSupportProvider#validFor(IStatus) is not added to Eclipse // platform until 3.7 version. try { Method validFor = overriddenProvider.getClass().getMethod("validFor", IStatus.class); return (Boolean) validFor.invoke(overriddenProvider, status); } catch (Exception e) { return false; } } /** * Create the custom support area that will be injected in the workbench * default error dialog. */ @Override public Control createSupportArea(Composite parent, final StatusAdapter statusAdapter) { final IStatus status = statusAdapter.getStatus(); // If the status is not associated with "com.amazonaws.*", we check // whether it should be handled by the overridden provider. if ( !isAwsErrorStatus(status) ) { return validForOverriddenProvider(status)? overriddenProvider.createSupportArea(parent, status) : parent; } GridData parentData = new GridData(SWT.FILL, SWT.FILL, true, true); parentData.widthHint = 300; parentData.minimumWidth = 300; parentData.heightHint = 250; parentData.minimumHeight = 250; parent.setLayoutData(parentData); GridLayout layout = new GridLayout(1, false); layout.marginBottom = 15; parent.setLayout(layout); Group userInputGroup = new Group(parent, SWT.NONE); GridData groupData = new GridData(SWT.FILL, SWT.FILL, true, true); userInputGroup.setLayoutData(groupData); userInputGroup.setLayout(new GridLayout(1, false)); /* User email input */ final Label label_email = new Label(userInputGroup, SWT.WRAP); label_email.setText("(Optional) Please provide a valid email:"); label_email.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); final Text email = new Text(userInputGroup, SWT.BORDER | SWT.SINGLE); email.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); // Pre-populate the default user email if it's found in the preference store email.setText(AwsToolkitCore.getDefault().getPreferenceStore() .getString(PreferenceConstants.P_ERROR_REPORT_DEFAULT_USER_EMAIL)); /* User description input */ Label label_description = new Label(userInputGroup, SWT.WRAP); label_description.setText("(Optional) Please provide more details to help us investigate:"); label_description.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); final Text description = new Text(userInputGroup, SWT.BORDER | SWT.MULTI | SWT.V_SCROLL); description.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); /* OK button */ final Button reportBtn = new Button(parent, SWT.PUSH); reportBtn.setText("Report this bug to AWS"); reportBtn.setLayoutData(new GridData(SWT.CENTER, SWT.TOP, false, false)); reportBtn.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { final String userEmail = email.getText(); final String userDescription = description.getText(); Job job = new Job("Sending error report to AWS...") { @Override protected IStatus run(IProgressMonitor arg0) { final ErrorReportDataModel errorData = new ErrorReportDataModel(); // Error data directly available from the error status errorData.setBug(status.getException()); errorData.setStatusMessage(status.getMessage()); // User-input data (email, description) errorData.setUserEmail(userEmail); errorData.setUserDescription(userDescription); // System collected data errorData.setPlatformEnv(PlatformEnvironmentDataCollector.getData()); try { AwsPortalFeedbackFormUtils.reportBugToAws(errorData); } catch (Exception error) { // Show a message box with mailto: link as fallback Display.getDefault().asyncExec(new Runnable() { public void run() { showFailureDialog(Display.getDefault().getActiveShell(), errorData); } }); AwsToolkitCore.getDefault().logInfo( "Unable to send error report. " + error.getMessage()); return Status.CANCEL_STATUS; } // If success, save the email as default AwsToolkitCore.getDefault().getPreferenceStore().setValue( PreferenceConstants.P_ERROR_REPORT_DEFAULT_USER_EMAIL, userEmail); // Show a confirmation message Display.getDefault().asyncExec(new Runnable() { public void run() { showSuccessDialog(Display.getDefault().getActiveShell()); } }); AwsToolkitCore.getDefault().logInfo("Successfully sent the error report."); return Status.OK_STATUS; } }; job.setUser(true); job.schedule(); } }); // Add simple validation to the email field email.addModifyListener(new ModifyListener() { public void modifyText(ModifyEvent e) { String userInput = email.getText(); // It's either empty, or a valid email address if (userInput.isEmpty() || userInput.matches(VALID_EMAIL_REGEX)) { label_email.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK)); reportBtn.setEnabled(true); } else { label_email.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_RED)); reportBtn.setEnabled(false); } } }); return parent; } private static void showSuccessDialog(Shell parentShell) { MessageDialog.openInformation(parentShell, "Successfully sent error report", "Thanks for reporting the error. " + "Our team will investigate this as soon as possible."); } private static void showFailureDialog(Shell parentShell, ErrorReportDataModel errorData) { MessageDialog dialog = new ErrorReportFailureMessageDialog(parentShell, errorData); dialog.open(); } private static class ErrorReportFailureMessageDialog extends MessageDialog { private static final String COPY_TO_CLIPBOARD_LABEL = "Copy error report data to clipboard"; private final ErrorReportDataModel errorData; public ErrorReportFailureMessageDialog(Shell parentShell, ErrorReportDataModel errorData) { super(parentShell, "Failed to send error report", AwsToolkitCore.getDefault().getImageRegistry().get(AwsToolkitCore.IMAGE_AWS_ICON), "Failed to send error report data to AWS.", MessageDialog.NONE, new String[] { "Ok", COPY_TO_CLIPBOARD_LABEL }, 0); this.errorData = errorData; } @Override protected Control createCustomArea(Composite parent) { // Add the mailto: link Link link = new Link(parent, SWT.WRAP); link.setText("Please contact us via email " + Toolkit.createAnchor( EmailMessageLauncher.AWS_ECLIPSE_ERRORS_AT_AMZN, EmailMessageLauncher.AWS_ECLIPSE_ERRORS_AT_AMZN)); EmailLinkListener emailLinkListener = new EmailLinkListener(EmailMessageLauncher.createEmptyErrorReportEmail()); link.addListener(SWT.Selection, emailLinkListener); return parent; } /** * We need to override this method in order to suppress closing the * dialog after the user clicks the * "Copy error report data to clipboard" button. */ @Override protected void buttonPressed(int buttonId) { if (buttonId == 1) { Clipboard clipboard = null; try { clipboard = new Clipboard(Display.getDefault()); clipboard.setContents(new Object[] { errorData.toString() }, new Transfer[] { TextTransfer.getInstance() }); } finally { if (clipboard != null) { clipboard.dispose(); } } return; } super.buttonPressed(buttonId); } } }