diff --git a/Jenkinsfile b/Jenkinsfile index 37a9ad6..46ca90b 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -16,7 +16,7 @@ node('rhel7'){ def isSnapshot = props['projectVersion'].contains('-SNAPSHOT') def version = isSnapshot?props['projectVersion'].replace('-SNAPSHOT', ".${env.BUILD_NUMBER}"):props['projectVersion'] + ".${env.BUILD_NUMBER}" - // github user and token are required for consuming the crda-java-api module from GHPR in build-time + // github user and token are required for consuming the exhort-java-api module from GHPR in build-time withCredentials([[$class: 'StringBinding', credentialsId: 'rhdevelopersci-github-token', variable: 'GITHUB_TOKEN']]) { stage('Build') { sh "./gradlew assemble -PprojectVersion=${version} -Pgpr.username=rhdevelopers-ci -Pgpr.token=${GITHUB_TOKEN}" @@ -44,7 +44,7 @@ node('rhel7'){ stage("Publish to Marketplace") { unstash 'zip' - // github user and token are required for consuming the crda-java-api module from GHPR in build-time + // github user and token are required for consuming the exhort-java-api module from GHPR in build-time withCredentials([[$class: 'StringBinding', credentialsId: 'rhdevelopersci-github-token', variable: 'GITHUB_TOKEN']]) { withCredentials([[$class: 'StringBinding', credentialsId: 'JetBrains marketplace token', variable: 'TOKEN']]) { sh "./gradlew publishPlugin -PjetBrainsToken=${TOKEN} -PprojectVersion=${version} -PjetBrainsChannel=${channel} -Pgpr.username=rhdevelopers-ci -Pgpr.token=${GITHUB_TOKEN}" diff --git a/build.gradle b/build.gradle index a048c5d..d591d3e 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ targetCompatibility = '11' intellij { version = ideaVersion //for a full list of IntelliJ IDEA releases please see https://www.jetbrains.com/intellij-repository/releases pluginName = 'org.jboss.tools.intellij.analytics' - plugins = ['com.redhat.devtools.intellij.telemetry:0.0.3.33'] + plugins = ['com.redhat.devtools.intellij.telemetry:0.0.3.33', "org.jetbrains.idea.maven"] updateSinceUntilBuild = false } @@ -48,19 +48,11 @@ dependencies { resolutionStrategy.cacheChangingModulesFor 0, 'seconds' } - // WATCH OUT - lsp4intellij version 0.95.1 breaks diagnostics for opened files - implementation 'com.github.ballerina-platform:lsp4intellij:0.95.0' - constraints { - implementation('com.google.guava:guava:30.0-jre') { - because 'version 27.1-jre introduced by lsp4intellij:0.95.0 reports vulnerabilities' - } - implementation('com.google.code.gson:gson:2.8.9') { - because 'version 2.8.2 introduced by lsp4intellij:0.95.0 reports vulnerabilities' - } - } implementation 'org.kohsuke:github-api:1.314' implementation 'org.apache.commons:commons-compress:1.21' - implementation 'com.redhat.exhort:exhort-java-api:0.0.1-SNAPSHOT' + implementation 'com.redhat.exhort:exhort-java-api:0.0.2-SNAPSHOT' + implementation 'com.github.ben-manes.caffeine:caffeine:3.1.8' + testImplementation('junit:junit:4.13.1') } diff --git a/src/main/java/org/jboss/tools/intellij/analytics/AnalyticsLanguageClient.java b/src/main/java/org/jboss/tools/intellij/analytics/AnalyticsLanguageClient.java deleted file mode 100644 index 4582ffd..0000000 --- a/src/main/java/org/jboss/tools/intellij/analytics/AnalyticsLanguageClient.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.jboss.tools.intellij.analytics; - -import com.intellij.notification.Notification; -import com.intellij.notification.NotificationType; -import com.intellij.notification.Notifications; -import com.redhat.devtools.intellij.telemetry.core.service.TelemetryMessageBuilder; -import com.redhat.devtools.intellij.telemetry.core.service.TelemetryMessageBuilder.ActionMessage; -import org.eclipse.lsp4j.jsonrpc.services.JsonNotification; -import org.jetbrains.annotations.NotNull; -import org.wso2.lsp4intellij.client.ClientContext; -import org.wso2.lsp4intellij.client.DefaultLanguageClient; - -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.file.Paths; -import java.util.Map; - -public class AnalyticsLanguageClient extends DefaultLanguageClient { - public AnalyticsLanguageClient(@NotNull ClientContext context) { - super(context); - } - - private static String getFilename(Map info) { - String filename = null; - String url = (String) info.get("uri"); - if (url != null) { - try { - filename = Paths.get(new URI(url)).getFileName().toString(); - } catch (URISyntaxException e) {} - } - return filename; - } - - @JsonNotification("caNotification") - public void caNotify(Object payload) { - if (payload instanceof Map) { - Map info = (Map) payload; - if (info.containsKey("data") && info.containsKey("diagCount")) { - ActionMessage telemetry = TelemetryService.instance().action("lsp:component_analysis_done"); - String filename = getFilename(info); - if (filename != null) { - telemetry.property("filename", filename); - } - telemetry.send(); - Notifications.Bus.notify(new Notification("Analytics", "Analytics", (String) info.get("data"), NotificationType.INFORMATION)); - } - } - } - -} diff --git a/src/main/java/org/jboss/tools/intellij/analytics/AnalyticsLanguageServerDefinition.java b/src/main/java/org/jboss/tools/intellij/analytics/AnalyticsLanguageServerDefinition.java deleted file mode 100644 index b6e5cea..0000000 --- a/src/main/java/org/jboss/tools/intellij/analytics/AnalyticsLanguageServerDefinition.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.jboss.tools.intellij.analytics; - -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.event.DocumentListener; -import org.eclipse.lsp4j.ServerCapabilities; -import org.eclipse.lsp4j.services.LanguageClient; -import org.eclipse.lsp4j.services.LanguageServer; -import org.wso2.lsp4intellij.client.ClientContext; -import org.wso2.lsp4intellij.client.connection.StreamConnectionProvider; -import org.wso2.lsp4intellij.client.languageserver.ServerOptions; -import org.wso2.lsp4intellij.client.languageserver.requestmanager.DefaultRequestManager; -import org.wso2.lsp4intellij.client.languageserver.requestmanager.RequestManager; -import org.wso2.lsp4intellij.client.languageserver.serverdefinition.RawCommandServerDefinition; -import org.wso2.lsp4intellij.client.languageserver.wrapper.LanguageServerWrapper; -import org.wso2.lsp4intellij.editor.EditorEventManager; -import org.wso2.lsp4intellij.extensions.LSPExtensionManager; -import org.wso2.lsp4intellij.listeners.EditorMouseListenerImpl; -import org.wso2.lsp4intellij.listeners.EditorMouseMotionListenerImpl; -import org.wso2.lsp4intellij.listeners.LSPCaretListenerImpl; - -import java.util.Arrays; - -public class AnalyticsLanguageServerDefinition extends RawCommandServerDefinition implements LSPExtensionManager { - public AnalyticsLanguageServerDefinition(String ext, String[] cmds) { - super(ext, cmds); - } - - @Override - public StreamConnectionProvider createConnectionProvider(String workingDir) { - return new AnalyticsProcessStreamConnectionProvider(Arrays.asList(command), workingDir); - } - - @Override - public T getExtendedRequestManagerFor(LanguageServerWrapper wrapper, LanguageServer server, LanguageClient client, ServerCapabilities serverCapabilities) { - return (T) new DefaultRequestManager(wrapper, server, client, serverCapabilities); - } - - @Override - public T getExtendedEditorEventManagerFor(Editor editor, DocumentListener documentListener, EditorMouseListenerImpl mouseListener, EditorMouseMotionListenerImpl mouseMotionListener, LSPCaretListenerImpl caretListener, RequestManager requestManager, ServerOptions serverOptions, LanguageServerWrapper wrapper) { - return (T) new EditorEventManager(editor, documentListener, mouseListener, mouseMotionListener, caretListener, - requestManager, serverOptions, wrapper); - } - - @Override - public Class getExtendedServerInterface() { - return LanguageServer.class; - } - - @Override - public LanguageClient getExtendedClientFor(ClientContext context) { - return new AnalyticsLanguageClient(context); - } -} diff --git a/src/main/java/org/jboss/tools/intellij/analytics/AnalyticsProcessStreamConnectionProvider.java b/src/main/java/org/jboss/tools/intellij/analytics/AnalyticsProcessStreamConnectionProvider.java deleted file mode 100644 index ab0c03b..0000000 --- a/src/main/java/org/jboss/tools/intellij/analytics/AnalyticsProcessStreamConnectionProvider.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.jboss.tools.intellij.analytics; - -import org.wso2.lsp4intellij.client.connection.ProcessStreamConnectionProvider; - -import java.io.File; -import java.util.List; - -public class AnalyticsProcessStreamConnectionProvider extends ProcessStreamConnectionProvider { - public AnalyticsProcessStreamConnectionProvider(List commands, String workingDir) { - super(createProcessBuilder(commands, workingDir)); - } - - protected static ProcessBuilder createProcessBuilder(List commands, String workingDir) { - ProcessBuilder builder = new ProcessBuilder(commands); - builder.directory(new File(workingDir)); - builder.redirectError(ProcessBuilder.Redirect.INHERIT); - builder.environment().put("RECOMMENDER_API_URL", "https://f8a-analytics-2445582058137.production.gw.apicast.io:443/api/v2"); - builder.environment().put("THREE_SCALE_USER_TOKEN", "9e7da76708fe374d8c10fa752e72989f"); - //builder.environment().put("PROVIDE_FULLSTACK_ACTION", "true"); - return builder; - } -} diff --git a/src/main/java/org/jboss/tools/intellij/analytics/GitHubRelease.java b/src/main/java/org/jboss/tools/intellij/analytics/GitHubRelease.java deleted file mode 100644 index 0f6ff4d..0000000 --- a/src/main/java/org/jboss/tools/intellij/analytics/GitHubRelease.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.jboss.tools.intellij.analytics; - -import org.kohsuke.github.GitHub; -import org.kohsuke.github.GHAsset; -import org.kohsuke.github.GHRepository; -import org.kohsuke.github.GHRelease; -import java.io.IOException; - -public class GitHubRelease { - private final GHRepository repo; - - public GitHubRelease(final String repository) throws IOException { - final GitHub github = GitHub.connectAnonymously(); - this.repo = github.getRepository(repository); - } - - public String getLatestRelease() throws IOException { - return this.repo.getLatestRelease().getTagName(); - } - - public String getDownloadUri(final String releaseLabel, final String fileLabel) throws IOException { - final GHRelease release = this.repo.getReleaseByTagName(releaseLabel); - final GHAsset asset = release.listAssets() - .toList() - .stream() - .filter(a -> a.getName().equals(fileLabel)) - .findFirst() - .orElseThrow(() -> new IOException(fileLabel + ": unable to download")); - return asset.getBrowserDownloadUrl(); - } -} diff --git a/src/main/java/org/jboss/tools/intellij/analytics/GitHubReleaseDownloader.java b/src/main/java/org/jboss/tools/intellij/analytics/GitHubReleaseDownloader.java deleted file mode 100644 index f176049..0000000 --- a/src/main/java/org/jboss/tools/intellij/analytics/GitHubReleaseDownloader.java +++ /dev/null @@ -1,87 +0,0 @@ -package org.jboss.tools.intellij.analytics; - -import java.io.File; -import java.io.IOException; - -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.util.io.HttpRequests; -import com.redhat.devtools.intellij.telemetry.core.service.TelemetryMessageBuilder.ActionMessage; -import com.intellij.openapi.diagnostic.Logger; -import org.jboss.tools.intellij.stackanalysis.Cli; - - -public class GitHubReleaseDownloader { - private static final Logger logger = Logger.getInstance(GitHubReleaseDownloader.class); - private final String fileName; - private final ICookie cookies; - private final GitHubRelease release; - private final boolean forCli; - private ICookie.Name cookieName; // Set name according to LSP/CLI calls - - public GitHubReleaseDownloader(final String fileName, final ICookie cookies, final String repoName, final boolean forCli) throws IOException { - this.fileName = fileName; - this.cookies = cookies; - this.release = new GitHubRelease(repoName); - this.forCli=forCli; // False for LSP download, True for CLI - } - - private boolean isNewRelease(final String releaseLabel) { - if (forCli) { - this.cookieName = ICookie.Name.CLIVersion; - } else { - this.cookieName = ICookie.Name.LSPVersion; - } - final String currentVersion = cookies.getValue(cookieName); - return !releaseLabel.equals(currentVersion); - } - - - public File download() throws IOException { - final ActionMessage telemetry; - final String latestReleaseTag; - final File dest = new File(Platform.pluginDirectory, fileName); - String telemetryAction; - String telemetryProperty; - - if (forCli){ - // CLI Release version is pinned to a latest stable version - // to avoid issues due to ongoing development of CLI - latestReleaseTag = Cli.current.cliReleaseTag; - - // Set telemetry action and property for CLI. - telemetryAction = "cli:download"; - telemetryProperty = "cliVersion"; - } else { - // Get latest LSP release version from repo - latestReleaseTag = this.release.getLatestRelease(); - - // Set telemetry action and property for LSP. - telemetryAction = "lsp:download"; - telemetryProperty = "lspVersion"; - } - - if (!isNewRelease(latestReleaseTag) && dest.exists()) { - return dest; - } - - telemetry = TelemetryService.instance().action(telemetryAction) - .property(telemetryProperty, latestReleaseTag); - - try { - final String url = this.release.getDownloadUri(latestReleaseTag, this.fileName); - HttpRequests - .request(url) - .productNameAsUserAgent() - .saveToFile(dest, ProgressManager.getGlobalProgressIndicator()); - - dest.setExecutable(true); - cookies.setValue(cookieName, latestReleaseTag); - telemetry.send(); - return dest; - } catch (IOException e) { - telemetry.error(e).send(); - logger.warn(e.getLocalizedMessage(), e); - throw e; - } - } -} diff --git a/src/main/java/org/jboss/tools/intellij/analytics/ICookie.java b/src/main/java/org/jboss/tools/intellij/analytics/ICookie.java deleted file mode 100644 index a6fdce8..0000000 --- a/src/main/java/org/jboss/tools/intellij/analytics/ICookie.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.jboss.tools.intellij.analytics; - -public interface ICookie { - enum Name { - LSPVersion, - CLIVersion - } - - void setValue(Name name, String value); - String getValue(Name name); -} diff --git a/src/main/java/org/jboss/tools/intellij/analytics/Platform.java b/src/main/java/org/jboss/tools/intellij/analytics/Platform.java deleted file mode 100644 index f5285ee..0000000 --- a/src/main/java/org/jboss/tools/intellij/analytics/Platform.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.jboss.tools.intellij.analytics; - -import com.intellij.ide.plugins.PluginManagerCore; -import com.intellij.openapi.extensions.PluginId; -import com.intellij.openapi.util.SystemInfo; - -import java.util.Arrays; -import java.util.List; - -public class Platform { - //Set Plugin location in host machine. Location will be used as download location. - public static final String pluginDirectory = PluginManagerCore.getPlugin( - PluginId.getId("org.jboss.tools.intellij.analytics")).getPluginPath().toAbsolutePath().toString(); - - // Set LSP and CLI tarballs to be downloaded, CLI version is pinned to last stable version instead of latest. - private static final Platform WINDOWS = new Platform("analytics-lsp-win.exe", "crda_0.2.5_Windows_64bit.tar.gz"); - private static final Platform LINUX = new Platform("analytics-lsp-linux", "crda_0.2.5_Linux_64bit.tar.gz"); - private static final Platform MACOS = new Platform("analytics-lsp-macos", "crda_0.2.5_macOS_64bit.tar.gz"); - - public String lspBundleName; - public String cliTarBallName; - private Platform(String lspBundleName, String cliTarBallName) { - this.lspBundleName = lspBundleName; - this.cliTarBallName = cliTarBallName; - } - - private static Platform detect() { - if (SystemInfo.isLinux) - return LINUX; - if (SystemInfo.isWindows) - return WINDOWS; - if (SystemInfo.isMac) - return MACOS; - throw new PlatformDetectionException(SystemInfo.OS_NAME + " is not supported"); - } - - public static final Platform current = detect(); - - // Set supported file names - public static final List supportedManifestFiles = Arrays.asList("pom.xml", - "package.json", "go.mod", "requirements.txt", "requirements-dev.txt"); -} diff --git a/src/main/java/org/jboss/tools/intellij/analytics/PlatformDetectionException.java b/src/main/java/org/jboss/tools/intellij/analytics/PlatformDetectionException.java deleted file mode 100644 index f184f50..0000000 --- a/src/main/java/org/jboss/tools/intellij/analytics/PlatformDetectionException.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.jboss.tools.intellij.analytics; - -public class PlatformDetectionException extends RuntimeException { - public PlatformDetectionException(String ex) { - super(ex); - } -} diff --git a/src/main/java/org/jboss/tools/intellij/analytics/PreloadLanguageServer.java b/src/main/java/org/jboss/tools/intellij/analytics/PreloadLanguageServer.java deleted file mode 100644 index a72a92d..0000000 --- a/src/main/java/org/jboss/tools/intellij/analytics/PreloadLanguageServer.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.jboss.tools.intellij.analytics; - -import java.io.File; -import java.io.IOException; -import java.util.List; - -import com.intellij.ide.AppLifecycleListener; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.components.ServiceManager; -import org.jetbrains.annotations.NotNull; -import org.wso2.lsp4intellij.IntellijLanguageClient; - -public final class PreloadLanguageServer implements AppLifecycleListener { - private static final Logger log = Logger.getInstance(PreloadLanguageServer.class); - private final ICookie cookies = ServiceManager.getService(Settings.class); - - private void attachLanguageClient(final File cliFile) { - final String[] cmds = {cliFile.toString(), "--stdio"}; - ApplicationManager.getApplication().invokeAndWait(() -> { - Platform.supportedManifestFiles.stream().map(s -> s.substring(s.lastIndexOf('.') + 1)).distinct().forEach(ext -> { - AnalyticsLanguageServerDefinition serverDefinition = new AnalyticsLanguageServerDefinition(ext, cmds); - IntellijLanguageClient.addServerDefinition(serverDefinition); - IntellijLanguageClient.addExtensionManager(ext, serverDefinition);} - ); - }); - log.warn(String.format("lsp registration done %s", cliFile)); - } - - @Override - public void appFrameCreated(@NotNull List commandLineArgs) { - log.info("lsp preload called"); - ApplicationManager.getApplication().executeOnPooledThread(() -> { - try { - final String devUrl = System.getenv("ANALYTICS_LSP_FILE_PATH"); - File lspBundle; - if (devUrl != null) { - lspBundle = new File(devUrl); - } else { - final GitHubReleaseDownloader bundle = new GitHubReleaseDownloader( - Platform.current.lspBundleName, - cookies, - "fabric8-analytics/fabric8-analytics-lsp-server", - false); - lspBundle = bundle.download(); - - log.info("lsp binary is ready for use."); - } - attachLanguageClient(lspBundle); - } catch(IOException ex) { - log.warn("lsp download fail", ex); - } - }); - } -} diff --git a/src/main/java/org/jboss/tools/intellij/analytics/Settings.java b/src/main/java/org/jboss/tools/intellij/analytics/Settings.java deleted file mode 100644 index 0e83625..0000000 --- a/src/main/java/org/jboss/tools/intellij/analytics/Settings.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.jboss.tools.intellij.analytics; - -import com.intellij.openapi.components.PersistentStateComponent; -import com.intellij.openapi.components.RoamingType; -import com.intellij.openapi.components.State; -import com.intellij.openapi.components.Service; -import com.intellij.openapi.components.Storage; -import com.intellij.util.xmlb.XmlSerializerUtil; -import com.intellij.util.xmlb.annotations.MapAnnotation; - -import java.util.Map; -import java.util.HashMap; - -@Service -@State( - name = "Settings", - storages = { - @Storage(value = "analytics.settings.xml", roamingType = RoamingType.DISABLED) -}) -public final class Settings implements ICookie, PersistentStateComponent { - - // str representation of ICookie.Name values are key. - private Map settings = new HashMap(); - - @Override - public Settings getState() { - return this; - } - - @Override - public void loadState(Settings state) { - XmlSerializerUtil.copyBean(state, this); - } - - @Override - public void setValue(ICookie.Name name, String value) { - this.settings.put(name.name(), value); - } - - @Override - public String getValue(ICookie.Name name) { - return this.settings.getOrDefault(name.name(), ""); - } - - @MapAnnotation - public Map getSettings() { - return settings; - } - - public void setSettings(Map settings) { - this.settings = settings; - } -} diff --git a/src/main/java/org/jboss/tools/intellij/componentanalysis/CAAnnotator.java b/src/main/java/org/jboss/tools/intellij/componentanalysis/CAAnnotator.java new file mode 100644 index 0000000..e6ffff2 --- /dev/null +++ b/src/main/java/org/jboss/tools/intellij/componentanalysis/CAAnnotator.java @@ -0,0 +1,226 @@ +/******************************************************************************* + * Copyright (c) 2023 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 http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ + +package org.jboss.tools.intellij.componentanalysis; + +import com.github.packageurl.PackageURL; +import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; +import com.intellij.codeInsight.daemon.HighlightDisplayKey; +import com.intellij.codeInspection.InspectionProfile; +import com.intellij.codeInspection.InspectionProfileEntry; +import com.intellij.lang.annotation.AnnotationBuilder; +import com.intellij.lang.annotation.AnnotationHolder; +import com.intellij.lang.annotation.ExternalAnnotator; +import com.intellij.lang.annotation.HighlightSeverity; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.profile.codeInspection.InspectionProjectProfileManager; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.serviceContainer.AlreadyDisposedException; +import com.redhat.exhort.api.DependencyReport; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +public abstract class CAAnnotator extends ExternalAnnotator> { + + private static final Logger LOG = Logger.getInstance(CAAnnotator.class); + + @Override + public @Nullable Info collectInformation(@NotNull PsiFile file) { + final InspectionProfileEntry inspection = this.getInspection(file, this.getInspectionShortName()); + if (inspection == null) { + return null; + } + return new Info(file, this.getDependencies(file)); + } + + @Override + public @Nullable Map doAnnotate(Info info) { + if (info != null && info.getFile() != null + && info.getDependencies() != null && !info.getDependencies().isEmpty()) { + String path = info.getFile().getVirtualFile().getPath(); + Set dependencies = info.getDependencies().keySet(); + + if (CAService.dependenciesModified(path, dependencies)) { + Project project = info.getFile().getProject(); + ApplicationManager.getApplication().executeOnPooledThread(() -> { + boolean updated = CAService.performAnalysis( + getPackageManager(info.getFile().getName()), + info.getFile().getVirtualFile().getName(), + info.getFile().getVirtualFile().getPath(), + dependencies); + + ApplicationManager.getApplication().runReadAction(() -> { + if (updated) { + try { + DaemonCodeAnalyzer.getInstance(project).restart(); + } catch (AlreadyDisposedException ex) { + LOG.warn("DaemonCodeAnalyzer disposed, invalidate cache: " + path, ex); + CAService.deleteReports(path); + } + } + }); + }); + } + + Map reports = CAService.getReports(path); + return this.matchDependencies(info.getDependencies(), reports); + } + + return null; + } + + @Override + public void apply(@NotNull PsiFile file, Map annotationResult, @NotNull AnnotationHolder holder) { + annotationResult.forEach((key, value) -> { + if (value != null) { + DependencyReport report = value.getReport(); + List elements = value.getElements(); + if (report.getIssues() != null && !report.getIssues().isEmpty() + && elements != null && !elements.isEmpty()) { + if (report.getRef() != null) { + String d = getDependencyString(report.getRef().purl()); + int num = report.getIssues().size(); + String m = d + ", " + "Known security vulnerabilities: " + num + ", "; + String t = "" + + "

" + d + "

" + + "

Known security vulnerabilities: " + num + "

"; + + if (report.getHighestVulnerability() != null && report.getHighestVulnerability().getSeverity() != null) { + String severity = report.getHighestVulnerability().getSeverity().getValue(); + m += "Highest severity: " + severity + ", "; + t += "

Highest severity: " + severity + "

"; + } + + m += "Dependency Analytics Plugin [Powered by Snyk]"; + t += "

Dependency Analytics Plugin [Powered by Snyk]

" + + ""; + + String message = m; + String tooltip = t; + + elements.forEach(e -> { + if (e != null) { + AnnotationBuilder builder = holder + .newAnnotation(HighlightSeverity.ERROR, message) + .tooltip(tooltip) + .range(e) + .withFix(new CAIntentionAction()); + builder.create(); + } + }); + } + } + } + }); + } + + abstract protected String getInspectionShortName(); + + abstract protected Map> getDependencies(PsiFile file); + + private Map matchDependencies(Map> dependencies, + Map reports) { + if (dependencies != null && !dependencies.isEmpty() + && reports != null && !reports.isEmpty()) { + return dependencies.entrySet() + .parallelStream() + .filter(e -> reports.containsKey(e.getKey())) + .collect(Collectors.toMap( + Map.Entry::getKey, + e -> new Result(dependencies.get(e.getKey()), reports.get(e.getKey())), + (o1, o2) -> o1)); + } + return null; + } + + private String getDependencyString(PackageURL purl) { + String namespace = purl.getNamespace(); + String s; + if (namespace != null) { + s = namespace + ":" + purl.getName(); + } else { + s = purl.getName(); + } + String version = purl.getVersion(); + if (version != null) { + s += "@" + version; + } + return s; + } + + private InspectionProfileEntry getInspection(@NotNull PsiElement context, @NotNull String inspectionShortName) { + final HighlightDisplayKey key = HighlightDisplayKey.find(inspectionShortName); + if (key == null) { + return null; + } + + final InspectionProfile profile = InspectionProjectProfileManager.getInstance(context.getProject()).getCurrentProfile(); + if (!profile.isToolEnabled(key, context)) { + return null; + } + return profile.getUnwrappedTool(inspectionShortName, context); + } + + private String getPackageManager(String file) { + switch (file) { + case "pom.xml": + return "maven"; + case "package.json": + return "npm"; + default: + return null; + } + } + + public static class Info { + PsiFile file; + Map> dependencies; + + public Info(PsiFile file, Map> dependencies) { + this.file = file; + this.dependencies = dependencies; + } + + public PsiFile getFile() { + return file; + } + + public Map> getDependencies() { + return dependencies; + } + } + + public static class Result { + List elements; + DependencyReport report; + + public Result(List elements, DependencyReport report) { + this.elements = elements; + this.report = report; + } + + public List getElements() { + return elements; + } + + public DependencyReport getReport() { + return report; + } + } +} diff --git a/src/main/java/org/jboss/tools/intellij/componentanalysis/CAIntentionAction.java b/src/main/java/org/jboss/tools/intellij/componentanalysis/CAIntentionAction.java new file mode 100644 index 0000000..87cab55 --- /dev/null +++ b/src/main/java/org/jboss/tools/intellij/componentanalysis/CAIntentionAction.java @@ -0,0 +1,69 @@ +/******************************************************************************* + * Copyright (c) 2023 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 http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ + +package org.jboss.tools.intellij.componentanalysis; + +import com.google.gson.JsonObject; +import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.codeInspection.util.IntentionFamilyName; +import com.intellij.codeInspection.util.IntentionName; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiFile; +import com.intellij.util.IncorrectOperationException; +import org.jboss.tools.intellij.stackanalysis.SaUtils; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; + +public class CAIntentionAction implements IntentionAction { + @Override + public @IntentionName @NotNull String getText() { + return "Detailed Vulnerability Report"; + } + + @Override + public @NotNull @IntentionFamilyName String getFamilyName() { + return "Maven"; + } + + @Override + public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { + if (file == null) { + return false; + } + return "pom.xml".equals(file.getName()) || "package.json".equals(file.getName()); + } + + @Override + public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + SaUtils saUtils = new SaUtils(); + VirtualFile vf = file.getVirtualFile(); + + if (vf != null) { + JsonObject manifestDetails = saUtils.performSA(vf); + if (manifestDetails != null) { + try { + saUtils.openCustomEditor(FileEditorManager.getInstance(project), manifestDetails); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + } + } + + @Override + public boolean startInWriteAction() { + return false; + } +} diff --git a/src/main/java/org/jboss/tools/intellij/componentanalysis/CAService.java b/src/main/java/org/jboss/tools/intellij/componentanalysis/CAService.java new file mode 100644 index 0000000..8ffc7c2 --- /dev/null +++ b/src/main/java/org/jboss/tools/intellij/componentanalysis/CAService.java @@ -0,0 +1,113 @@ +/******************************************************************************* + * Copyright (c) 2023 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 http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ + +package org.jboss.tools.intellij.componentanalysis; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.intellij.openapi.components.Service; +import com.intellij.openapi.components.ServiceManager; +import com.redhat.exhort.api.AnalysisReport; +import com.redhat.exhort.api.DependencyReport; +import org.jboss.tools.intellij.exhort.ApiService; + +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Service(Service.Level.PROJECT) +public final class CAService { + + public static CAService getInstance() { + return ServiceManager.getService(CAService.class); + } + + private final Cache> vulnerabilityCache = Caffeine.newBuilder() + .maximumSize(100) + .build(); + + private final Cache> dependencyCache = Caffeine.newBuilder() + .expireAfterWrite(25, TimeUnit.SECONDS) + .maximumSize(100) + .build(); + + public static Map getReports(String filePath) { + return Collections.unmodifiableMap(getInstance().vulnerabilityCache.get(filePath, p -> Collections.emptyMap())); + } + + public static void deleteReports(String filePath) { + getInstance().vulnerabilityCache.invalidate(filePath); + } + + public static boolean dependenciesModified(String filePath, Set dependencies) { + return !dependencies.equals(getInstance().dependencyCache.get(filePath, p -> Collections.emptySet())); + } + + public static boolean performAnalysis(String packageManager, + String fileName, + String filePath, + Set dependencies) { + if (dependenciesModified(filePath, dependencies)) { + ApiService apiService = ServiceManager.getService(ApiService.class); + AnalysisReport report = apiService.getComponentAnalysis(packageManager, fileName, filePath); + if (report == null) { + throw new RuntimeException("Failed to perform component analysis, result is invalid."); + } + if (report.getDependencies() != null) { + // Avoid comparing the version of dependency + Map dependencyMap = Collections.unmodifiableMap( + dependencies + .parallelStream() + .collect(Collectors.toMap( + Function.identity(), + d -> new Dependency(d, false), + (o1, o2) -> o1 + )) + ); + + Map reportMap = Collections.unmodifiableMap( + report.getDependencies() + .parallelStream() + .filter(r -> Objects.nonNull(r.getRef())) + .collect(Collectors.toMap( + r -> new Dependency(r.getRef().purl(), false), + Function.identity(), + (o1, o2) -> o1 + )) + ); + + Map resultMap = Collections.unmodifiableMap( + dependencyMap.entrySet() + .parallelStream() + .filter(e -> reportMap.containsKey(e.getValue())) + .map(e -> { + DependencyReport dp = reportMap.get(e.getValue()); + return new AbstractMap.SimpleEntry<>(e.getKey(), dp); + }) + .collect(Collectors.toMap( + AbstractMap.SimpleEntry::getKey, + AbstractMap.SimpleEntry::getValue, + (o1, o2) -> o1 + )) + ); + + getInstance().vulnerabilityCache.put(filePath, resultMap); + } else { + getInstance().vulnerabilityCache.invalidate(filePath); + } + + getInstance().dependencyCache.put(filePath, dependencies); + return true; + } + return false; + } +} diff --git a/src/main/java/org/jboss/tools/intellij/componentanalysis/Dependency.java b/src/main/java/org/jboss/tools/intellij/componentanalysis/Dependency.java new file mode 100644 index 0000000..21bd615 --- /dev/null +++ b/src/main/java/org/jboss/tools/intellij/componentanalysis/Dependency.java @@ -0,0 +1,109 @@ +/******************************************************************************* + * Copyright (c) 2023 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 http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ + +package org.jboss.tools.intellij.componentanalysis; + +import com.github.packageurl.PackageURL; + +import java.util.Objects; + +public class Dependency { + + String type; + String namespace; + String name; + String version; + + public Dependency(String type, String namespace, String name, String version) { + this.type = type; + this.namespace = namespace; + this.name = name; + this.version = version; + } + + public Dependency(Dependency d, boolean version) { + this.type = d.type; + this.namespace = d.namespace; + this.name = d.name; + if (version) { + this.version = d.version; + } + } + + public Dependency(PackageURL purl) { + this(purl, true); + } + + public Dependency(PackageURL purl, boolean version) { + this.type = purl.getType(); + this.namespace = purl.getNamespace(); + this.name = purl.getName(); + if (version) { + this.version = purl.getVersion(); + } + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getNamespace() { + return namespace; + } + + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Dependency that = (Dependency) o; + return Objects.equals(type, that.type) && Objects.equals(namespace, that.namespace) + && Objects.equals(name, that.name) && Objects.equals(version, that.version); + } + + @Override + public int hashCode() { + return Objects.hash(type, namespace, name, version); + } + + @Override + public String toString() { + return "Dependency{" + + "type='" + type + '\'' + + ", namespace='" + namespace + '\'' + + ", name='" + name + '\'' + + ", version='" + version + '\'' + + '}'; + } +} diff --git a/src/main/java/org/jboss/tools/intellij/componentanalysis/maven/MavenCAAnnotator.java b/src/main/java/org/jboss/tools/intellij/componentanalysis/maven/MavenCAAnnotator.java new file mode 100644 index 0000000..fa112f9 --- /dev/null +++ b/src/main/java/org/jboss/tools/intellij/componentanalysis/maven/MavenCAAnnotator.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright (c) 2023 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 http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ + +package org.jboss.tools.intellij.componentanalysis.maven; + +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.xml.XmlComment; +import com.intellij.psi.xml.XmlElement; +import org.jboss.tools.intellij.componentanalysis.CAAnnotator; +import org.jboss.tools.intellij.componentanalysis.Dependency; +import org.jetbrains.idea.maven.dom.MavenDomUtil; +import org.jetbrains.idea.maven.dom.model.MavenDomDependency; +import org.jetbrains.idea.maven.dom.model.MavenDomProjectModel; + +import java.util.*; +import java.util.stream.Collectors; + +public class MavenCAAnnotator extends CAAnnotator { + + @Override + protected String getInspectionShortName() { + return MavenCAInspection.SHORT_NAME; + } + + @Override + protected Map> getDependencies(PsiFile file) { + MavenDomProjectModel projectModel = MavenDomUtil.getMavenDomModel(file, MavenDomProjectModel.class); + if (projectModel != null) { + List dependencies = projectModel.getDependencies().getDependencies(); + dependencies = dependencies.stream() + .filter(d -> { + if ("test".equals(d.getScope().getStringValue())) { + return false; + } + XmlElement element = d.getXmlElement(); + if (element != null) { + return Arrays.stream(element.getChildren()) + .noneMatch(c -> c instanceof XmlComment + && "exhortignore".equals(((XmlComment) c).getCommentText().trim())); + } + return false; + }).collect(Collectors.toList()); + + Map> resultMap = new HashMap<>(); + dependencies.forEach(d -> { + Dependency dp = new Dependency( + "maven", + d.getGroupId().getStringValue(), + d.getArtifactId().getStringValue(), + d.getVersion().getStringValue()); + resultMap.computeIfAbsent(dp, k -> new LinkedList<>()).add(d.getXmlElement()); + }); + return resultMap; + } + return Collections.emptyMap(); + } +} diff --git a/src/main/java/org/jboss/tools/intellij/componentanalysis/maven/MavenCAInspection.java b/src/main/java/org/jboss/tools/intellij/componentanalysis/maven/MavenCAInspection.java new file mode 100644 index 0000000..8032f53 --- /dev/null +++ b/src/main/java/org/jboss/tools/intellij/componentanalysis/maven/MavenCAInspection.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2023 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 http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ + +package org.jboss.tools.intellij.componentanalysis.maven; + +import com.intellij.codeHighlighting.HighlightDisplayLevel; +import com.intellij.codeInspection.LocalInspectionTool; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.maven.dom.MavenDomBundle; + +public class MavenCAInspection extends LocalInspectionTool { + + @NonNls + public static final String SHORT_NAME = "MavenCAInspection"; + + @Override + @NotNull + public String getGroupDisplayName() { + return MavenDomBundle.message("inspection.group"); + } + + @Override + @NotNull + public String getShortName() { + return SHORT_NAME; + } + + @Override + @NotNull + public HighlightDisplayLevel getDefaultLevel() { + return HighlightDisplayLevel.ERROR; + } +} \ No newline at end of file diff --git a/src/main/java/org/jboss/tools/intellij/componentanalysis/maven/POMFileType.java b/src/main/java/org/jboss/tools/intellij/componentanalysis/maven/POMFileType.java new file mode 100644 index 0000000..b4a64fe --- /dev/null +++ b/src/main/java/org/jboss/tools/intellij/componentanalysis/maven/POMFileType.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2023 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 http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ + +package org.jboss.tools.intellij.componentanalysis.maven; + +import com.intellij.lang.xml.XMLLanguage; +import com.intellij.openapi.fileTypes.LanguageFileType; +import com.intellij.openapi.util.NlsContexts; +import com.intellij.openapi.util.NlsSafe; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.Icon; + +public class POMFileType extends LanguageFileType { + + POMFileType() { + super(XMLLanguage.INSTANCE, true); + } + + @Override + public @NonNls @NotNull String getName() { + return "pom"; + } + + @Override + public @NlsContexts.Label @NotNull String getDescription() { + return "Maven project object model"; + } + + @Override + public @NlsSafe @NotNull String getDefaultExtension() { + return "xml"; + } + + @Override + public @Nullable Icon getIcon() { + return null; + } +} + diff --git a/src/main/java/org/jboss/tools/intellij/componentanalysis/npm/NpmCAAnnotator.java b/src/main/java/org/jboss/tools/intellij/componentanalysis/npm/NpmCAAnnotator.java new file mode 100644 index 0000000..7f403e6 --- /dev/null +++ b/src/main/java/org/jboss/tools/intellij/componentanalysis/npm/NpmCAAnnotator.java @@ -0,0 +1,79 @@ +/******************************************************************************* + * Copyright (c) 2023 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 http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ + +package org.jboss.tools.intellij.componentanalysis.npm; + +import com.intellij.json.psi.*; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.util.PsiTreeUtil; +import org.jboss.tools.intellij.componentanalysis.CAAnnotator; +import org.jboss.tools.intellij.componentanalysis.Dependency; + +import java.util.*; +import java.util.stream.Collectors; + +public class NpmCAAnnotator extends CAAnnotator { + + @Override + protected String getInspectionShortName() { + return NpmCAInspection.SHORT_NAME; + } + + @Override + protected Map> getDependencies(PsiFile file) { + if ("package.json".equals(file.getName())) { + Set ignored = PsiTreeUtil.findChildrenOfType(file, JsonArray.class) + .stream() + .filter(c -> { + PsiElement p = c.getParent(); + if (p != null) { + return p instanceof JsonProperty && "exhortignore".equals(((JsonProperty) p).getName()); + } + return false; + }) + .flatMap(c -> Arrays.stream(c.getChildren())) + .filter(c -> c instanceof JsonStringLiteral) + .map(c -> ((JsonStringLiteral) c).getValue()) + .collect(Collectors.toSet()); + + Map> resultMap = new HashMap<>(); + PsiTreeUtil.findChildrenOfType(file, JsonObject.class) + .stream() + .filter(c -> { + PsiElement p = c.getParent(); + if (p != null) { + return p instanceof JsonProperty && "dependencies".equals(((JsonProperty) p).getName()); + } + return false; + }) + .flatMap(c -> Arrays.stream(c.getChildren())) + .filter(c -> c instanceof JsonProperty && !ignored.contains(((JsonProperty) c).getName())) + .forEach(c -> { + String name = ((JsonProperty) c).getName(); + String[] parts = name.split("/", 2); + String namespace = null; + if (parts.length == 2) { + namespace = parts[0]; + name = parts[1]; + } + JsonValue value = ((JsonProperty) c).getValue(); + String version = value instanceof JsonStringLiteral + ? ((JsonStringLiteral) value).getValue() + : null; + Dependency dp = new Dependency("npm", namespace, name, version); + resultMap.computeIfAbsent(dp, k -> new LinkedList<>()).add(c); + }); + return resultMap; + } + return Collections.emptyMap(); + } +} diff --git a/src/main/java/org/jboss/tools/intellij/componentanalysis/npm/NpmCAInspection.java b/src/main/java/org/jboss/tools/intellij/componentanalysis/npm/NpmCAInspection.java new file mode 100644 index 0000000..488a307 --- /dev/null +++ b/src/main/java/org/jboss/tools/intellij/componentanalysis/npm/NpmCAInspection.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2023 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 http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ + +package org.jboss.tools.intellij.componentanalysis.npm; + +import com.intellij.codeHighlighting.HighlightDisplayLevel; +import com.intellij.codeInspection.LocalInspectionTool; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +public class NpmCAInspection extends LocalInspectionTool { + + @NonNls + public static final String SHORT_NAME = "NpmCAInspection"; + + @Override + @NotNull + public String getGroupDisplayName() { + return "npm"; + } + + @Override + public @Nls(capitalization = Nls.Capitalization.Sentence) String @NotNull [] getGroupPath() { + return new String[]{"Javascript and Typescript"}; + } + + @Override + @NotNull + public String getShortName() { + return SHORT_NAME; + } + + @Override + @NotNull + public HighlightDisplayLevel getDefaultLevel() { + return HighlightDisplayLevel.ERROR; + } +} diff --git a/src/main/java/org/jboss/tools/intellij/componentanalysis/npm/PackageFileType.java b/src/main/java/org/jboss/tools/intellij/componentanalysis/npm/PackageFileType.java new file mode 100644 index 0000000..da6731e --- /dev/null +++ b/src/main/java/org/jboss/tools/intellij/componentanalysis/npm/PackageFileType.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2023 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 http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ + +package org.jboss.tools.intellij.componentanalysis.npm; + +import com.intellij.json.JsonLanguage; +import com.intellij.openapi.fileTypes.LanguageFileType; +import com.intellij.openapi.util.NlsContexts; +import com.intellij.openapi.util.NlsSafe; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; + +public class PackageFileType extends LanguageFileType { + protected PackageFileType() { + super(JsonLanguage.INSTANCE, true); + } + + @Override + public @NonNls @NotNull String getName() { + return "package"; + } + + @Override + public @NlsContexts.Label @NotNull String getDescription() { + return "Project manifest"; + } + + @Override + public @NlsSafe @NotNull String getDefaultExtension() { + return "json"; + } + + @Override + public @Nullable Icon getIcon() { + return null; + } +} diff --git a/src/main/java/org/jboss/tools/intellij/exhort/ApiService.java b/src/main/java/org/jboss/tools/intellij/exhort/ApiService.java index da79243..54ea9ed 100644 --- a/src/main/java/org/jboss/tools/intellij/exhort/ApiService.java +++ b/src/main/java/org/jboss/tools/intellij/exhort/ApiService.java @@ -1,60 +1,101 @@ +/******************************************************************************* + * Copyright (c) 2023 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 http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ + package org.jboss.tools.intellij.exhort; import com.intellij.openapi.components.Service; +import com.intellij.openapi.diagnostic.Logger; import com.redhat.exhort.Api; +import com.redhat.exhort.api.AnalysisReport; import com.redhat.exhort.impl.ExhortApi; -import com.redhat.exhort.tools.Ecosystem; -import org.jboss.tools.intellij.analytics.TelemetryService; +import org.jboss.tools.intellij.settings.ApiSettingsState; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutionException; @Service(Service.Level.PROJECT) public final class ApiService { - enum TelemetryKeys { - MANIFEST, ECOSYSTEM, PLATFORM; - @Override - public String toString() { - return this.name().toLowerCase(); + private static final Logger LOG = Logger.getInstance(ApiService.class); + + enum TelemetryKeys { + MANIFEST, ECOSYSTEM, PLATFORM; + + @Override + public String toString() { + return this.name().toLowerCase(); + } } - } - private final Api exhortApi; + private final Api exhortApi; - public ApiService() { - this(new ExhortApi()); - } + public ApiService() { + this(new ExhortApi()); + } - ApiService(Api exhortApi) { - this.exhortApi = exhortApi; - } + ApiService(Api exhortApi) { + this.exhortApi = exhortApi; + } + + public Path getStackAnalysis(final String packageManager, final String manifestName, final String manifestPath) { + var telemetryMsg = TelemetryService.instance().action("stack-analysis"); + telemetryMsg.property(TelemetryKeys.ECOSYSTEM.toString(), packageManager); + telemetryMsg.property(TelemetryKeys.PLATFORM.toString(), System.getProperty("os.name")); + telemetryMsg.property(TelemetryKeys.MANIFEST.toString(), manifestName); - public Path getStackAnalysis( - final String packageManager, - final String manifestName, - final String manifestPath - ) throws RuntimeException { + try { + ApiSettingsState.getInstance().setApiOptions(); + var htmlContent = exhortApi.stackAnalysisHtml(manifestPath); + var tmpFile = Files.createTempFile("exhort_", ".html"); + Files.write(tmpFile, htmlContent.get()); - var telemetryMsg = TelemetryService.instance().action("stack-analysis"); - telemetryMsg.property(TelemetryKeys.ECOSYSTEM.toString(), packageManager); - telemetryMsg.property(TelemetryKeys.PLATFORM.toString(), System.getProperty("os.name")); - telemetryMsg.property(TelemetryKeys.MANIFEST.toString(), manifestName); + telemetryMsg.send(); + return tmpFile; - try { - var htmlContent = exhortApi.stackAnalysisHtml(manifestPath); - var tmpFile = Files.createTempFile("exhort_", ".html"); - Files.write(tmpFile, htmlContent.get()); + } catch (IOException | InterruptedException | ExecutionException exc) { + telemetryMsg.error(exc); + telemetryMsg.send(); + throw new RuntimeException(exc); + } + } - telemetryMsg.send(); - return tmpFile; + public AnalysisReport getComponentAnalysis(final String packageManager, final String manifestName, final String manifestPath) { + var telemetryMsg = TelemetryService.instance().action("component-analysis"); + telemetryMsg.property(TelemetryKeys.ECOSYSTEM.toString(), packageManager); + telemetryMsg.property(TelemetryKeys.PLATFORM.toString(), System.getProperty("os.name")); + telemetryMsg.property(TelemetryKeys.MANIFEST.toString(), manifestName); - } catch (IOException | InterruptedException | ExecutionException exc) { - telemetryMsg.error(exc); - telemetryMsg.send(); - throw new RuntimeException(exc); + try { + ApiSettingsState.getInstance().setApiOptions(); + CompletableFuture componentReport = exhortApi.componentAnalysis(manifestPath); + AnalysisReport report = componentReport.get(); + telemetryMsg.send(); + return report; + } catch (IOException | InterruptedException | ExecutionException ex) { + telemetryMsg.error(ex); + telemetryMsg.send(); + throw new RuntimeException(ex); + } catch (IllegalArgumentException ex) { + telemetryMsg.error(ex); + telemetryMsg.send(); + LOG.warn("Invalid manifest file submitted.", ex); + } catch (CompletionException ex) { + telemetryMsg.error(ex); + telemetryMsg.send(); + LOG.warn("Invalid vulnerability report returned.", ex); + } + return null; } - } } diff --git a/src/main/java/org/jboss/tools/intellij/analytics/TelemetryService.java b/src/main/java/org/jboss/tools/intellij/exhort/TelemetryService.java similarity index 95% rename from src/main/java/org/jboss/tools/intellij/analytics/TelemetryService.java rename to src/main/java/org/jboss/tools/intellij/exhort/TelemetryService.java index 079c782..ae635be 100644 --- a/src/main/java/org/jboss/tools/intellij/analytics/TelemetryService.java +++ b/src/main/java/org/jboss/tools/intellij/exhort/TelemetryService.java @@ -8,7 +8,7 @@ * Contributors: * Red Hat, Inc. - initial API and implementation ******************************************************************************/ -package org.jboss.tools.intellij.analytics; +package org.jboss.tools.intellij.exhort; import com.redhat.devtools.intellij.telemetry.core.service.TelemetryMessageBuilder; import com.redhat.devtools.intellij.telemetry.core.util.Lazy; diff --git a/src/main/java/org/jboss/tools/intellij/settings/ApiSettingsComponent.java b/src/main/java/org/jboss/tools/intellij/settings/ApiSettingsComponent.java new file mode 100644 index 0000000..5727bd5 --- /dev/null +++ b/src/main/java/org/jboss/tools/intellij/settings/ApiSettingsComponent.java @@ -0,0 +1,152 @@ +/******************************************************************************* + * Copyright (c) 2023 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 http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ + +package org.jboss.tools.intellij.settings; + +import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; +import com.intellij.openapi.ui.TextComponentAccessor; +import com.intellij.openapi.ui.TextFieldWithBrowseButton; +import com.intellij.ui.components.JBLabel; +import com.intellij.ui.components.JBTextField; +import com.intellij.util.ui.FormBuilder; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; + +public class ApiSettingsComponent { + + private final static String mvnPathLabel = "Maven > Executable: Path" + + "
Specifies absolute path of mvn executable."; + private final static String javaPathLabel = "Maven > JAVA_HOME: Path" + + "
Specifies absolute path of Java installation directory."; + private final static String npmPathLabel = "Npm > Executable: Path" + + "
Specifies absolute path of npm executable."; + private final static String nodePathLabel = "Node > Directory: Path" + + "
Specifies absolute path of the directory containing node executable."; + private final static String snykTokenLabel = "Red Hat Dependency Analytics: Exhort Snyk Token" + + "
Red Hat Dependency Analytics sever authentication token for Snky."; + + private final JPanel mainPanel; + + private final TextFieldWithBrowseButton mvnPathText; + private final TextFieldWithBrowseButton javaPathText; + private final TextFieldWithBrowseButton npmPathText; + private final TextFieldWithBrowseButton nodePathText; + private final JBTextField snykTokenText; + + public ApiSettingsComponent() { + mvnPathText = new TextFieldWithBrowseButton(); + mvnPathText.addBrowseFolderListener( + null, + null, + null, + FileChooserDescriptorFactory.createSingleFileDescriptor(), + TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT + ); + + javaPathText= new TextFieldWithBrowseButton(); + javaPathText.addBrowseFolderListener( + null, + null, + null, + FileChooserDescriptorFactory.createSingleFolderDescriptor(), + TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT + ); + + npmPathText = new TextFieldWithBrowseButton(); + npmPathText.addBrowseFolderListener( + null, + null, + null, + FileChooserDescriptorFactory.createSingleFileDescriptor(), + TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT + ); + + nodePathText = new TextFieldWithBrowseButton(); + nodePathText.addBrowseFolderListener( + null, + null, + null, + FileChooserDescriptorFactory.createSingleFolderDescriptor(), + TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT + ); + + snykTokenText = new JBTextField(); + + mainPanel = FormBuilder.createFormBuilder() + .addLabeledComponent(new JBLabel(mvnPathLabel), mvnPathText, 1, true) + .addVerticalGap(10) + .addLabeledComponent(new JBLabel(javaPathLabel), javaPathText, 1, true) + .addSeparator(10) + .addVerticalGap(10) + .addLabeledComponent(new JBLabel(npmPathLabel), npmPathText, 1, true) + .addVerticalGap(10) + .addLabeledComponent(new JBLabel(nodePathLabel), nodePathText, 1, true) + .addSeparator(10) + .addVerticalGap(10) + .addLabeledComponent(new JBLabel(snykTokenLabel), snykTokenText, 1, true) + .addComponentFillVertically(new JPanel(), 0) + .getPanel(); + } + + public JPanel getPanel() { + return mainPanel; + } + + public JComponent getPreferredFocusedComponent() { + return mvnPathText; + } + + @NotNull + public String getMvnPathText() { + return mvnPathText.getText(); + } + + public void setMvnPathText(@NotNull String text) { + mvnPathText.setText(text); + } + + @NotNull + public String getJavaPathText() { + return javaPathText.getText(); + } + + public void setJavaPathText(@NotNull String text) { + javaPathText.setText(text); + } + + @NotNull + public String getNpmPathText() { + return npmPathText.getText(); + } + + public void setNpmPathText(@NotNull String text) { + npmPathText.setText(text); + } + + @NotNull + public String getNodePathText() { + return nodePathText.getText(); + } + + public void setNodePathText(@NotNull String text) { + nodePathText.setText(text); + } + + @NotNull + public String getSnykTokenText() { + return snykTokenText.getText(); + } + + public void setSnykTokenText(@NotNull String text) { + snykTokenText.setText(text); + } +} diff --git a/src/main/java/org/jboss/tools/intellij/settings/ApiSettingsConfigurable.java b/src/main/java/org/jboss/tools/intellij/settings/ApiSettingsConfigurable.java new file mode 100644 index 0000000..a9fdadd --- /dev/null +++ b/src/main/java/org/jboss/tools/intellij/settings/ApiSettingsConfigurable.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (c) 2023 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 http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ + +package org.jboss.tools.intellij.settings; + +import com.intellij.openapi.util.NlsContexts; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; + +public class ApiSettingsConfigurable implements com.intellij.openapi.options.Configurable { + + private ApiSettingsComponent settingsComponent; + + @Override + public @NlsContexts.ConfigurableName String getDisplayName() { + return "Red Hat Dependency Analytics"; + } + + @Override + public @Nullable JComponent createComponent() { + settingsComponent = new ApiSettingsComponent(); + return settingsComponent.getPanel(); + } + + @Override + public JComponent getPreferredFocusedComponent() { + return settingsComponent.getPreferredFocusedComponent(); + } + + @Override + public boolean isModified() { + ApiSettingsState settings = ApiSettingsState.getInstance(); + boolean modified = !settingsComponent.getMvnPathText().equals(settings.mvnPath); + modified |= !settingsComponent.getJavaPathText().equals(settings.javaPath); + modified |= !settingsComponent.getNpmPathText().equals(settings.npmPath); + modified |= !settingsComponent.getNodePathText().equals(settings.nodePath); + modified |= !settingsComponent.getSnykTokenText().equals(settings.snykToken); + return modified; + } + + @Override + public void apply() { + ApiSettingsState settings = ApiSettingsState.getInstance(); + settings.mvnPath = settingsComponent.getMvnPathText(); + settings.javaPath = settingsComponent.getJavaPathText(); + settings.npmPath = settingsComponent.getNpmPathText(); + settings.nodePath = settingsComponent.getNodePathText(); + settings.snykToken = settingsComponent.getSnykTokenText(); + } + + @Override + public void reset() { + ApiSettingsState settings = ApiSettingsState.getInstance(); + settingsComponent.setMvnPathText(settings.mvnPath); + settingsComponent.setJavaPathText(settings.javaPath); + settingsComponent.setNpmPathText(settings.npmPath); + settingsComponent.setNodePathText(settings.nodePath); + settingsComponent.setSnykTokenText(settings.snykToken); + } + + @Override + public void disposeUIResources() { + settingsComponent = null; + } +} diff --git a/src/main/java/org/jboss/tools/intellij/settings/ApiSettingsState.java b/src/main/java/org/jboss/tools/intellij/settings/ApiSettingsState.java new file mode 100644 index 0000000..5b84e8c --- /dev/null +++ b/src/main/java/org/jboss/tools/intellij/settings/ApiSettingsState.java @@ -0,0 +1,77 @@ +/******************************************************************************* + * Copyright (c) 2023 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 http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ + +package org.jboss.tools.intellij.settings; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.components.*; +import com.intellij.util.xmlb.XmlSerializerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +@State( + name = "org.jboss.tools.intellij.settings.ApiSettingsState", + storages = @Storage( + value = "rhda.exhort.xml", + roamingType = RoamingType.DISABLED + ) +) +@Service(Service.Level.APP) +public final class ApiSettingsState implements PersistentStateComponent { + + public static ApiSettingsState getInstance() { + return ApplicationManager.getApplication().getService(ApiSettingsState.class); + } + + public String mvnPath; + public String javaPath; + public String npmPath; + public String nodePath; + public String snykToken; + + @Override + public @Nullable ApiSettingsState getState() { + return this; + } + + @Override + public void loadState(@NotNull ApiSettingsState state) { + XmlSerializerUtil.copyBean(state, this); + } + + public void setApiOptions() { + if (mvnPath != null && !mvnPath.isBlank()) { + System.setProperty("EXHORT_MVN_PATH", mvnPath); + } else { + System.clearProperty("EXHORT_MVN_PATH"); + } + if (javaPath != null && !javaPath.isBlank()) { + System.setProperty("JAVA_HOME", javaPath); + } else { + System.clearProperty("JAVA_HOME"); + } + if (npmPath != null && !npmPath.isBlank()) { + System.setProperty("EXHORT_NPM_PATH", npmPath); + } else { + System.clearProperty("EXHORT_NPM_PATH"); + } + if (nodePath != null && !nodePath.isBlank()) { + System.setProperty("NODE_HOME", nodePath); + } else { + System.clearProperty("NODE_HOME"); + } + if (snykToken != null && !snykToken.isBlank()) { + System.setProperty("EXHORT_SNYK_TOKEN", snykToken); + } else { + System.clearProperty("EXHORT_SNYK_TOKEN"); + } + } +} diff --git a/src/main/java/org/jboss/tools/intellij/stackanalysis/Cli.java b/src/main/java/org/jboss/tools/intellij/stackanalysis/Cli.java deleted file mode 100644 index bf83a05..0000000 --- a/src/main/java/org/jboss/tools/intellij/stackanalysis/Cli.java +++ /dev/null @@ -1,41 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2021 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 http://www.eclipse.org/legal/epl-v20.html - * - * Contributors: - * Red Hat, Inc. - initial API and implementation - ******************************************************************************/ -package org.jboss.tools.intellij.stackanalysis; - -import com.intellij.openapi.util.SystemInfo; -import org.jboss.tools.intellij.analytics.PlatformDetectionException; - -public class Cli { - public String cliBinaryName; - public String cliReleaseTag; - - // Set name of CLI binary and release tag - private static final Cli WINDOWS = new Cli("crda.exe", "v0.2.5"); - private static final Cli LINUX = new Cli("crda", "v0.2.5"); - private static final Cli MACOS = new Cli("crda", "v0.2.5"); - - private Cli(String cliBinaryName, String cliReleaseTag) { - this.cliBinaryName = cliBinaryName; - this.cliReleaseTag = cliReleaseTag; - } - - private static Cli detect() { - if (SystemInfo.isLinux) - return LINUX; - if (SystemInfo.isWindows) - return WINDOWS; - if (SystemInfo.isMac) - return MACOS; - throw new PlatformDetectionException(SystemInfo.OS_NAME + " is not supported"); - } - - public static final Cli current = detect(); -} diff --git a/src/main/java/org/jboss/tools/intellij/stackanalysis/PlatformDetectionException.java b/src/main/java/org/jboss/tools/intellij/stackanalysis/PlatformDetectionException.java new file mode 100644 index 0000000..bdbd732 --- /dev/null +++ b/src/main/java/org/jboss/tools/intellij/stackanalysis/PlatformDetectionException.java @@ -0,0 +1,18 @@ +/******************************************************************************* + * Copyright (c) 2021 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 http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ + +package org.jboss.tools.intellij.stackanalysis; + +public class PlatformDetectionException extends RuntimeException { + public PlatformDetectionException(String ex) { + super(ex); + } +} diff --git a/src/main/java/org/jboss/tools/intellij/stackanalysis/PreloadCli.java b/src/main/java/org/jboss/tools/intellij/stackanalysis/PreloadCli.java deleted file mode 100644 index 175c154..0000000 --- a/src/main/java/org/jboss/tools/intellij/stackanalysis/PreloadCli.java +++ /dev/null @@ -1,63 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2021 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 http://www.eclipse.org/legal/epl-v20.html - * - * Contributors: - * Red Hat, Inc. - initial API and implementation - ******************************************************************************/ -package org.jboss.tools.intellij.stackanalysis; - -import com.intellij.ide.AppLifecycleListener; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.components.ServiceManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.progress.ProcessCanceledException; -import org.jboss.tools.intellij.analytics.GitHubReleaseDownloader; -import org.jboss.tools.intellij.analytics.ICookie; -import org.jboss.tools.intellij.analytics.Platform; -import org.jboss.tools.intellij.analytics.Settings; -import org.jetbrains.annotations.NotNull; - -import java.io.IOException; -import java.util.List; - -public final class PreloadCli implements AppLifecycleListener { - private static final Logger logger = Logger.getInstance(PreloadCli.class); - private final ICookie cookies = ServiceManager.getService(Settings.class); - - @Override - public void appFrameCreated(@NotNull List commandLineArgs) { - logger.info("CLI preload is called"); - ApplicationManager.getApplication().executeOnPooledThread(() -> { - try { - // If Env variable is set then use binary file from value given - final String cliPath = System.getenv("CLI_FILE_PATH"); - - // If Env variable is not set download binary from GitHub Repo - if (cliPath == null) { - final GitHubReleaseDownloader bundle = new GitHubReleaseDownloader( - Platform.current.cliTarBallName, - cookies, - "fabric8-analytics/cli-tools", - true); - - // Download the CLI tarball - bundle.download(); - - // Extract tar file to get CLI Binary - new SaUtils().unTarBundle(Platform.current.cliTarBallName, Cli.current.cliBinaryName); - logger.info("CLI binary is ready for use."); - } - - // Authenticate user - new SaProcessExecutor().authenticateUser(); - } catch(IOException | InterruptedException e) { - logger.warn(e); - throw new ProcessCanceledException(e); - } - }); - } -} diff --git a/src/main/java/org/jboss/tools/intellij/stackanalysis/SaAction.java b/src/main/java/org/jboss/tools/intellij/stackanalysis/SaAction.java index 255644d..fbdd3dd 100644 --- a/src/main/java/org/jboss/tools/intellij/stackanalysis/SaAction.java +++ b/src/main/java/org/jboss/tools/intellij/stackanalysis/SaAction.java @@ -12,33 +12,37 @@ import com.google.gson.JsonObject; -import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.CommonDataKeys; -import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.actionSystem.PlatformDataKeys; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiFile; - -import org.jboss.tools.intellij.exhort.ApiService; -import org.jboss.tools.intellij.analytics.Platform; import org.jetbrains.annotations.NotNull; +import java.util.Arrays; +import java.util.List; + public class SaAction extends AnAction { private static final Logger logger = Logger.getInstance(SaAction.class); - private final ApiService apiService; + private static final List supportedManifestFiles = Arrays.asList( + "pom.xml", + "package.json" +// Disable support for go and python +// , "go.mod", "requirements.txt", "requirements-dev.txt" + ); public SaAction() { - apiService = ServiceManager.getService(ApiService.class); + } /** *

Intellij Plugin Action implementation for triggering SA.

- * + *

* Analysis will be performed on the file for which Action is triggered and Report will be shown in editor workspace. * * @param event An instance of AnActionEvent. @@ -49,29 +53,13 @@ public void actionPerformed(@NotNull AnActionEvent event) { SaUtils saUtils = new SaUtils(); VirtualFile manifestFile = event.getData(PlatformDataKeys.VIRTUAL_FILE); - // Get SA report for given manifest file. - String reportLink; - if ("pom.xml".equals(manifestFile.getName()) || "package.json".equals(manifestFile.getName()) ) { - reportLink = apiService.getStackAnalysis( - determinePackageManagerName(manifestFile.getName()), - manifestFile.getName(), - manifestFile.getPath() - ).toUri().toString(); - } else { - reportLink = saUtils.getReport(manifestFile.getPath()).get("report_link").getAsString(); + if (manifestFile != null) { + JsonObject manifestDetails = saUtils.performSA(manifestFile); + if (manifestDetails != null) { + // Open custom editor window which will load SA Report in browser attached to it. + saUtils.openCustomEditor(FileEditorManager.getInstance(event.getProject()), manifestDetails); + } } - - // Manifest file details to be saved in temp file which will be used while opening Report tab - JsonObject manifestDetails = new JsonObject(); - manifestDetails.addProperty("showParent", false); - manifestDetails.addProperty("manifestName", manifestFile.getName()); - manifestDetails.addProperty("manifestPath", manifestFile.getPath()); - manifestDetails.addProperty("manifestFileParent", manifestFile.getParent().getName()); - manifestDetails.addProperty("report_link", reportLink); - manifestDetails.addProperty("manifestNameWithoutExtension", manifestFile.getNameWithoutExtension()); - - // Open custom editor window which will load SA Report in browser attached to it. - saUtils.openCustomEditor(FileEditorManager.getInstance(event.getProject()), manifestDetails); } catch (Exception e) { logger.warn(e); Messages.showErrorDialog(event.getProject(), @@ -80,23 +68,6 @@ public void actionPerformed(@NotNull AnActionEvent event) { } } - private String determinePackageManagerName(String name) { - String packageManager; - switch(name) - { - case "pom.xml": - packageManager = "maven"; - break; - case "package.json": - packageManager = "npm"; - break; - default: - throw new IllegalArgumentException("package manager not implemented"); - } - return packageManager; - } - - /** *

Updates the state of the action, Action is show if this method returns true.

* @@ -109,7 +80,7 @@ public void update(AnActionEvent event) { // Check if file where context menu is opened is type of supported extension. // If yes then show the action for SA in menu if (psiFile != null) { - event.getPresentation().setEnabledAndVisible(Platform.supportedManifestFiles + event.getPresentation().setEnabledAndVisible(supportedManifestFiles .contains(psiFile.getName())); } else { event.getPresentation().setEnabledAndVisible(false); diff --git a/src/main/java/org/jboss/tools/intellij/stackanalysis/SaEditorTabTitleProvider.java b/src/main/java/org/jboss/tools/intellij/stackanalysis/SaEditorTabTitleProvider.java index 3c18997..606214f 100644 --- a/src/main/java/org/jboss/tools/intellij/stackanalysis/SaEditorTabTitleProvider.java +++ b/src/main/java/org/jboss/tools/intellij/stackanalysis/SaEditorTabTitleProvider.java @@ -21,7 +21,7 @@ import java.io.IOException; -public class SaEditorTabTitleProvider implements EditorTabTitleProvider{ +public class SaEditorTabTitleProvider implements EditorTabTitleProvider { @Override public @NotNull String getEditorTabTitle(@NotNull Project project, @NotNull VirtualFile file) { // Check if file opened in Editor is SA report, if Yes then change the title of Custom Editor Tab @@ -32,9 +32,9 @@ public class SaEditorTabTitleProvider implements EditorTabTitleProvider{ JsonObject manifestDetails = new Gson().fromJson(VfsUtilCore.loadText(file), JsonObject.class); // If a tab is already opened for same manifest type then add parent directory to distinguish between tabs if (manifestDetails.get("showParent").getAsBoolean()) { - tabName = "Dependency Analytics Report for "+manifestDetails.get("manifestFileParent").getAsString()+"/"+manifestDetails.get("manifestName").getAsString(); + tabName = "Dependency Analytics Report for " + manifestDetails.get("manifestFileParent").getAsString() + "/" + manifestDetails.get("manifestName").getAsString(); } else { - tabName = "Dependency Analytics Report for "+manifestDetails.get("manifestName").getAsString(); + tabName = "Dependency Analytics Report for " + manifestDetails.get("manifestName").getAsString(); } } catch (IOException e) { tabName = "Dependency Analytics Report"; diff --git a/src/main/java/org/jboss/tools/intellij/stackanalysis/SaProcessExecutor.java b/src/main/java/org/jboss/tools/intellij/stackanalysis/SaProcessExecutor.java deleted file mode 100644 index f639220..0000000 --- a/src/main/java/org/jboss/tools/intellij/stackanalysis/SaProcessExecutor.java +++ /dev/null @@ -1,118 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2021 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 http://www.eclipse.org/legal/epl-v20.html - * - * Contributors: - * Red Hat, Inc. - initial API and implementation - ******************************************************************************/ -package org.jboss.tools.intellij.stackanalysis; - -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.util.SystemInfo; -import org.jboss.tools.intellij.analytics.Platform; -import org.jboss.tools.intellij.analytics.PlatformDetectionException; - -import java.io.BufferedReader; -import java.io.File; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.UUID; - - -public class SaProcessExecutor { - private static final Logger logger = Logger.getInstance(SaProcessExecutor.class); - private static final String CLI_CONFIG_FILE_PATH = Paths.get(System.getProperty("user.home"), - ".crda", "config.yaml").toString(); - private static final String CLI_COMMAND = Platform.pluginDirectory + File.separator + "crda"; - - - /** - *

Authenticate a CRDA CLI user.

- * - * If CLI config file is not present in host machine then user is considered to be new - * and needs to be authenticated in CRDA Platform. - * - * @throws IOException In case of process failure - * @throws InterruptedException In case of process failure - */ - public void authenticateUser() throws IOException, InterruptedException { - // Check if crda config file is already present in system. if not create CLI config file. - if(!Files.exists(Paths.get(CLI_CONFIG_FILE_PATH))) { - logger.info("Authenticating user."); - - // Run CLI command to set user consent to False for CLI telemetry data collection. - execute(new String[]{CLI_COMMAND, "config", "set", "consent_telemetry", "false"}); - - // Run command to authenticate user in CRDA Platform. - execute(new String[]{CLI_COMMAND, "config", "set", "crda_key", UUID.randomUUID().toString()}); - } - } - - - /** - *

Perform Stack Analysis on given file.

- * - * @param filePath Path to target manifest file. - * - * @return String object having analysis report. - * - * @throws IOException In case of process failure - * @throws InterruptedException In case of process failure - */ - public String performStackAnalysis(String filePath) throws IOException, InterruptedException { - logger.info("Starting Stack Analysis."); - // Authenticate user, in case file has been deleted after loading the plugin - authenticateUser(); - - // Execute CLI command for analysis - return execute(new String[]{CLI_COMMAND, "analyse", filePath, "-j", "-m", "intellij"}); - } - - - /** - *

Execute CLI commands.

- * - * @param arguments Arguments for command to be executed. - * - * @return String object having result of command. - * - * @throws IOException In case of process failure - * @throws InterruptedException In case of process failure - */ - public String execute(String[] arguments) throws IOException, InterruptedException { - // Logic to execute given CLI command and get the result. - ProcessBuilder processBuilder = new ProcessBuilder(arguments); - - // Set CLI binary location as working directory for process. - processBuilder.directory(new File(Platform.pluginDirectory)); - processBuilder.redirectErrorStream(true); - - Process process = processBuilder.start(); - StringBuilder output = new StringBuilder(); - - try (InputStreamReader inputStreamReader = new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8); - BufferedReader bufferedReader = new BufferedReader(inputStreamReader)){ - - String line; - while ((line = bufferedReader.readLine()) != null) { - output.append(line).append("\n"); - } - - int exitVal = process.waitFor(); - - // Return data according to exit code of command. - if (exitVal == 0 || exitVal == 2) { - return output.toString(); - } else { - logger.info("Process execution failed for = "+processBuilder.command()); - throw new PlatformDetectionException(output.toString()); - } - } - } -} diff --git a/src/main/java/org/jboss/tools/intellij/stackanalysis/SaReport.java b/src/main/java/org/jboss/tools/intellij/stackanalysis/SaReport.java index a0ad6c2..34e3b64 100644 --- a/src/main/java/org/jboss/tools/intellij/stackanalysis/SaReport.java +++ b/src/main/java/org/jboss/tools/intellij/stackanalysis/SaReport.java @@ -11,6 +11,7 @@ package org.jboss.tools.intellij.stackanalysis; import com.intellij.ui.jcef.JBCefBrowser; + import javax.swing.JComponent; diff --git a/src/main/java/org/jboss/tools/intellij/stackanalysis/SaReportEditor.java b/src/main/java/org/jboss/tools/intellij/stackanalysis/SaReportEditor.java index 7c27f68..3184044 100644 --- a/src/main/java/org/jboss/tools/intellij/stackanalysis/SaReportEditor.java +++ b/src/main/java/org/jboss/tools/intellij/stackanalysis/SaReportEditor.java @@ -54,10 +54,13 @@ public SaReportEditor(VirtualFile virtualFile) throws IOException { } @Override - public @NotNull String getName() { return ""; } + public @NotNull String getName() { + return ""; + } @Override - public void setState(@NotNull FileEditorState state) { } + public void setState(@NotNull FileEditorState state) { + } @Override public boolean isModified() { @@ -70,16 +73,20 @@ public boolean isValid() { } @Override - public void selectNotify() {} + public void selectNotify() { + } @Override - public void deselectNotify() {} + public void deselectNotify() { + } @Override - public void addPropertyChangeListener(@NotNull PropertyChangeListener listener) {} + public void addPropertyChangeListener(@NotNull PropertyChangeListener listener) { + } @Override - public void removePropertyChangeListener(@NotNull PropertyChangeListener listener) {} + public void removePropertyChangeListener(@NotNull PropertyChangeListener listener) { + } @Nullable @Override @@ -101,5 +108,7 @@ public void dispose() { @NotNull @Override - public VirtualFile getFile() { return this.virtualFile; } + public VirtualFile getFile() { + return this.virtualFile; + } } diff --git a/src/main/java/org/jboss/tools/intellij/stackanalysis/SaReportEditorProvider.java b/src/main/java/org/jboss/tools/intellij/stackanalysis/SaReportEditorProvider.java index 1702ea1..49a46df 100644 --- a/src/main/java/org/jboss/tools/intellij/stackanalysis/SaReportEditorProvider.java +++ b/src/main/java/org/jboss/tools/intellij/stackanalysis/SaReportEditorProvider.java @@ -17,7 +17,6 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; -import org.jboss.tools.intellij.analytics.PlatformDetectionException; import org.jetbrains.annotations.NotNull; diff --git a/src/main/java/org/jboss/tools/intellij/stackanalysis/SaUtils.java b/src/main/java/org/jboss/tools/intellij/stackanalysis/SaUtils.java index 2798849..21d56aa 100644 --- a/src/main/java/org/jboss/tools/intellij/stackanalysis/SaUtils.java +++ b/src/main/java/org/jboss/tools/intellij/stackanalysis/SaUtils.java @@ -12,15 +12,12 @@ import com.google.gson.Gson; import com.google.gson.JsonObject; +import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; -import org.apache.commons.compress.archivers.tar.TarArchiveEntry; -import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; -import org.apache.commons.compress.utils.IOUtils; -import org.jboss.tools.intellij.analytics.Platform; -import org.jboss.tools.intellij.analytics.PlatformDetectionException; +import org.jboss.tools.intellij.exhort.ApiService; import java.io.BufferedWriter; import java.io.FileOutputStream; @@ -28,36 +25,16 @@ import java.io.OutputStreamWriter; import java.io.File; import java.io.Writer; -import java.io.FileInputStream; -import java.io.BufferedInputStream; -import java.util.zip.GZIPInputStream; public class SaUtils { - /** - *

Get Stack Analysis Report for given manifest file using CLI.

- * - * @param filePath Path to target manifest file - * - * @return A JSONObject having SA Report. - * - * @throws IOException In case of process failure - * @throws InterruptedException In case of process failure - */ - public JsonObject getReport(String filePath) throws IOException, InterruptedException { - // Get the SA report using CLI and return JSON data - return new Gson().fromJson(new SaProcessExecutor().performStackAnalysis(filePath), JsonObject.class); - } - - /** *

Open a custom editor window.

* *

The custom editor window will open a file which will have browser attached to it.

* - * @param instance An instance of FileEditorManager. + * @param instance An instance of FileEditorManager. * @param manifestDetails Manifest file details. - * * @throws IOException In case of process failure */ public void openCustomEditor(FileEditorManager instance, JsonObject manifestDetails) throws IOException { @@ -67,7 +44,7 @@ public void openCustomEditor(FileEditorManager instance, JsonObject manifestDeta manifestDetails = closeCustomEditor(instance, manifestDetails); // Create a temp file in which is registered with SaReportEditorProvider. - File reportFile = File.createTempFile("CRDA-", "_"+manifestDetails.get("manifestName").getAsString()+".sa"); + File reportFile = File.createTempFile("exhort-", "_" + manifestDetails.get("manifestName").getAsString() + ".sa"); //Save the SA Report URL in file, which will be loaded in browser try (Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(reportFile)))) { @@ -80,7 +57,7 @@ public void openCustomEditor(FileEditorManager instance, JsonObject manifestDeta // Refresh the cached file from the physical file system. // if Virtual file is already opened in editor window from previous run then refresh file from physical file // else old web page will be shown in editor window. - if (virtualFile == null){ + if (virtualFile == null) { throw new PlatformDetectionException("Dependency Analytics Report file is not created."); } virtualFile.refresh(false, true); @@ -102,20 +79,20 @@ private JsonObject closeCustomEditor(FileEditorManager instance, JsonObject mani VirtualFile[] openFiles = instance.getOpenFiles(); // iterate through all files and if Report file is open then close it - for (VirtualFile openFile : openFiles){ + for (VirtualFile openFile : openFiles) { // Check if opened file extension is sa, and existing tab manifest file type is same as new (pom.xml, go.mod) // if not then no need to close any existing tab, just create new tab. if (openFile.getExtension().equals("sa") && openFile.getNameWithoutExtension() .replaceAll("^.*?_", "") - .equals(manifestDetails.get("manifestName").getAsString())){ + .equals(manifestDetails.get("manifestName").getAsString())) { // If existing tab manifest type is same as new (pom.xml == pom.xml), then check if existing tab ss for same file by comparing the paths String existingFilePath = new Gson().fromJson(VfsUtilCore.loadText(openFile), JsonObject.class).get("manifestPath").getAsString(); String currentFilePath = manifestDetails.get("manifestPath").getAsString(); // If file path is same then close existing tab - if (currentFilePath.equals(existingFilePath)){ + if (currentFilePath.equals(existingFilePath)) { // Close the Report file in workspace, // dispose method of SaReportEditor will delete file from filesystem as well. instance.closeFile(openFile); @@ -135,37 +112,43 @@ private JsonObject closeCustomEditor(FileEditorManager instance, JsonObject mani return manifestDetails; } + public JsonObject performSA(VirtualFile manifestFile) { + // Get SA report for given manifest file. + String reportLink; + if ("pom.xml".equals(manifestFile.getName()) || "package.json".equals(manifestFile.getName())) { + ApiService apiService = ServiceManager.getService(ApiService.class); + reportLink = apiService.getStackAnalysis( + determinePackageManagerName(manifestFile.getName()), + manifestFile.getName(), + manifestFile.getPath() + ).toUri().toString(); + + // Manifest file details to be saved in temp file which will be used while opening Report tab + JsonObject manifestDetails = new JsonObject(); + manifestDetails.addProperty("showParent", false); + manifestDetails.addProperty("manifestName", manifestFile.getName()); + manifestDetails.addProperty("manifestPath", manifestFile.getPath()); + manifestDetails.addProperty("manifestFileParent", manifestFile.getParent().getName()); + manifestDetails.addProperty("report_link", reportLink); + manifestDetails.addProperty("manifestNameWithoutExtension", manifestFile.getNameWithoutExtension()); + + return manifestDetails; + } + return null; + } - /** - *

Extract given tar.gz file.

- * - * @param cliTarBallName Tar file to be extracted. - * @param cliBinaryName File which need to be extracted from tar - * - * @throws IOException In case of process failure - */ - public void unTarBundle(final String cliTarBallName, final String cliBinaryName) throws IOException { - // Logic to extract downloaded file into a directory - - // Get plugin directory to store extracted data - String sandBox = Platform.pluginDirectory; - - // CLI Binary file - final File cliBinaryDest = new File(sandBox, cliBinaryName); - - try (FileInputStream fileInputStream = new FileInputStream(sandBox + File.separator + cliTarBallName); - GZIPInputStream gzipInputStream = new GZIPInputStream(new BufferedInputStream(fileInputStream)); - TarArchiveInputStream tarArchiveInputStream = new TarArchiveInputStream(gzipInputStream)) { - - TarArchiveEntry tarEntry; - while ((tarEntry = tarArchiveInputStream.getNextTarEntry()) != null) { - File outputFile = new File(sandBox + File.separator + tarEntry.getName()); - outputFile.getParentFile().mkdirs(); - try (FileOutputStream fileOutputStream = new FileOutputStream(outputFile)) { - IOUtils.copy(tarArchiveInputStream, fileOutputStream); - } - } - cliBinaryDest.setExecutable(true); + private String determinePackageManagerName(String name) { + String packageManager; + switch (name) { + case "pom.xml": + packageManager = "maven"; + break; + case "package.json": + packageManager = "npm"; + break; + default: + throw new IllegalArgumentException("package manager not implemented"); } + return packageManager; } } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 229d43a..9a15b7d 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -1,10 +1,10 @@ - org.jboss.tools.intellij.analytics - Dependency Analytics - 1.0 - Red-Hat + org.jboss.tools.intellij.analytics + Dependency Analytics + 1.0 + Red-Hat -

Overview

@@ -86,7 +86,7 @@

]]>
- 0.6.0

Various dependency bumps.

Various maintenance resolutions.

@@ -117,65 +117,58 @@

0.0.1

Initial release

]]> -
- - - - - - com.intellij.modules.lang - com.redhat.devtools.intellij.telemetry - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + com.intellij.modules.lang + com.redhat.devtools.intellij.telemetry + org.jetbrains.idea.maven + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/main/resources/inspectionDescriptions/MavenCAInspection.html b/src/main/resources/inspectionDescriptions/MavenCAInspection.html new file mode 100644 index 0000000..83e9645 --- /dev/null +++ b/src/main/resources/inspectionDescriptions/MavenCAInspection.html @@ -0,0 +1,8 @@ + + +Reports dependencies vulnerabilities in pom.xml files. +

+ Powered by Snyk. +

+ + \ No newline at end of file diff --git a/src/main/resources/inspectionDescriptions/NpmCAInspection.html b/src/main/resources/inspectionDescriptions/NpmCAInspection.html new file mode 100644 index 0000000..80ed0ae --- /dev/null +++ b/src/main/resources/inspectionDescriptions/NpmCAInspection.html @@ -0,0 +1,8 @@ + + +Reports dependencies vulnerabilities in package.json files. +

+ Powered by Snyk. +

+ + \ No newline at end of file diff --git a/src/test/java/org/jboss/tools/intellij/analytics/GitHubCLIReleaseTest.java b/src/test/java/org/jboss/tools/intellij/analytics/GitHubCLIReleaseTest.java deleted file mode 100644 index adb44e8..0000000 --- a/src/test/java/org/jboss/tools/intellij/analytics/GitHubCLIReleaseTest.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.jboss.tools.intellij.analytics; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import java.io.IOException; - -import static org.junit.Assert.assertNotNull; - - -public class GitHubCLIReleaseTest { - - private GitHubRelease release; - - @Before - public void setupCli() throws IOException { - final GitHubRelease release = new GitHubRelease("fabric8-analytics/cli-tools"); - assertNotNull(release); - this.release = release; - } - - @After - public void tearDown() { - this.release = null; - } - - @Test - public void testCliDownloadUri() throws IOException { - final String uri = this.release.getDownloadUri("v0.2.4", "crda_0.2.4_Windows_64bit.tar.gz"); - assertNotNull(uri); - } - - @Test(expected = IOException.class) - public void testCliDownloadUriException() throws IOException { - final String uri = this.release.getDownloadUri("v0.2.4", "bad file name"); - assertNotNull(uri); - } -} diff --git a/src/test/java/org/jboss/tools/intellij/analytics/GitHubReleaseTest.java b/src/test/java/org/jboss/tools/intellij/analytics/GitHubReleaseTest.java deleted file mode 100644 index a50b969..0000000 --- a/src/test/java/org/jboss/tools/intellij/analytics/GitHubReleaseTest.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.jboss.tools.intellij.analytics; - -import java.io.IOException; - -import org.junit.Test; -import org.junit.Before; -import org.junit.After; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - - -public class GitHubReleaseTest { - - private GitHubRelease release; - - @Before - public void setup() throws IOException { - final GitHubRelease release = new GitHubRelease("fabric8-analytics/fabric8-analytics-lsp-server"); - assertNotNull(release); - this.release = release; - } - - @After - public void tearDown() { - this.release = null; - } - - @Test - public void testLatestDownloadUri() throws IOException { - final String latestReleaseTag = this.release.getLatestRelease(); - assertNotNull(latestReleaseTag); - - final String uri = this.release.getDownloadUri(latestReleaseTag, "analytics-lsp-win.exe"); - assertNotNull(uri); - } - - @Test(expected = IOException.class) - public void testLatestDownloadUriException() throws IOException { - final String latestReleaseTag = this.release.getLatestRelease(); - assertNotNull(latestReleaseTag); - - final String uri = this.release.getDownloadUri(latestReleaseTag, "bad tag name"); - assertNotNull(uri); - } -}