diff --git a/src/main/java/com/redhat/devtools/intellij/quarkus/QuarkusBundle.java b/src/main/java/com/redhat/devtools/intellij/quarkus/QuarkusBundle.java new file mode 100644 index 000000000..69822d720 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/quarkus/QuarkusBundle.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ +package com.redhat.devtools.intellij.quarkus; + +import com.intellij.DynamicBundle; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.PropertyKey; + +import java.util.function.Supplier; + +/** + * Quarkus messages bundle. + */ +public final class QuarkusBundle extends DynamicBundle { + + @NonNls public static final String BUNDLE = "messages.QuarkusBundle"; + private static final QuarkusBundle INSTANCE = new QuarkusBundle(); + + private QuarkusBundle() { + super(BUNDLE); + } + + @NotNull + public static @Nls String message(@NotNull @PropertyKey(resourceBundle = BUNDLE) String key, Object @NotNull ... params) { + return INSTANCE.getMessage(key, params); + } + + @NotNull + public static Supplier<@Nls String> messagePointer(@NotNull @PropertyKey(resourceBundle = BUNDLE) String key, Object @NotNull ... params) { + return INSTANCE.getLazyMessage(key, params); + } +} \ No newline at end of file diff --git a/src/main/java/com/redhat/devtools/intellij/quarkus/QuarkusConstants.java b/src/main/java/com/redhat/devtools/intellij/quarkus/QuarkusConstants.java index 4b3227b3e..ee00abc23 100644 --- a/src/main/java/com/redhat/devtools/intellij/quarkus/QuarkusConstants.java +++ b/src/main/java/com/redhat/devtools/intellij/quarkus/QuarkusConstants.java @@ -11,7 +11,8 @@ package com.redhat.devtools.intellij.quarkus; import com.intellij.openapi.util.Key; -import com.redhat.devtools.intellij.quarkus.module.QuarkusExtensionsModel; +import com.redhat.devtools.intellij.quarkus.projectWizard.QuarkusExtensionsModel; +import com.redhat.devtools.intellij.quarkus.projectWizard.QuarkusModel; import com.redhat.devtools.intellij.quarkus.tool.ToolDelegate; public class QuarkusConstants { @@ -24,6 +25,7 @@ public class QuarkusConstants { public final static Key WIZARD_PATH_KEY = Key.create(QuarkusConstants.class.getPackage().getName() + ".path"); public final static Key WIZARD_EXTENSIONS_MODEL_KEY = Key.create(QuarkusConstants.class.getPackage().getName() + ".model"); public final static Key WIZARD_ENDPOINT_URL_KEY = Key.create(QuarkusConstants.class.getPackage().getName() + ".endpointURL"); + public final static Key WIZARD_QUARKUS_STREAMS = Key.create(QuarkusConstants.class.getPackage().getName() + ".streams"); public static final String CONFIG_ROOT_ANNOTATION = "io.quarkus.runtime.annotations.ConfigRoot"; public static final String CONFIG_GROUP_ANNOTATION = "io.quarkus.runtime.annotations.ConfigGroup"; diff --git a/src/main/java/com/redhat/devtools/intellij/quarkus/module/QuarkusCodeEndpointChooserStep.java b/src/main/java/com/redhat/devtools/intellij/quarkus/module/QuarkusCodeEndpointChooserStep.java deleted file mode 100644 index 17d4a3220..000000000 --- a/src/main/java/com/redhat/devtools/intellij/quarkus/module/QuarkusCodeEndpointChooserStep.java +++ /dev/null @@ -1,148 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2019 Red Hat, Inc. - * Distributed under license by Red Hat, Inc. All rights reserved. - * This program is made available under the terms of the - * Eclipse Public License v2.0 which accompanies this distribution, - * and is available at https://www.eclipse.org/legal/epl-v20.html - * - * Contributors: - * Red Hat, Inc. - initial API and implementation - ******************************************************************************/ -package com.redhat.devtools.intellij.quarkus.module; - -import com.intellij.icons.AllIcons; -import com.intellij.ide.BrowserUtil; -import com.intellij.ide.util.PropertiesComponent; -import com.intellij.ide.util.projectWizard.ModuleWizardStep; -import com.intellij.ide.util.projectWizard.WizardContext; -import com.intellij.openapi.options.ConfigurationException; -import com.intellij.openapi.progress.EmptyProgressIndicator; -import com.intellij.openapi.ui.ComponentWithBrowseButton; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.util.Comparing; -import com.intellij.ui.HyperlinkLabel; -import com.intellij.ui.IdeBorderFactory; -import com.intellij.ui.TextFieldWithStoredHistory; -import com.intellij.ui.components.JBLabel; -import com.intellij.ui.components.JBRadioButton; -import com.intellij.util.ui.FormBuilder; -import com.intellij.util.ui.JBUI; -import com.intellij.util.ui.components.BorderLayoutPanel; -import com.redhat.devtools.intellij.quarkus.QuarkusConstants; - -import javax.swing.ButtonGroup; -import javax.swing.JComponent; -import javax.swing.JPanel; -import java.awt.BorderLayout; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.io.IOException; -import java.util.List; - -import static com.redhat.devtools.intellij.quarkus.QuarkusConstants.QUARKUS_CODE_URL; - -public class QuarkusCodeEndpointChooserStep extends ModuleWizardStep { - private static final String LAST_ENDPOINT_URL = "quarkus.code.endpoint.url.last"; - private static final String ENDPOINT_URL_HISTORY = "quarkus.code.endpoint.url.history"; - private final WizardContext wizardContext; - private final JBRadioButton defaultRadioButton = new JBRadioButton("Default:", true); - private final JBRadioButton customRadioButton = new JBRadioButton("Custom:", false); - private final TextFieldWithStoredHistory endpointURL = new TextFieldWithStoredHistory(ENDPOINT_URL_HISTORY); - private final ComponentWithBrowseButton customUrlWithBrowseButton; - - QuarkusCodeEndpointChooserStep(WizardContext wizardContext) { - this.customUrlWithBrowseButton = new ComponentWithBrowseButton(this.endpointURL, new ActionListener() { - public void actionPerformed(ActionEvent e) { - try { - QuarkusCodeEndpointChooserStep.this.validate(); - BrowserUtil.browse(QuarkusCodeEndpointChooserStep.this.endpointURL.getText()); - } catch (ConfigurationException var3) { - Messages.showErrorDialog(var3.getMessage(), "Cannot Open URL"); - } - - } - }); - this.wizardContext = wizardContext; - String lastServiceUrl = PropertiesComponent.getInstance().getValue(LAST_ENDPOINT_URL, QUARKUS_CODE_URL); - if (!lastServiceUrl.equals(QUARKUS_CODE_URL)) { - this.endpointURL.setSelectedItem(lastServiceUrl); - this.defaultRadioButton.setSelected(false); - this.customRadioButton.setSelected(true); - } else { - this.defaultRadioButton.setSelected(true); - } - - List history = this.endpointURL.getHistory(); - history.remove(QUARKUS_CODE_URL); - this.endpointURL.setHistory(history); - this.updateCustomUrl(); - } - - public JComponent getComponent() { - ButtonGroup group = new ButtonGroup(); - group.add(this.defaultRadioButton); - group.add(this.customRadioButton); - ActionListener listener = new ActionListener() { - public void actionPerformed(ActionEvent e) { - QuarkusCodeEndpointChooserStep.this.updateCustomUrl(); - } - }; - this.defaultRadioButton.addActionListener(listener); - this.customRadioButton.addActionListener(listener); - FormBuilder builder = new FormBuilder(); - builder.addComponent(new JBLabel("Choose Quarkus Code endpoint URL.")); - BorderLayoutPanel defaultPanel = JBUI.Panels.simplePanel(10, 0); - defaultPanel.addToLeft(this.defaultRadioButton); - HyperlinkLabel label = new HyperlinkLabel(QUARKUS_CODE_URL); - label.setHyperlinkTarget(QUARKUS_CODE_URL); - defaultPanel.addToCenter(label); - builder.addComponent(defaultPanel); - BorderLayoutPanel customPanel = JBUI.Panels.simplePanel(10, 0); - customPanel.addToLeft(this.customRadioButton); - this.customUrlWithBrowseButton.setButtonIcon(AllIcons.Actions.Preview); - customPanel.addToCenter(this.customUrlWithBrowseButton); - builder.addComponent(customPanel); - builder.addTooltip("Make sure your network connection is active before continuing."); - JPanel panel = new JPanel(new BorderLayout()); - panel.add(builder.getPanel(), "North"); - panel.setBorder(JBUI.Borders.emptyLeft(20)); - return panel; - } - - private void updateCustomUrl() { - boolean custom = this.customRadioButton.isSelected(); - this.endpointURL.getTextEditor().setEnabled(custom); - this.endpointURL.getTextEditor().setEditable(custom); - this.endpointURL.setEnabled(custom); - this.customUrlWithBrowseButton.setButtonEnabled(custom); - } - - public boolean validate() throws ConfigurationException { - if (this.defaultRadioButton.isSelected()) { - return true; - } else { - String serviceUrl = this.endpointURL.getText(); - if (serviceUrl.isEmpty()) { - throw new ConfigurationException("Quarkus Code endpoint URL must be set"); - } else if (!serviceUrl.startsWith("http://") && !serviceUrl.startsWith("https://")) { - throw new ConfigurationException("Invalid custom Quarkus Code endpoint URL"); - } else { - try { - QuarkusModelRegistry.INSTANCE.load(serviceUrl, new EmptyProgressIndicator()); - return true; - } catch (IOException var3) { - throw new ConfigurationException("Invalid Custom Quarkus Code endpoint URL"); - } - } - } - } - - public void updateDataModel() { - String endpointURL = this.customRadioButton.isSelected() ? this.endpointURL.getText() : QUARKUS_CODE_URL; - if (!Comparing.strEqual(this.wizardContext.getUserData(QuarkusConstants.WIZARD_ENDPOINT_URL_KEY), endpointURL)) { - this.endpointURL.addCurrentTextToHistory(); - this.wizardContext.putUserData(QuarkusConstants.WIZARD_ENDPOINT_URL_KEY, endpointURL); - PropertiesComponent.getInstance().setValue(LAST_ENDPOINT_URL, endpointURL); - } - } -} diff --git a/src/main/java/com/redhat/devtools/intellij/quarkus/module/QuarkusModuleInfoStep.java b/src/main/java/com/redhat/devtools/intellij/quarkus/module/QuarkusModuleInfoStep.java deleted file mode 100644 index 0499c6b4c..000000000 --- a/src/main/java/com/redhat/devtools/intellij/quarkus/module/QuarkusModuleInfoStep.java +++ /dev/null @@ -1,198 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2019 Red Hat, Inc. - * Distributed under license by Red Hat, Inc. All rights reserved. - * This program is made available under the terms of the - * Eclipse Public License v2.0 which accompanies this distribution, - * and is available at https://www.eclipse.org/legal/epl-v20.html - * - * Contributors: - * Red Hat, Inc. - initial API and implementation - ******************************************************************************/ -package com.redhat.devtools.intellij.quarkus.module; - -import com.intellij.ide.util.projectWizard.ModuleWizardStep; -import com.intellij.ide.util.projectWizard.WizardContext; -import com.intellij.openapi.Disposable; -import com.intellij.openapi.options.ConfigurationException; -import com.intellij.openapi.progress.EmptyProgressIndicator; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.ui.ComboBox; -import com.intellij.ui.CollectionComboBoxModel; -import com.intellij.ui.ColoredListCellRenderer; -import com.intellij.ui.ScrollPaneFactory; -import com.intellij.ui.SimpleTextAttributes; -import com.intellij.ui.components.JBCheckBox; -import com.intellij.ui.components.JBLoadingPanel; -import com.intellij.ui.components.JBTextField; -import com.intellij.util.ui.FormBuilder; -import com.intellij.util.ui.JBUI; -import com.redhat.devtools.intellij.quarkus.QuarkusConstants; -import com.redhat.devtools.intellij.quarkus.tool.ToolDelegate; -import org.jetbrains.annotations.NotNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.swing.JComponent; -import javax.swing.JList; -import javax.swing.SwingUtilities; -import javax.swing.event.ListDataEvent; -import javax.swing.event.ListDataListener; -import java.awt.BorderLayout; -import java.io.IOException; -import java.util.Arrays; - -public class QuarkusModuleInfoStep extends ModuleWizardStep implements Disposable { - private static final Logger LOGGER = LoggerFactory.getLogger(QuarkusModuleInfoStep.class); - - private final JBLoadingPanel panel = new JBLoadingPanel(new BorderLayout(), this, 300); - - private ComboBox streamComboBox; - - private ComboBox toolComboBox; - - private JBCheckBox exampleField; - - private JBTextField groupIdField; - - private JBTextField artifactIdField; - - private JBTextField versionField; - - private JBTextField classNameField; - - private JBTextField pathField; - - private final WizardContext context; - - private QuarkusModel model; - - private QuarkusExtensionsModel extensionsModel; - - public QuarkusModuleInfoStep(WizardContext context) { - this.context = context; - } - - @Override - public JComponent getComponent() { - return panel; - } - - @Override - public void updateDataModel() { - context.putUserData(QuarkusConstants.WIZARD_TOOL_KEY, (ToolDelegate)toolComboBox.getModel().getSelectedItem()); - context.putUserData(QuarkusConstants.WIZARD_EXAMPLE_KEY, exampleField.isSelected()); - context.putUserData(QuarkusConstants.WIZARD_GROUPID_KEY, groupIdField.getText()); - context.putUserData(QuarkusConstants.WIZARD_ARTIFACTID_KEY, artifactIdField.getText()); - context.putUserData(QuarkusConstants.WIZARD_VERSION_KEY, versionField.getText()); - context.putUserData(QuarkusConstants.WIZARD_CLASSNAME_KEY, classNameField.getText()); - context.putUserData(QuarkusConstants.WIZARD_PATH_KEY, pathField.getText()); - if (extensionsModel != null) { - context.putUserData(QuarkusConstants.WIZARD_EXTENSIONS_MODEL_KEY, extensionsModel); - } else { - throw new RuntimeException("Unable to get extensions"); - } - } - - @Override - public void dispose() { - - } - - @Override - public void _init() { - panel.setBorder(JBUI.Borders.empty(20)); - - ProgressIndicator indicator = new EmptyProgressIndicator() { - @Override - public void setText(String text) { - SwingUtilities.invokeLater(() -> panel.setLoadingText(text)); - } - }; - try { - model = QuarkusModelRegistry.INSTANCE.load(context.getUserData(QuarkusConstants.WIZARD_ENDPOINT_URL_KEY), indicator); - final FormBuilder formBuilder = new FormBuilder(); - final CollectionComboBoxModel streamModel = new CollectionComboBoxModel<>(model.getStreams()); - streamModel.setSelectedItem(model.getStreams().stream().filter(QuarkusStream::isRecommended).findFirst().orElse(model.getStreams().get(0))); - streamModel.addListDataListener(new ListDataListener() { - @Override - public void intervalAdded(ListDataEvent e) { - } - - @Override - public void intervalRemoved(ListDataEvent e) { - } - - @Override - public void contentsChanged(ListDataEvent e) { - try { - loadExtensionsModel(streamModel, indicator); - } catch (IOException ex) { - LOGGER.warn(ex.getLocalizedMessage(), ex); - } - } - }); - loadExtensionsModel(streamModel, indicator); - streamComboBox = new ComboBox<>(streamModel); - streamComboBox.setRenderer(new ColoredListCellRenderer() { - @Override - protected void customizeCellRenderer(@NotNull JList list, QuarkusStream stream, int index, boolean selected, boolean hasFocus) { - if (stream.isRecommended()) { - this.append(stream.getPlatformVersion(), SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES, true); - } else { - this.append(stream.getPlatformVersion(), SimpleTextAttributes.REGULAR_ATTRIBUTES, true); - } - if (stream.getStatus() != null) { - this.append(" ").append(stream.getStatus()); - } - } - }); - formBuilder.addLabeledComponent("Quarkus stream:", streamComboBox); - final CollectionComboBoxModel toolModel = new CollectionComboBoxModel<>(Arrays.asList(ToolDelegate.getDelegates())); - toolComboBox = new ComboBox<>(toolModel); - toolComboBox.setRenderer(new ColoredListCellRenderer() { - @Override - protected void customizeCellRenderer(@NotNull JList list, ToolDelegate toolDelegate, int index, boolean selected, boolean hasFocus) { - this.append(toolDelegate.getDisplay()); - } - }); - formBuilder.addLabeledComponent("Tool:", toolComboBox); - exampleField = new JBCheckBox("If selected, project will contain sample code from extensions that suppport codestarts.", true); - formBuilder.addLabeledComponent("Example code:", exampleField); - groupIdField = new JBTextField("org.acme"); - formBuilder.addLabeledComponent("Group:", groupIdField); - artifactIdField = new JBTextField("code-with-quarkus"); - formBuilder.addLabeledComponent("Artifact:", artifactIdField); - versionField = new JBTextField("1.0.0-SNAPSHOT"); - formBuilder.addLabeledComponent("Version:", versionField); - classNameField = new JBTextField("org.acme.ExampleResource"); - formBuilder.addLabeledComponent("Class name:", classNameField); - pathField = new JBTextField("/hello"); - formBuilder.addLabeledComponent("Path:", pathField); - panel.add(ScrollPaneFactory.createScrollPane(formBuilder.getPanel(), true), "North"); - } catch (IOException e) { - LOGGER.error(e.getLocalizedMessage(), e); - throw new RuntimeException(e); - } - } - - private void loadExtensionsModel(CollectionComboBoxModel streamModel, ProgressIndicator indicator) throws IOException { - extensionsModel = model.getExtensionsModel(((QuarkusStream) streamModel.getSelectedItem()).getKey(), indicator); - } - - @Override - public boolean validate() throws ConfigurationException { - if (groupIdField.getText().isEmpty()) { - throw new ConfigurationException("Group must be specified"); - } - if (artifactIdField.getText().isEmpty()) { - throw new ConfigurationException("Artifact must be specified"); - } - if (versionField.getText().isEmpty()) { - throw new ConfigurationException("Version must be specified"); - } - if (extensionsModel == null) { - throw new ConfigurationException("Unable to get extensions for this stream"); - } - return true; - } -} diff --git a/src/main/java/com/redhat/devtools/intellij/quarkus/module/QuarkusCategory.java b/src/main/java/com/redhat/devtools/intellij/quarkus/projectWizard/QuarkusCategory.java similarity index 94% rename from src/main/java/com/redhat/devtools/intellij/quarkus/module/QuarkusCategory.java rename to src/main/java/com/redhat/devtools/intellij/quarkus/projectWizard/QuarkusCategory.java index 1c91f87d9..b4c7884c3 100644 --- a/src/main/java/com/redhat/devtools/intellij/quarkus/module/QuarkusCategory.java +++ b/src/main/java/com/redhat/devtools/intellij/quarkus/projectWizard/QuarkusCategory.java @@ -8,7 +8,7 @@ * Contributors: * Red Hat, Inc. - initial API and implementation ******************************************************************************/ -package com.redhat.devtools.intellij.quarkus.module; +package com.redhat.devtools.intellij.quarkus.projectWizard; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/com/redhat/devtools/intellij/quarkus/projectWizard/QuarkusCodeEndpointChooserStep.java b/src/main/java/com/redhat/devtools/intellij/quarkus/projectWizard/QuarkusCodeEndpointChooserStep.java new file mode 100644 index 000000000..4a221cd3c --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/quarkus/projectWizard/QuarkusCodeEndpointChooserStep.java @@ -0,0 +1,268 @@ +/******************************************************************************* + * Copyright (c) 2019 Red Hat, Inc. + * Distributed under license by Red Hat, Inc. All rights reserved. + * This program is made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, + * and is available at https://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ +package com.redhat.devtools.intellij.quarkus.projectWizard; + +import com.intellij.icons.AllIcons; +import com.intellij.ide.BrowserUtil; +import com.intellij.ide.util.PropertiesComponent; +import com.intellij.ide.util.projectWizard.ModuleWizardStep; +import com.intellij.ide.util.projectWizard.WizardContext; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.options.ConfigurationException; +import com.intellij.openapi.progress.EmptyProgressIndicator; +import com.intellij.openapi.ui.ComponentWithBrowseButton; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.Disposer; +import com.intellij.ui.HyperlinkLabel; +import com.intellij.ui.TextFieldWithStoredHistory; +import com.intellij.ui.components.JBLabel; +import com.intellij.ui.components.JBRadioButton; +import com.intellij.util.ui.AsyncProcessIcon; +import com.intellij.util.ui.FormBuilder; +import com.intellij.util.ui.JBUI; +import com.intellij.util.ui.components.BorderLayoutPanel; +import com.redhat.devtools.intellij.quarkus.QuarkusBundle; +import com.redhat.devtools.intellij.quarkus.QuarkusConstants; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionListener; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static com.redhat.devtools.intellij.quarkus.QuarkusConstants.QUARKUS_CODE_URL; +import static com.redhat.devtools.intellij.quarkus.projectWizard.QuarkusModelRegistry.DEFAULT_TIMEOUT_IN_SEC; +import static com.redhat.devtools.intellij.quarkus.projectWizard.RequestHelper.waitFor; + +/** + * Quarkus Code endpoint selection step of the Quarkus project wizard. + */ +public class QuarkusCodeEndpointChooserStep extends ModuleWizardStep implements Disposable { + private static final String LAST_ENDPOINT_URL = "quarkus.code.endpoint.url.last"; + private static final String ENDPOINT_URL_HISTORY = "quarkus.code.endpoint.url.history"; + private final WizardContext wizardContext; + private final JBRadioButton defaultRadioButton = new JBRadioButton("Default:", true); + private final JBRadioButton customRadioButton = new JBRadioButton("Custom:", false); + private final TextFieldWithStoredHistory endpointURL = new TextFieldWithStoredHistory(ENDPOINT_URL_HISTORY); + private final ComponentWithBrowseButton customUrlWithBrowseButton; + private final JPanel component; + + private Future loadingRequest = null; + + private QuarkusModel streams = null; + + private AsyncProcessIcon spinner = new AsyncProcessIcon(QuarkusBundle.message("quarkus.wizard.loading.streams")); + + QuarkusCodeEndpointChooserStep(WizardContext wizardContext) { + Disposer.register(wizardContext.getDisposable(), this); + this.customUrlWithBrowseButton = new ComponentWithBrowseButton(this.endpointURL, e -> { + try { + QuarkusCodeEndpointChooserStep.this.validate(); + BrowserUtil.browse(QuarkusCodeEndpointChooserStep.this.endpointURL.getText()); + } catch (ConfigurationException var3) { + Messages.showErrorDialog(var3.getMessage(), "Cannot open URL"); + } + + }); + + endpointURL.addActionListener(e -> { + if (customRadioButton.isSelected()) { + updateCustomUrl(); + } + }); + + this.wizardContext = wizardContext; + String lastServiceUrl = PropertiesComponent.getInstance().getValue(LAST_ENDPOINT_URL, QUARKUS_CODE_URL); + if (!lastServiceUrl.equals(QUARKUS_CODE_URL)) { + this.endpointURL.setSelectedItem(lastServiceUrl); + this.defaultRadioButton.setSelected(false); + this.customRadioButton.setSelected(true); + } else { + this.defaultRadioButton.setSelected(true); + } + + List history = this.endpointURL.getHistory(); + history.remove(QUARKUS_CODE_URL); + this.endpointURL.setHistory(history); + this.component = createComponent(); + } + + @Override + public void _init() { + super._init(); + SwingUtilities.invokeLater(() -> { + this.updateCustomUrl(); + }); + } + + @Override + public JComponent getComponent() { + return component; + } + + public JPanel createComponent() { + ButtonGroup group = new ButtonGroup(); + group.add(this.defaultRadioButton); + group.add(this.customRadioButton); + ActionListener listener = e -> QuarkusCodeEndpointChooserStep.this.updateCustomUrl(); + this.defaultRadioButton.addActionListener(listener); + this.customRadioButton.addActionListener(listener); + FormBuilder builder = new FormBuilder(); + builder.addComponent(new JBLabel("Choose Quarkus Code endpoint URL.")); + BorderLayoutPanel defaultPanel = JBUI.Panels.simplePanel(10, 0); + defaultPanel.addToLeft(this.defaultRadioButton); + HyperlinkLabel label = new HyperlinkLabel(QUARKUS_CODE_URL); + label.setHyperlinkTarget(QUARKUS_CODE_URL); + defaultPanel.addToCenter(label); + builder.addComponent(defaultPanel); + BorderLayoutPanel customPanel = JBUI.Panels.simplePanel(10, 0); + customPanel.addToLeft(this.customRadioButton); + this.customUrlWithBrowseButton.setButtonIcon(AllIcons.Actions.Preview); + customPanel.addToCenter(this.customUrlWithBrowseButton); + builder.addComponent(customPanel); + builder.addTooltip("Make sure your network connection is active."); + builder.addComponentFillVertically(spinner, 10); + JPanel panel = new JPanel(new BorderLayout()); + panel.add(builder.getPanel(), "North"); + panel.setBorder(JBUI.Borders.emptyLeft(20)); + return panel; + } + + private void updateCustomUrl() { + boolean custom = this.customRadioButton.isSelected(); + this.endpointURL.getTextEditor().setEnabled(custom); + this.endpointURL.getTextEditor().setEditable(custom); + this.endpointURL.setEnabled(custom); + this.customUrlWithBrowseButton.setButtonEnabled(custom); + if (loadingRequest != null) { + loadingRequest.cancel(true); + } + loadingRequest = loadStreams(); + } + + private Future loadStreams() { + final String endpoint = getSelectedEndpointUrl(); + if (endpoint == null || endpoint.isBlank()) { + return null; + } + showSpinner(); + ModalityState modalityState = getModalityState(); + return ApplicationManager.getApplication().executeOnPooledThread(() -> { + try { + return QuarkusModelRegistry.INSTANCE.loadStreams(endpoint, new EmptyProgressIndicator()); + } catch (Exception e) { + if (getComponent().isShowing()) { + ApplicationManager.getApplication().invokeLater(() -> { + if (getComponent().isShowing()) { + Messages.showErrorDialog(QuarkusBundle.message("quarkus.wizard.error.streams.loading.message", endpoint, e.getMessage()), QuarkusBundle.message("quarkus.wizard.error.streams.loading")); + } + }, modalityState); + } + } finally { + if (getComponent().isShowing()) { + ApplicationManager.getApplication().invokeLater(() -> { + if (getComponent().isShowing()) { + hideSpinner(); + } + }, modalityState); + } + } + return null; + }); + } + + private ModalityState getModalityState() { + return ModalityState.stateForComponent(getComponent()); + } + + private void showSpinner() { + spinner.setVisible(true); + spinner.resume(); + } + + private void hideSpinner() { + spinner.suspend(); + spinner.setVisible(false); + } + + @Override + public boolean validate() throws ConfigurationException { + if (this.customRadioButton.isSelected()) { + String serviceUrl = this.endpointURL.getText(); + if (serviceUrl.isEmpty()) { + throw new ConfigurationException("Quarkus Code endpoint URL must be set"); + } else if (!serviceUrl.matches("^https?://.*")) { + throw new ConfigurationException("Invalid custom Quarkus Code endpoint URL"); + } + } + try { + boolean requestComplete = checkRequestComplete(); + if (!requestComplete) { + return false; + } + streams = loadingRequest.get(DEFAULT_TIMEOUT_IN_SEC, TimeUnit.SECONDS); + if (streams == null) { + //Unlikely to happen, most likely, a parsing failure will be caught in the catch block + throw new ConfigurationException(QuarkusBundle.message("quarkus.wizard.error.streams.loading")); + } + } catch (TimeoutException e) { + throw new ConfigurationException(QuarkusBundle.message("quarkus.wizard.error.process.timeout", DEFAULT_TIMEOUT_IN_SEC), QuarkusBundle.message("quarkus.wizard.error.streams.loading")); + } catch (ConfigurationException e) { + throw e; + } catch (Exception e) { + throw new ConfigurationException(QuarkusBundle.message("quarkus.wizard.error.streams.loading"), e, QuarkusBundle.message("quarkus.wizard.error.streams.loading")); + } + return true; + } + + private boolean checkRequestComplete() throws ExecutionException, InterruptedException { + if (loadingRequest == null) { + loadingRequest = loadStreams(); + } + if (loadingRequest.isDone()){ + return true; + } + waitFor(loadingRequest, QuarkusBundle.message("quarkus.wizard.loading.streams"), + DEFAULT_TIMEOUT_IN_SEC*10, + wizardContext.getProject()); + return false; + } + + @Override + public void updateDataModel() { + String endpointURL = getSelectedEndpointUrl(); + if (!Comparing.strEqual(this.wizardContext.getUserData(QuarkusConstants.WIZARD_ENDPOINT_URL_KEY), endpointURL)) { + this.endpointURL.addCurrentTextToHistory(); + this.wizardContext.putUserData(QuarkusConstants.WIZARD_ENDPOINT_URL_KEY, endpointURL); + this.wizardContext.putUserData(QuarkusConstants.WIZARD_QUARKUS_STREAMS, streams); + PropertiesComponent.getInstance().setValue(LAST_ENDPOINT_URL, endpointURL); + } + } + + private String getSelectedEndpointUrl() { + return this.customRadioButton.isSelected() ? this.endpointURL.getText() : QUARKUS_CODE_URL; + } + + @Override + public void dispose() { + if (loadingRequest != null) { + loadingRequest.cancel(true); + loadingRequest = null; + } + streams = null; + } +} diff --git a/src/main/java/com/redhat/devtools/intellij/quarkus/module/QuarkusExtension.java b/src/main/java/com/redhat/devtools/intellij/quarkus/projectWizard/QuarkusExtension.java similarity index 98% rename from src/main/java/com/redhat/devtools/intellij/quarkus/module/QuarkusExtension.java rename to src/main/java/com/redhat/devtools/intellij/quarkus/projectWizard/QuarkusExtension.java index 2c946adca..1df78ccce 100644 --- a/src/main/java/com/redhat/devtools/intellij/quarkus/module/QuarkusExtension.java +++ b/src/main/java/com/redhat/devtools/intellij/quarkus/projectWizard/QuarkusExtension.java @@ -8,7 +8,7 @@ * Contributors: * Red Hat, Inc. - initial API and implementation ******************************************************************************/ -package com.redhat.devtools.intellij.quarkus.module; +package com.redhat.devtools.intellij.quarkus.projectWizard; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/com/redhat/devtools/intellij/quarkus/module/QuarkusExtensionsModel.java b/src/main/java/com/redhat/devtools/intellij/quarkus/projectWizard/QuarkusExtensionsModel.java similarity index 96% rename from src/main/java/com/redhat/devtools/intellij/quarkus/module/QuarkusExtensionsModel.java rename to src/main/java/com/redhat/devtools/intellij/quarkus/projectWizard/QuarkusExtensionsModel.java index 2ace6f727..9f36ddecc 100644 --- a/src/main/java/com/redhat/devtools/intellij/quarkus/module/QuarkusExtensionsModel.java +++ b/src/main/java/com/redhat/devtools/intellij/quarkus/projectWizard/QuarkusExtensionsModel.java @@ -8,7 +8,7 @@ * Contributors: * Red Hat, Inc. - initial API and implementation ******************************************************************************/ -package com.redhat.devtools.intellij.quarkus.module; +package com.redhat.devtools.intellij.quarkus.projectWizard; import java.util.ArrayList; import java.util.Collections; diff --git a/src/main/java/com/redhat/devtools/intellij/quarkus/module/QuarkusExtensionsStep.java b/src/main/java/com/redhat/devtools/intellij/quarkus/projectWizard/QuarkusExtensionsStep.java similarity index 99% rename from src/main/java/com/redhat/devtools/intellij/quarkus/module/QuarkusExtensionsStep.java rename to src/main/java/com/redhat/devtools/intellij/quarkus/projectWizard/QuarkusExtensionsStep.java index 60677a117..e5489ec98 100644 --- a/src/main/java/com/redhat/devtools/intellij/quarkus/module/QuarkusExtensionsStep.java +++ b/src/main/java/com/redhat/devtools/intellij/quarkus/projectWizard/QuarkusExtensionsStep.java @@ -8,7 +8,7 @@ * Contributors: * Red Hat, Inc. - initial API and implementation ******************************************************************************/ -package com.redhat.devtools.intellij.quarkus.module; +package com.redhat.devtools.intellij.quarkus.projectWizard; import com.intellij.ide.BrowserUtil; import com.intellij.ide.util.projectWizard.ModuleWizardStep; diff --git a/src/main/java/com/redhat/devtools/intellij/quarkus/module/QuarkusModel.java b/src/main/java/com/redhat/devtools/intellij/quarkus/projectWizard/QuarkusModel.java similarity index 66% rename from src/main/java/com/redhat/devtools/intellij/quarkus/module/QuarkusModel.java rename to src/main/java/com/redhat/devtools/intellij/quarkus/projectWizard/QuarkusModel.java index b1338505f..384fa000f 100644 --- a/src/main/java/com/redhat/devtools/intellij/quarkus/module/QuarkusModel.java +++ b/src/main/java/com/redhat/devtools/intellij/quarkus/projectWizard/QuarkusModel.java @@ -8,16 +8,19 @@ * Contributors: * Red Hat, Inc. - initial API and implementation ******************************************************************************/ -package com.redhat.devtools.intellij.quarkus.module; +package com.redhat.devtools.intellij.quarkus.projectWizard; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.progress.ProgressIndicator; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.ExecutionException; -public class QuarkusModel { +public class QuarkusModel implements Disposable { private String baseURL; private List streams; @@ -32,7 +35,16 @@ public List getStreams() { return streams; } + //Used in Unit test public QuarkusExtensionsModel getExtensionsModel(String key, ProgressIndicator indicator) throws IOException { + try { + return ApplicationManager.getApplication().executeOnPooledThread(() -> loadExtensionsModel(key, indicator)).get(); + } catch (InterruptedException | ExecutionException e) { + throw new IOException(e); + } + } + + public QuarkusExtensionsModel loadExtensionsModel(String key, ProgressIndicator indicator) throws IOException { QuarkusExtensionsModel extensionsModel = extensionsModelMap.get(key); if (extensionsModel == null) { extensionsModel = QuarkusModelRegistry.loadExtensionsModel(baseURL, key, indicator); @@ -40,4 +52,9 @@ public QuarkusExtensionsModel getExtensionsModel(String key, ProgressIndicator i } return extensionsModel; } + + @Override + public void dispose() { + extensionsModelMap.clear(); + } } diff --git a/src/main/java/com/redhat/devtools/intellij/quarkus/module/QuarkusModelRegistry.java b/src/main/java/com/redhat/devtools/intellij/quarkus/projectWizard/QuarkusModelRegistry.java similarity index 59% rename from src/main/java/com/redhat/devtools/intellij/quarkus/module/QuarkusModelRegistry.java rename to src/main/java/com/redhat/devtools/intellij/quarkus/projectWizard/QuarkusModelRegistry.java index 8d3a7bae7..51305818f 100644 --- a/src/main/java/com/redhat/devtools/intellij/quarkus/module/QuarkusModelRegistry.java +++ b/src/main/java/com/redhat/devtools/intellij/quarkus/projectWizard/QuarkusModelRegistry.java @@ -8,7 +8,7 @@ * Contributors: * Red Hat, Inc. - initial API and implementation ******************************************************************************/ -package com.redhat.devtools.intellij.quarkus.module; +package com.redhat.devtools.intellij.quarkus.projectWizard; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; @@ -22,6 +22,8 @@ import com.intellij.util.Urls; import com.intellij.util.io.HttpRequests; import com.intellij.util.io.RequestBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.zeroturnaround.zip.ZipUtil; import java.io.File; @@ -31,26 +33,23 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; -import static com.redhat.devtools.intellij.quarkus.QuarkusConstants.CODE_ARTIFACT_ID_PARAMETER_NAME; -import static com.redhat.devtools.intellij.quarkus.QuarkusConstants.CODE_CLASSNAME_PARAMETER_NAME; -import static com.redhat.devtools.intellij.quarkus.QuarkusConstants.CODE_EXTENSIONS_PARAMETER_NAME; -import static com.redhat.devtools.intellij.quarkus.QuarkusConstants.CODE_GROUP_ID_PARAMETER_NAME; -import static com.redhat.devtools.intellij.quarkus.QuarkusConstants.CODE_NO_EXAMPLES_DEFAULT; -import static com.redhat.devtools.intellij.quarkus.QuarkusConstants.CODE_NO_EXAMPLES_NAME; -import static com.redhat.devtools.intellij.quarkus.QuarkusConstants.CODE_PATH_PARAMETER_NAME; -import static com.redhat.devtools.intellij.quarkus.QuarkusConstants.CODE_QUARKUS_IO_CLIENT_CONTACT_EMAIL_HEADER_NAME; -import static com.redhat.devtools.intellij.quarkus.QuarkusConstants.CODE_QUARKUS_IO_CLIENT_CONTACT_EMAIL_HEADER_VALUE; -import static com.redhat.devtools.intellij.quarkus.QuarkusConstants.CODE_QUARKUS_IO_CLIENT_NAME_HEADER_NAME; -import static com.redhat.devtools.intellij.quarkus.QuarkusConstants.CODE_QUARKUS_IO_CLIENT_NAME_HEADER_VALUE; -import static com.redhat.devtools.intellij.quarkus.QuarkusConstants.CODE_STREAM_PARAMETER_NAME; -import static com.redhat.devtools.intellij.quarkus.QuarkusConstants.CODE_TOOL_PARAMETER_NAME; -import static com.redhat.devtools.intellij.quarkus.QuarkusConstants.CODE_VERSION_PARAMETER_NAME; -import static com.redhat.devtools.intellij.quarkus.QuarkusConstants.PLATFORM_ONLY_PARAMETER; +import static com.redhat.devtools.intellij.quarkus.QuarkusConstants.*; +/** + * Quarkus model registry fetching and caching data for Quarkus stream versions and extensions. + */ public class QuarkusModelRegistry { - private static final String EXTENSIONS_SUFFIX = "/api/extensions/stream/"; + private static final Logger LOGGER = LoggerFactory.getLogger(QuarkusModelRegistry.class); + /** + * Default request timeout in seconds + */ + public static final int DEFAULT_TIMEOUT_IN_SEC = 10; + public static final int DEFAULT_TIMEOUT_IN_MS = DEFAULT_TIMEOUT_IN_SEC*1000; + private static final String EXTENSIONS_SUFFIX = "/api/extensions/stream/"; private static final String STREAMS_SUFFIX = "/api/streams"; public static final QuarkusModelRegistry INSTANCE = new QuarkusModelRegistry(); @@ -72,33 +71,42 @@ private static String computeUserAgent() { } public QuarkusModel load(String endPointURL, ProgressIndicator indicator) throws IOException { + try { + return ApplicationManager.getApplication().executeOnPooledThread(() -> loadStreams(endPointURL, indicator)).get(DEFAULT_TIMEOUT_IN_SEC, TimeUnit.SECONDS); + } catch (InterruptedException|ExecutionException|TimeoutException e) { + throw new IOException(e); + } + } + + public QuarkusModel loadStreams(String endPointURL, ProgressIndicator indicator) throws IOException { + long start = System.currentTimeMillis(); String normalizedEndPointURL = normalizeURL(endPointURL); - indicator.setText("Looking up Quarkus model from endpoint " + endPointURL); - QuarkusModel model = models.get(endPointURL); - if (model == null) { - indicator.setText("Loading Quarkus model from endpoint " + endPointURL); - try { - model = ApplicationManager.getApplication().executeOnPooledThread(() -> HttpRequests.request(normalizedEndPointURL + STREAMS_SUFFIX).userAgent(USER_AGENT).tuner(request -> { + indicator.setText("Looking up Quarkus streams from endpoint " + endPointURL); + QuarkusModel streamModel = models.get(endPointURL); + if (streamModel != null) { + return streamModel; + } + indicator.setText("Loading Quarkus streams from endpoint " + endPointURL); + streamModel = HttpRequests.request(normalizedEndPointURL + STREAMS_SUFFIX) + .connectTimeout(DEFAULT_TIMEOUT_IN_MS) + .readTimeout(DEFAULT_TIMEOUT_IN_MS) + .userAgent(USER_AGENT) + .tuner(request -> { request.setRequestProperty(CODE_QUARKUS_IO_CLIENT_NAME_HEADER_NAME, CODE_QUARKUS_IO_CLIENT_NAME_HEADER_VALUE); request.setRequestProperty(CODE_QUARKUS_IO_CLIENT_CONTACT_EMAIL_HEADER_NAME, CODE_QUARKUS_IO_CLIENT_CONTACT_EMAIL_HEADER_VALUE); - }).connect(request -> { - try (Reader reader = request.getReader(indicator)) { - List streams = mapper.readValue(reader, new TypeReference>() { - }); - QuarkusModel newModel = new QuarkusModel(normalizedEndPointURL, streams); - return newModel; - } catch (IOException e) { - throw new ProcessCanceledException(e); - } - })).get(); - } catch (InterruptedException|ExecutionException e) { - throw new IOException(e); - } - } - if (model == null) { - throw new IOException(); - } - return model; + }) + .connect(request -> { + try (Reader reader = request.getReader(indicator)) { + List streams = mapper.readValue(reader, new TypeReference>() { + }); + QuarkusModel model = new QuarkusModel(normalizedEndPointURL, streams); + long elapsed = System.currentTimeMillis() - start; + LOGGER.info("Loaded Quarkus streams in {} ms", elapsed); + return model; + } + }); + models.put(endPointURL, streamModel); + return streamModel; } private static String normalizeURL(String endPointURL) { @@ -110,30 +118,25 @@ private static String normalizeURL(String endPointURL) { } public static QuarkusExtensionsModel loadExtensionsModel(String endPointURL, String key, ProgressIndicator indicator) throws IOException { + long start = System.currentTimeMillis(); String normalizedEndPointURL = normalizeURL(endPointURL); indicator.setText("Looking up Quarkus extensions from endpoint " + endPointURL + " and key " + key); - QuarkusExtensionsModel model = null; - try { - model = ApplicationManager.getApplication().executeOnPooledThread(() -> HttpRequests.request(normalizedEndPointURL + EXTENSIONS_SUFFIX + key + "?" + PLATFORM_ONLY_PARAMETER + "=false").userAgent(USER_AGENT).tuner(request -> { - request.setRequestProperty(CODE_QUARKUS_IO_CLIENT_NAME_HEADER_NAME, CODE_QUARKUS_IO_CLIENT_NAME_HEADER_VALUE); - request.setRequestProperty(CODE_QUARKUS_IO_CLIENT_CONTACT_EMAIL_HEADER_NAME, CODE_QUARKUS_IO_CLIENT_CONTACT_EMAIL_HEADER_VALUE); - }).connect(request -> { - try (Reader reader = request.getReader(indicator)) { - List extensions = mapper.readValue(reader, new TypeReference>() { - }); - QuarkusExtensionsModel newModel = new QuarkusExtensionsModel(key, extensions); - return newModel; - } catch (IOException e) { - throw new ProcessCanceledException(e); - } - })).get(); - } catch (InterruptedException|ExecutionException e) { - throw new IOException(e); - } - if (model == null) { - throw new IOException(); - } - return model; + String query = normalizedEndPointURL + EXTENSIONS_SUFFIX + key + "?" + PLATFORM_ONLY_PARAMETER + "=false"; + return HttpRequests.request(query).userAgent(USER_AGENT).tuner(request -> { + request.setRequestProperty(CODE_QUARKUS_IO_CLIENT_NAME_HEADER_NAME, CODE_QUARKUS_IO_CLIENT_NAME_HEADER_VALUE); + request.setRequestProperty(CODE_QUARKUS_IO_CLIENT_CONTACT_EMAIL_HEADER_NAME, CODE_QUARKUS_IO_CLIENT_CONTACT_EMAIL_HEADER_VALUE); + }).connect(request -> { + try (Reader reader = request.getReader(indicator)) { + List extensions = mapper.readValue(reader, new TypeReference>() { + }); + QuarkusExtensionsModel newModel = new QuarkusExtensionsModel(key, extensions); + long elapsed = System.currentTimeMillis() - start; + LOGGER.info("Loaded Quarkus extensions in {} ms", elapsed); + return newModel; + } catch (IOException e) { + throw new ProcessCanceledException(e); + } + }); } public static void zip(String endpoint, String tool, String groupId, String artifactId, String version, @@ -183,9 +186,7 @@ private static String buildParameters(String tool, String groupId, String artifa return json.toString(); } - public static void zip(String endpoint, String tool, String groupId, String artifactId, String version, - String className, String path, QuarkusExtensionsModel model, File output) throws IOException { - zip(endpoint, tool, groupId, artifactId, version, className, path, model, output, true); - } - + public void reset() { + models.clear(); } +} diff --git a/src/main/java/com/redhat/devtools/intellij/quarkus/module/QuarkusModuleBuilder.java b/src/main/java/com/redhat/devtools/intellij/quarkus/projectWizard/QuarkusModuleBuilder.java similarity index 93% rename from src/main/java/com/redhat/devtools/intellij/quarkus/module/QuarkusModuleBuilder.java rename to src/main/java/com/redhat/devtools/intellij/quarkus/projectWizard/QuarkusModuleBuilder.java index 61b5abb1a..5a83e78c9 100644 --- a/src/main/java/com/redhat/devtools/intellij/quarkus/module/QuarkusModuleBuilder.java +++ b/src/main/java/com/redhat/devtools/intellij/quarkus/projectWizard/QuarkusModuleBuilder.java @@ -8,13 +8,9 @@ * Contributors: * Red Hat, Inc. - initial API and implementation ******************************************************************************/ -package com.redhat.devtools.intellij.quarkus.module; +package com.redhat.devtools.intellij.quarkus.projectWizard; -import com.intellij.ide.util.projectWizard.JavaModuleBuilder; -import com.intellij.ide.util.projectWizard.ModuleNameLocationSettings; -import com.intellij.ide.util.projectWizard.ModuleWizardStep; -import com.intellij.ide.util.projectWizard.SettingsStep; -import com.intellij.ide.util.projectWizard.WizardContext; +import com.intellij.ide.util.projectWizard.*; import com.intellij.openapi.Disposable; import com.intellij.openapi.module.ModifiableModuleModel; import com.intellij.openapi.module.Module; @@ -33,7 +29,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import javax.swing.Icon; +import javax.swing.*; import java.io.File; import java.io.IOException; diff --git a/src/main/java/com/redhat/devtools/intellij/quarkus/projectWizard/QuarkusModuleInfoStep.java b/src/main/java/com/redhat/devtools/intellij/quarkus/projectWizard/QuarkusModuleInfoStep.java new file mode 100644 index 000000000..ef8d124ca --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/quarkus/projectWizard/QuarkusModuleInfoStep.java @@ -0,0 +1,286 @@ +/******************************************************************************* + * Copyright (c) 2019 Red Hat, Inc. + * Distributed under license by Red Hat, Inc. All rights reserved. + * This program is made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, + * and is available at https://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ +package com.redhat.devtools.intellij.quarkus.projectWizard; + +import com.intellij.ide.util.projectWizard.ModuleWizardStep; +import com.intellij.ide.util.projectWizard.WizardContext; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.options.ConfigurationException; +import com.intellij.openapi.progress.EmptyProgressIndicator; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.ui.ComboBox; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Disposer; +import com.intellij.ui.CollectionComboBoxModel; +import com.intellij.ui.ColoredListCellRenderer; +import com.intellij.ui.ScrollPaneFactory; +import com.intellij.ui.SimpleTextAttributes; +import com.intellij.ui.components.JBCheckBox; +import com.intellij.ui.components.JBLoadingPanel; +import com.intellij.ui.components.JBTextField; +import com.intellij.util.ui.AsyncProcessIcon; +import com.intellij.util.ui.FormBuilder; +import com.intellij.util.ui.JBUI; +import com.redhat.devtools.intellij.quarkus.QuarkusBundle; +import com.redhat.devtools.intellij.quarkus.QuarkusConstants; +import com.redhat.devtools.intellij.quarkus.tool.ToolDelegate; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.swing.*; +import javax.swing.event.ListDataEvent; +import javax.swing.event.ListDataListener; +import java.awt.*; +import java.util.Arrays; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static com.redhat.devtools.intellij.quarkus.projectWizard.QuarkusModelRegistry.DEFAULT_TIMEOUT_IN_SEC; +import static com.redhat.devtools.intellij.quarkus.projectWizard.RequestHelper.waitFor; + +/** + * Quarkus module coordinates selection step of the Quarkus project wizard. + */ +public class QuarkusModuleInfoStep extends ModuleWizardStep implements Disposable { + private static final Logger LOGGER = LoggerFactory.getLogger(QuarkusModuleInfoStep.class); + + private final JBLoadingPanel panel = new JBLoadingPanel(new BorderLayout(), this, 300); + + private ComboBox streamComboBox; + + private ComboBox toolComboBox; + + private JBCheckBox exampleField; + + private JBTextField groupIdField; + + private JBTextField artifactIdField; + + private JBTextField versionField; + + private JBTextField classNameField; + + private JBTextField pathField; + + private AsyncProcessIcon spinner = new AsyncProcessIcon(QuarkusBundle.message("quarkus.wizard.loading.extensions")); + + private final WizardContext context; + + private QuarkusModel model; + + private Future extensionsModelRequest; + + private QuarkusExtensionsModel extensionsModel; + private CollectionComboBoxModel streamModel; + private EmptyProgressIndicator indicator; + + public QuarkusModuleInfoStep(WizardContext context) { + Disposer.register(context.getDisposable(), this); + this.context = context; + } + + @Override + public JComponent getComponent() { + return panel; + } + + @Override + public void updateDataModel() { + context.putUserData(QuarkusConstants.WIZARD_TOOL_KEY, (ToolDelegate)toolComboBox.getModel().getSelectedItem()); + context.putUserData(QuarkusConstants.WIZARD_EXAMPLE_KEY, exampleField.isSelected()); + context.putUserData(QuarkusConstants.WIZARD_GROUPID_KEY, groupIdField.getText()); + context.putUserData(QuarkusConstants.WIZARD_ARTIFACTID_KEY, artifactIdField.getText()); + context.putUserData(QuarkusConstants.WIZARD_VERSION_KEY, versionField.getText()); + context.putUserData(QuarkusConstants.WIZARD_CLASSNAME_KEY, classNameField.getText()); + context.putUserData(QuarkusConstants.WIZARD_PATH_KEY, pathField.getText()); + context.putUserData(QuarkusConstants.WIZARD_EXTENSIONS_MODEL_KEY, extensionsModel); + } + + @Override + public void dispose() { + if (model != null) { + model.dispose(); + } + if (extensionsModelRequest != null) { + extensionsModelRequest.cancel(true); + extensionsModelRequest = null; + } + model = null; + } + + @Override + public void _init() { + panel.setBorder(JBUI.Borders.empty(20)); + + indicator = new EmptyProgressIndicator() { + @Override + public void setText(String text) { + SwingUtilities.invokeLater(() -> panel.setLoadingText(text)); + } + }; + model = context.getUserData(QuarkusConstants.WIZARD_QUARKUS_STREAMS); + final FormBuilder formBuilder = new FormBuilder(); + streamModel = new CollectionComboBoxModel<>(model.getStreams()); + streamModel.setSelectedItem(model.getStreams().stream().filter(QuarkusStream::isRecommended).findFirst().orElse(model.getStreams().get(0))); + streamModel.addListDataListener(new ListDataListener() { + @Override + public void intervalAdded(ListDataEvent e) { + } + + @Override + public void intervalRemoved(ListDataEvent e) { + } + + @Override + public void contentsChanged(ListDataEvent e) { + loadExtensionsModel(streamModel, indicator); + } + }); + + streamComboBox = new ComboBox<>(streamModel); + streamComboBox.setRenderer(new ColoredListCellRenderer() { + @Override + protected void customizeCellRenderer(@NotNull JList list, QuarkusStream stream, int index, boolean selected, boolean hasFocus) { + if (stream.isRecommended()) { + this.append(stream.getPlatformVersion(), SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES, true); + } else { + this.append(stream.getPlatformVersion(), SimpleTextAttributes.REGULAR_ATTRIBUTES, true); + } + if (stream.getStatus() != null) { + this.append(" ").append(stream.getStatus()); + } + } + }); + JComponent streamComponent = new JComponent() {}; + streamComponent.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); + streamComponent.add(streamComboBox); + streamComponent.add(spinner); + streamComponent.add(Box.createHorizontalGlue()); + + formBuilder.addLabeledComponent("Quarkus stream:", streamComponent); + + final CollectionComboBoxModel toolModel = new CollectionComboBoxModel<>(Arrays.asList(ToolDelegate.getDelegates())); + toolComboBox = new ComboBox<>(toolModel); + toolComboBox.setRenderer(new ColoredListCellRenderer() { + @Override + protected void customizeCellRenderer(@NotNull JList list, ToolDelegate toolDelegate, int index, boolean selected, boolean hasFocus) { + this.append(toolDelegate.getDisplay()); + } + }); + formBuilder.addLabeledComponent("Tool:", toolComboBox); + exampleField = new JBCheckBox("If selected, project will contain sample code from extensions that suppport codestarts.", true); + formBuilder.addLabeledComponent("Example code:", exampleField); + groupIdField = new JBTextField("org.acme"); + formBuilder.addLabeledComponent("Group:", groupIdField); + artifactIdField = new JBTextField("code-with-quarkus"); + formBuilder.addLabeledComponent("Artifact:", artifactIdField); + versionField = new JBTextField("1.0.0-SNAPSHOT"); + formBuilder.addLabeledComponent("Version:", versionField); + classNameField = new JBTextField("org.acme.ExampleResource"); + formBuilder.addLabeledComponent("Class name:", classNameField); + pathField = new JBTextField("/hello"); + formBuilder.addLabeledComponent("Path:", pathField); + panel.add(ScrollPaneFactory.createScrollPane(formBuilder.getPanel(), true), "North"); + hideSpinner(); + extensionsModelRequest = loadExtensionsModel(streamModel, indicator); + } + + private Future loadExtensionsModel(CollectionComboBoxModel streamModel, ProgressIndicator indicator) { + String key = ((QuarkusStream) streamModel.getSelectedItem()).getKey(); + if (key == null) { + return null; + } + showSpinner(); + ModalityState modalityState = getModalityState(); + return ApplicationManager.getApplication().executeOnPooledThread(() -> { + try { + return model.loadExtensionsModel(key, indicator); + } catch (Exception e) { + if (getComponent().isShowing()) { + ApplicationManager.getApplication().invokeLater(() -> { + Messages.showErrorDialog(QuarkusBundle.message("quarkus.wizard.error.extensions.loading.message", key, e.getMessage()), QuarkusBundle.message("quarkus.wizard.error.extensions.loading")); + }, modalityState); + } + } finally { + if (getComponent().isShowing()) { + ApplicationManager.getApplication().invokeLater(() -> { + if (getComponent().isShowing()) { + hideSpinner(); + } + }, modalityState); + } + } + return null; + }); + } + + @Override + public boolean validate() throws ConfigurationException { + if (groupIdField.getText().isEmpty()) { + throw new ConfigurationException("Group must be specified"); + } + if (artifactIdField.getText().isEmpty()) { + throw new ConfigurationException("Artifact must be specified"); + } + if (versionField.getText().isEmpty()) { + throw new ConfigurationException("Version must be specified"); + } + try { + boolean requestComplete = checkRequestComplete(); + if (!requestComplete) { + return false; + } + extensionsModel = extensionsModelRequest.get(DEFAULT_TIMEOUT_IN_SEC, TimeUnit.SECONDS); + if (extensionsModel == null) { + //Unlikely to happen, most likely, a parsing failure will be caught in the catch block + throw new ConfigurationException(QuarkusBundle.message("quarkus.wizard.error.extensions.loading")); + } + } catch (TimeoutException e) { + throw new ConfigurationException(QuarkusBundle.message("quarkus.wizard.error.process.timeout", DEFAULT_TIMEOUT_IN_SEC), QuarkusBundle.message("quarkus.wizard.error.extensions.loading")); + } catch (ConfigurationException e) { + throw e; + } catch (Exception e) { + throw new ConfigurationException(QuarkusBundle.message("quarkus.wizard.error.extensions.loading"), e, QuarkusBundle.message("quarkus.wizard.error.extensions.loading")); + } + return true; + } + + private ModalityState getModalityState() { + return ModalityState.stateForComponent(getComponent()); + } + + private void showSpinner() { + spinner.setVisible(true); + spinner.resume(); + } + + private void hideSpinner() { + spinner.setVisible(false); + spinner.suspend(); + } + + private boolean checkRequestComplete() { + if (extensionsModelRequest == null) { + extensionsModelRequest = loadExtensionsModel(streamModel, indicator); + } + if (extensionsModelRequest.isDone()) { + return true; + } + waitFor(extensionsModelRequest, QuarkusBundle.message("quarkus.wizard.loading.extensions"), + DEFAULT_TIMEOUT_IN_SEC*10, + context.getProject()); + return false; + } +} diff --git a/src/main/java/com/redhat/devtools/intellij/quarkus/module/QuarkusStream.java b/src/main/java/com/redhat/devtools/intellij/quarkus/projectWizard/QuarkusStream.java similarity index 96% rename from src/main/java/com/redhat/devtools/intellij/quarkus/module/QuarkusStream.java rename to src/main/java/com/redhat/devtools/intellij/quarkus/projectWizard/QuarkusStream.java index 9eea2fd47..0cb15de8a 100644 --- a/src/main/java/com/redhat/devtools/intellij/quarkus/module/QuarkusStream.java +++ b/src/main/java/com/redhat/devtools/intellij/quarkus/projectWizard/QuarkusStream.java @@ -8,7 +8,7 @@ * Contributors: * Red Hat, Inc. - initial API and implementation ******************************************************************************/ -package com.redhat.devtools.intellij.quarkus.module; +package com.redhat.devtools.intellij.quarkus.projectWizard; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/com/redhat/devtools/intellij/quarkus/projectWizard/RequestHelper.java b/src/main/java/com/redhat/devtools/intellij/quarkus/projectWizard/RequestHelper.java new file mode 100644 index 000000000..217f8581a --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/quarkus/projectWizard/RequestHelper.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * Copyright (c) 2019 Red Hat, Inc. + * Distributed under license by Red Hat, Inc. All rights reserved. + * This program is made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, + * and is available at https://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ +package com.redhat.devtools.intellij.quarkus.projectWizard; + +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +/** + * Shows a modal Progress dialog while waiting for a {@link Future} to complete + */ +public class RequestHelper { + + private RequestHelper() { + } + + /** + * Shows a modal Progress dialog while waiting for a {@link Future} to complete. + * @param request the request to wait for + * @param title the title of the progress bar + * @param durationInMs the maximum duration in milliseconds to keep the progress dialog open + * @param project the {@link Project} this dialog applies to + */ + public static void waitFor(@NotNull Future request, @NotNull String title, int durationInMs, Project project) { + if (isComplete(request)) { + return; + } + ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> { + //Let's wait for DEFAULT_TIMEOUT sec at most (request should time out before that) + ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); + indicator.setIndeterminate(true); + // Wait for the loading to finish + for (int i = 0; i < durationInMs; i++) { + indicator.checkCanceled(); + if (isComplete(request)) { + return; + } + try { + TimeUnit.MILLISECONDS.sleep(100); + } catch (InterruptedException e) { + throw new ProcessCanceledException(e); + } + } + }, title, true, project); + } + + private static boolean isComplete(Future future){ + return future.isDone() || future.isCancelled(); + } +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index cc3157114..ea4f68ef0 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -321,7 +321,7 @@ - + category.getExtensions().stream().filter(extension -> extension.getName().equals(name)). - forEach(extension -> extension.setSelected(true))); + Stream extStream = model.getCategories().stream().flatMap(cat -> cat.getExtensions().stream()).filter(extension -> extension.getName().equals(name)); + List extensions = extStream.collect(Collectors.toList()); + if (extensions.isEmpty()) { + fail("Could not find any "+name+" extension in "+model.getKey()); + } + extensions.forEach(extension -> extension.setSelected(true)); } private QuarkusExtensionsModel getRecommendedModel(QuarkusModel model) throws IOException { @@ -95,7 +106,7 @@ private File checkBaseMavenProject(boolean examples) throws IOException { File folder = temporaryFolder.newFolder(); QuarkusModel model = registry.load(QUARKUS_CODE_URL, new EmptyProgressIndicator()); QuarkusExtensionsModel extensionsModel = getRecommendedModel(model); - enableExtension(extensionsModel, "RESTEasy JAX-RS"); + enableExtension(extensionsModel, JAXRS_EXTENSION); QuarkusModelRegistry.zip(QUARKUS_CODE_URL, "MAVEN", "org.acme", "code-with-quarkus", "0.0.1-SNAPSHOT", "org.acme.ExampleResource", "/example", extensionsModel, folder, examples); @@ -136,7 +147,7 @@ private File checkBaseGradleProject(boolean examples) throws IOException { File folder = temporaryFolder.newFolder(); QuarkusModel model = registry.load(QUARKUS_CODE_URL, new EmptyProgressIndicator()); QuarkusExtensionsModel extensionsModel = getRecommendedModel(model); - enableExtension(extensionsModel, "RESTEasy JAX-RS"); + enableExtension(extensionsModel, JAXRS_EXTENSION); QuarkusModelRegistry.zip(QUARKUS_CODE_URL, "GRADLE", "org.acme", "code-with-quarkus", "0.0.1-SNAPSHOT", "org.acme.ExampleResource", "/example", extensionsModel, folder, examples);