diff --git a/platform/platform-impl/src/com/intellij/diagnostic/DevelopersLoader.java b/platform/platform-impl/src/com/intellij/diagnostic/DevelopersLoader.java deleted file mode 100644 index 2a2a1643f771..000000000000 --- a/platform/platform-impl/src/com/intellij/diagnostic/DevelopersLoader.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2000-2016 JetBrains s.r.o. - * - * 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://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License 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.intellij.diagnostic; - -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.util.io.HttpRequests; -import org.jetbrains.annotations.NotNull; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -class DevelopersLoader { - private static final String DEVELOPERS_LIST_URL = "https://ea-engine.labs.intellij.net/data?category=developers"; - - private DevelopersLoader() { } - - @NotNull - static Collection fetchDevelopers(@NotNull ProgressIndicator indicator) throws IOException { - return HttpRequests.request(DEVELOPERS_LIST_URL).connect((HttpRequests.RequestProcessor>)request -> { - List developers = new ArrayList<>(); - developers.add(Developer.NULL); - - String line; - while ((line = request.getReader().readLine()) != null) { - int i = line.indexOf('\t'); - if (i == -1) throw new IOException("Protocol error"); - int id = Integer.parseInt(line.substring(0, i)); - String name = line.substring(i + 1); - developers.add(new Developer(id, name)); - indicator.checkCanceled(); - } - - return developers; - }); - } -} \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/errorreport/itn/ITNProxy.java b/platform/platform-impl/src/com/intellij/diagnostic/ITNProxy.java similarity index 82% rename from platform/platform-impl/src/com/intellij/errorreport/itn/ITNProxy.java rename to platform/platform-impl/src/com/intellij/diagnostic/ITNProxy.java index 3c562aae9a4a..08ba4c422252 100644 --- a/platform/platform-impl/src/com/intellij/errorreport/itn/ITNProxy.java +++ b/platform/platform-impl/src/com/intellij/diagnostic/ITNProxy.java @@ -1,9 +1,8 @@ // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -package com.intellij.errorreport.itn; +package com.intellij.diagnostic; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; -import com.intellij.diagnostic.DiagnosticBundle; import com.intellij.errorreport.bean.ErrorBean; import com.intellij.errorreport.error.InternalEAPException; import com.intellij.errorreport.error.NoSuchEAPUserException; @@ -26,16 +25,21 @@ import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.security.CompositeX509TrustManager; import com.intellij.util.Consumer; +import com.intellij.util.io.HttpRequests; import com.intellij.util.net.NetUtils; import com.intellij.util.net.ssl.CertificateUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.net.ssl.*; -import java.io.*; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.MessageDigest; @@ -44,38 +48,62 @@ import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; -import java.util.Calendar; -import java.util.Map; +import java.util.*; import java.util.function.IntConsumer; /** * @author stathik * @since Aug 4, 2003 */ -public class ITNProxy { - private static final String NEW_THREAD_VIEW_URL = "https://ea.jetbrains.com/browser/ea_reports/"; +class ITNProxy { + private static final String DEFAULT_USER = "idea_anonymous"; + private static final String DEFAULT_PASS = "guest"; + private static final String DEVELOPERS_LIST_URL = "https://ea-engine.labs.intellij.net/data?category=developers"; private static final String NEW_THREAD_POST_URL = "https://ea-report.jetbrains.com/trackerRpc/idea/createScr"; + private static final String NEW_THREAD_VIEW_URL = "https://ea.jetbrains.com/browser/ea_reports/"; - private static final String ENCODING = "UTF8"; + static @NotNull Collection fetchDevelopers(@NotNull ProgressIndicator indicator) throws IOException { + return HttpRequests.request(DEVELOPERS_LIST_URL).connect((HttpRequests.RequestProcessor>)request -> { + List developers = new ArrayList<>(); + developers.add(Developer.NULL); - public static void sendError(@Nullable Project project, - final String login, - final String password, - @NotNull final ErrorBean error, - @NotNull final IntConsumer callback, - @NotNull final Consumer errorCallback) { - if (StringUtil.isEmpty(login)) { - return; + String line; + while ((line = request.getReader().readLine()) != null) { + int i = line.indexOf('\t'); + if (i == -1) throw new IOException("Protocol error"); + int id = Integer.parseInt(line.substring(0, i)); + String name = line.substring(i + 1); + developers.add(new Developer(id, name)); + indicator.checkCanceled(); + } + + return developers; + }); + } + + static void sendError(@Nullable Project project, + @Nullable String login, + @Nullable String password, + @NotNull ErrorBean error, + @NotNull IntConsumer onSuccess, + @NotNull Consumer onError) { + if (StringUtil.isEmptyOrSpaces(login)) { + login = DEFAULT_USER; + password = DEFAULT_PASS; + } + else if (password == null) { + password = ""; } + String _login = login, _password = password; Task.Backgroundable task = new Task.Backgroundable(project, DiagnosticBundle.message("title.submitting.error.report")) { @Override public void run(@NotNull ProgressIndicator indicator) { try { - callback.accept(postNewThread(login, password, error)); + onSuccess.accept(postNewThread(_login, _password, error)); } catch (Exception ex) { - errorCallback.consume(ex); + onError.consume(ex); } } }; @@ -88,14 +116,13 @@ public class ITNProxy { } } - @NotNull - public static String getBrowseUrl(int threadId) { + static @NotNull String getBrowseUrl(int threadId) { return NEW_THREAD_VIEW_URL + threadId; } private static SSLContext ourSslContext; - private static int postNewThread(String login, String password, @NotNull ErrorBean error) throws Exception { + private static int postNewThread(String login, String password, ErrorBean error) throws Exception { if (ourSslContext == null) { ourSslContext = initContext(); } @@ -107,14 +134,7 @@ public class ITNProxy { throw new InternalEAPException(DiagnosticBundle.message("error.http.result.code", responseCode)); } - String response; - InputStream is = connection.getInputStream(); - try { - response = FileUtil.loadTextAndClose(is); - } - finally { - is.close(); - } + String response = FileUtil.loadTextAndClose(connection.getInputStream()); if ("unauthorized".equals(response)) { throw new NoSuchEAPUserException(login); @@ -134,8 +154,7 @@ public class ITNProxy { } } - @NotNull - private static Multimap createParameters(String login, String password, @NotNull ErrorBean error) { + private static Multimap createParameters(String login, String password, ErrorBean error) { Multimap params = ArrayListMultimap.create(40, 1); params.put("protocol.version", "1"); @@ -194,29 +213,27 @@ public class ITNProxy { return params; } - @Nullable - private static String format(@Nullable Calendar calendar) { + private static @Nullable String format(@Nullable Calendar calendar) { return calendar == null ? null : Long.toString(calendar.getTime().getTime()); } - private static byte[] join(@NotNull Multimap params) throws UnsupportedEncodingException { + private static byte[] join(Multimap params) throws UnsupportedEncodingException { StringBuilder builder = new StringBuilder(); for (Map.Entry param : params.entries()) { if (StringUtil.isEmpty(param.getKey())) { throw new IllegalArgumentException(param.toString()); } - if (builder.length() > 0) { - builder.append('&'); - } if (StringUtil.isNotEmpty(param.getValue())) { - builder.append(param.getKey()).append('=').append(URLEncoder.encode(param.getValue(), ENCODING)); + if (builder.length() > 0) { + builder.append('&'); + } + builder.append(param.getKey()).append('=').append(URLEncoder.encode(param.getValue(), StandardCharsets.UTF_8.name())); } } - return builder.toString().getBytes(ENCODING); + return builder.toString().getBytes(StandardCharsets.UTF_8); } - @NotNull - private static HttpURLConnection post(@NotNull URL url, @NotNull byte[] bytes) throws IOException { + private static HttpURLConnection post(URL url, byte[] bytes) throws IOException { HttpsURLConnection connection = (HttpsURLConnection)url.openConnection(); connection.setSSLSocketFactory(ourSslContext.getSocketFactory()); @@ -227,23 +244,19 @@ public class ITNProxy { connection.setRequestMethod("POST"); connection.setDoInput(true); connection.setDoOutput(true); - connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=" + ENCODING); + connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=" + StandardCharsets.UTF_8.name()); connection.setRequestProperty("Content-Length", Integer.toString(bytes.length)); - OutputStream out = connection.getOutputStream(); - try { + try (OutputStream out = connection.getOutputStream()) { out.write(bytes); } - finally { - out.close(); - } return connection; } private synchronized static SSLContext initContext() throws GeneralSecurityException, IOException { CertificateFactory cf = CertificateFactory.getInstance(CertificateUtil.X509); - Certificate ca = cf.generateCertificate(new ByteArrayInputStream(JB_CA_CERT.getBytes(ENCODING))); + Certificate ca = cf.generateCertificate(new ByteArrayInputStream(JB_CA_CERT.getBytes(StandardCharsets.US_ASCII))); KeyStore ks = KeyStore.getInstance(CertificateUtil.JKS); ks.load(null, null); ks.setCertificateEntry("JetBrains CA", ca); @@ -328,4 +341,4 @@ public class ITNProxy { private static final String JB_CA_CN = "JetBrains Enterprise CA"; private static final String JB_CA_FP = "604d3c703a13a3be2d452f14442be11b37e186f"; -} +} \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/diagnostic/ITNReporter.kt b/platform/platform-impl/src/com/intellij/diagnostic/ITNReporter.kt index 37b11fcd773c..c3a2070b3fed 100644 --- a/platform/platform-impl/src/com/intellij/diagnostic/ITNReporter.kt +++ b/platform/platform-impl/src/com/intellij/diagnostic/ITNReporter.kt @@ -1,18 +1,4 @@ -/* - * Copyright 2000-2016 JetBrains s.r.o. - * - * 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://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License 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. - */ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.diagnostic import com.intellij.CommonBundle @@ -21,7 +7,6 @@ import com.intellij.errorreport.bean.ErrorBean import com.intellij.errorreport.error.InternalEAPException import com.intellij.errorreport.error.NoSuchEAPUserException import com.intellij.errorreport.error.UpdateAvailableException -import com.intellij.errorreport.itn.ITNProxy import com.intellij.ide.DataManager import com.intellij.ide.plugins.PluginManager import com.intellij.idea.IdeaLogger @@ -39,6 +24,7 @@ import com.intellij.openapi.ui.Messages import com.intellij.util.Consumer import com.intellij.xml.util.XmlStringUtil import java.awt.Component +import java.lang.Exception import javax.swing.Icon private var previousExceptionThreadId = 0 @@ -50,7 +36,7 @@ open class ITNReporter : ErrorReportSubmitter() { additionalInfo: String?, parentComponent: Component, consumer: Consumer): Boolean { - return submit(events.get(0), parentComponent, consumer, ErrorBean(events.get(0).throwable, IdeaLogger.ourLastActionId), additionalInfo) + return submit(events[0], parentComponent, consumer, ErrorBean(events[0].throwable, IdeaLogger.ourLastActionId), additionalInfo) } /** @@ -73,10 +59,6 @@ fun setPluginInfo(event: IdeaLoggingEvent, errorBean: ErrorBean) { } } -private fun updatePreviousThreadId(threadId: Int?) { - previousExceptionThreadId = threadId!! -} - private fun showMessageDialog(parentComponent: Component, project: Project?, message: String, title: String, icon: Icon) { if (parentComponent.isShowing) { Messages.showMessageDialog(parentComponent, message, title, icon) @@ -86,10 +68,14 @@ private fun showMessageDialog(parentComponent: Component, project: Project?, mes } } -private fun submit(event: IdeaLoggingEvent, parentComponent: Component, callback: Consumer, errorBean: ErrorBean, description: String?): Boolean { +private fun submit(event: IdeaLoggingEvent, + parentComponent: Component, + callback: Consumer, + errorBean: ErrorBean, + description: String?): Boolean { var credentials = ErrorReportConfigurable.getCredentials() - // ask password only if user name was specified if (credentials.hasOnlyUserName()) { + // ask password only if user name was specified if (!showJetBrainsAccountDialog(parentComponent).showAndGet()) { return false } @@ -112,53 +98,56 @@ private fun submit(event: IdeaLoggingEvent, parentComponent: Component, callback errorBean.attachments = data.includedAttachments } - var login = credentials?.userName - var password = credentials?.getPasswordAsString() - if (login.isNullOrBlank() && password.isNullOrBlank()) { - login = "idea_anonymous" - password = "guest" - } - val project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(parentComponent)) - ITNProxy.sendError(project, login, password, errorBean, { threadId -> - updatePreviousThreadId(threadId) - val linkText = threadId.toString() - val reportInfo = SubmittedReportInfo(ITNProxy.getBrowseUrl(threadId), linkText, SubmittedReportInfo.SubmissionStatus.NEW_ISSUE) - callback.consume(reportInfo) - ApplicationManager.getApplication().invokeLater { - val text = StringBuilder() - IdeErrorsDialog.appendSubmissionInformation(reportInfo, text) - text.append('.').append("
").append(DiagnosticBundle.message("error.report.gratitude")) - val content = XmlStringUtil.wrapInHtml(text) - ReportMessages.GROUP.createNotification(ReportMessages.ERROR_REPORT, content, NotificationType.INFORMATION, - NotificationListener.URL_OPENING_LISTENER).setImportant(false).notify(project) - } - }) { e -> - Logger.getInstance(ITNReporter::class.java).info("reporting failed: $e") - ApplicationManager.getApplication().invokeLater { - val msg = when (e) { - is NoSuchEAPUserException -> DiagnosticBundle.message("error.report.authentication.failed") - is InternalEAPException -> DiagnosticBundle.message("error.report.posting.failed", e.message) - else -> DiagnosticBundle.message("error.report.sending.failure") - } - - if (e is UpdateAvailableException) { - val message = DiagnosticBundle.message("error.report.new.eap.build.message", e.message) - showMessageDialog(parentComponent, project, message, CommonBundle.getWarningTitle(), Messages.getWarningIcon()) - callback.consume(SubmittedReportInfo(SubmittedReportInfo.SubmissionStatus.FAILED)) - return@invokeLater - } - - if (!MessageDialogBuilder.yesNo(ReportMessages.ERROR_REPORT, msg).project(project).isYes) { - callback.consume(SubmittedReportInfo(SubmittedReportInfo.SubmissionStatus.FAILED)) - } - else { - if (e is NoSuchEAPUserException) { - showJetBrainsAccountDialog(parentComponent, project).show() - } - ApplicationManager.getApplication().invokeLater { submit(event, parentComponent, callback, errorBean, description) } - } - } - } + ITNProxy.sendError(project, credentials?.userName, credentials?.getPasswordAsString(), errorBean, + { threadId -> onSuccess(threadId, callback, project) }, + { e -> onError(e, parentComponent, project, callback, event, errorBean, description) }) return true } + +private fun onSuccess(threadId: Int, callback: Consumer, project: Project?) { + previousExceptionThreadId = threadId + val linkText = threadId.toString() + val reportInfo = SubmittedReportInfo(ITNProxy.getBrowseUrl(threadId), linkText, SubmittedReportInfo.SubmissionStatus.NEW_ISSUE) + callback.consume(reportInfo) + ApplicationManager.getApplication().invokeLater { + val text = StringBuilder() + IdeErrorsDialog.appendSubmissionInformation(reportInfo, text) + text.append('.').append("
").append(DiagnosticBundle.message("error.report.gratitude")) + val content = XmlStringUtil.wrapInHtml(text) + ReportMessages.GROUP.createNotification(ReportMessages.ERROR_REPORT, content, NotificationType.INFORMATION, + NotificationListener.URL_OPENING_LISTENER).setImportant(false).notify(project) + } +} + +private fun onError(e: Exception, + parentComponent: Component, + project: Project?, + callback: Consumer, + event: IdeaLoggingEvent, + errorBean: ErrorBean, + description: String?) { + Logger.getInstance(ITNReporter::class.java).info("reporting failed: $e") + ApplicationManager.getApplication().invokeLater { + val msg = when (e) { + is NoSuchEAPUserException -> DiagnosticBundle.message("error.report.authentication.failed") + is InternalEAPException -> DiagnosticBundle.message("error.report.posting.failed", e.message) + else -> DiagnosticBundle.message("error.report.sending.failure") + } + + if (e is UpdateAvailableException) { + val message = DiagnosticBundle.message("error.report.new.eap.build.message", e.message) + showMessageDialog(parentComponent, project, message, CommonBundle.getWarningTitle(), Messages.getWarningIcon()) + callback.consume(SubmittedReportInfo(SubmittedReportInfo.SubmissionStatus.FAILED)) + } + else if (!MessageDialogBuilder.yesNo(ReportMessages.ERROR_REPORT, msg).project(project).isYes) { + callback.consume(SubmittedReportInfo(SubmittedReportInfo.SubmissionStatus.FAILED)) + } + else { + if (e is NoSuchEAPUserException) { + showJetBrainsAccountDialog(parentComponent, project).show() + } + ApplicationManager.getApplication().invokeLater { submit(event, parentComponent, callback, errorBean, description) } + } + } +} diff --git a/platform/platform-impl/src/com/intellij/diagnostic/IdeErrorsDialog.java b/platform/platform-impl/src/com/intellij/diagnostic/IdeErrorsDialog.java index 96fc764de257..4ca2af41eb7f 100644 --- a/platform/platform-impl/src/com/intellij/diagnostic/IdeErrorsDialog.java +++ b/platform/platform-impl/src/com/intellij/diagnostic/IdeErrorsDialog.java @@ -1,18 +1,4 @@ -/* - * Copyright 2000-2017 JetBrains s.r.o. - * - * 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://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License 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. - */ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.diagnostic; import com.intellij.CommonBundle; @@ -41,7 +27,6 @@ import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.extensions.PluginDescriptor; import com.intellij.openapi.extensions.PluginId; import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; @@ -142,29 +127,20 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene } private void loadDevelopersAsynchronously() { - Task.Backgroundable task = new Task.Backgroundable(null, "Loading Developers List", true) { - private Collection myDevelopers = Collections.emptyList(); - + new Task.Backgroundable(null, "Loading Developers List", true) { @Override public void run(@NotNull ProgressIndicator indicator) { try { - myDevelopers = DevelopersLoader.fetchDevelopers(indicator); + Collection developers = ITNProxy.fetchDevelopers(indicator); + myDetailsTabForm.setDevelopers(developers); + //noinspection AssignmentToStaticFieldFromInstanceMethod + ourDevelopersList = developers; } catch (IOException e) { - //Notifications.Bus.register("Error reporter", NotificationDisplayType.BALLOON); - //Notifications.Bus.notify(new Notification("Error reporter", "Communication error", - // "Unable to load developers list from server.", NotificationType.WARNING)); + LOG.warn(e); } } - - @Override - public void onSuccess() { - myDetailsTabForm.setDevelopers(myDevelopers); - //noinspection AssignmentToStaticFieldFromInstanceMethod - ourDevelopersList = myDevelopers; - } - }; - ProgressManager.getInstance().run(task); + }.queue(); } private boolean moveSelectionToMessage(LogMessage defaultMessage) { @@ -794,10 +770,8 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene String msg = "Detected plugin " + id + " by class " + className; IdeaPluginDescriptor descriptor = PluginManager.getPlugin(id); if (descriptor != null) { - msg += "; ideaLoader=" + descriptor.getUseIdeaClassLoader(); - ClassLoader loader = descriptor.getPluginClassLoader(); - msg += "; loader=" + loader; + msg += "; loader=" + loader + '/' + loader.getClass(); if (loader instanceof PluginClassLoader) { msg += "; loaded class: " + ((PluginClassLoader)loader).hasLoadedClass(className); }