google-site-verification: google61fe8ba583a51912.html
LGTM.com Integration in Java: Complete Guide

Introduction to LGTM Integration

LGTM.com (Looks Good To Me) is a code analysis platform that helps identify security vulnerabilities, bugs, and code quality issues. This guide covers integrating LGTM into Java projects for continuous code analysis and quality monitoring.


System Architecture Overview

LGTM Integration Pipeline ├── Code Analysis Setup │ ├ - LGTM Project Configuration │ ├ - Analysis Configuration │ ├ - Query Suites │ └ - Custom Queries ├── CI/CD Integration │ ├ - GitHub Actions │ ├ - Jenkins Pipeline │ ├ - GitLab CI │ └ - Local Analysis ├── Results Processing │ ├ - Alert Management │ ├ - Result Filtering │ ├ - Report Generation │ └ - Trend Analysis └── Developer Workflow ├ - IDE Integration ├ - PR Checks ├ - Notifications └ - Fix Verification

Core Implementation

1. Maven Dependencies

<properties> <lgtm.maven.plugin.version>1.0</lgtm.maven.plugin.version> <github.api.version>1.1.0</github.api.version> <jackson.version>2.15.2</jackson.version> <okhttp.version>4.11.0</okhttp.version> </properties> <dependencies> <!-- LGTM API Client --> <dependency> <groupId>com.lgtm</groupId> <artifactId>lgtm-java-client</artifactId> <version>1.0.0</version> </dependency> <!-- GitHub API for PR integration --> <dependency> <groupId>org.kohsuke</groupId> <artifactId>github-api</artifactId> <version>${github.api.version}</version> </dependency> <!-- HTTP Client --> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>${okhttp.version}</version> </dependency> <!-- JSON Processing --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>${jackson.version}</version> </dependency> <!-- Maven Plugin API --> <dependency> <groupId>org.apache.maven</groupId> <artifactId>maven-plugin-api</artifactId> <version>3.8.1</version> </dependency> <!-- Code Analysis Tools --> <dependency> <groupId>org.sonarsource.sonarlint.core</groupId> <artifactId>sonarlint-core</artifactId> <version>8.0.0</version> </dependency> </dependencies> <build> <plugins> <!-- LGTM Maven Plugin --> <plugin> <groupId>com.lgtm</groupId> <artifactId>lgtm-maven-plugin</artifactId> <version>${lgtm.maven.plugin.version}</version> <executions> <execution> <goals> <goal>analyze</goal> </goals> </execution> </executions> <configuration> <outputDirectory>${project.build.directory}/lgtm</outputDirectory> <format>sarif</format> <failOnCritical>true</failOnCritical> </configuration> </plugin> </plugins> </build>

2. LGTM Configuration Files

lgtm.yml

# lgtm.yml - LGTM analysis configuration path_classifiers: docs: - "**/*.md" - "**/*.rst" - "**/*.txt" code: - "**/*.java" - "**/*.kt" # Kotlin files test: - "**/test/**" - "**/*Test.java" - "**/*Spec.java" extraction: java: index: # Java version for analysis jdk: 11 # Build configuration maven: # Maven goals to run for building the project goals: - "clean" - "compile" - "test-compile" # Exclude generated code and dependencies exclude: - "**/target/**" - "**/generated/**" - "**/node_modules/**" queries: # Include security queries - include: java/security # Include performance queries - include: java/performance # Include maintainability queries - include: java/maintainability # Exclude specific queries - exclude: java/security/audit/xss # Custom query packs - pack: custom-queries query_filters: - exclude: "**/test/**" - exclude: "**/*Test.java" alerters: # PR integration settings pull_requests: comments: true status: true # Only show new alerts in PRs new_alerts_only: true # Alert threshold threshold: warning

Custom Query Suite

# custom-queries.yml name: "custom-java-queries" version: "1.0.0" description: "Custom Java queries for our project" queries: - id: "custom/sql-injection" name: "Potential SQL Injection" description: "Detects potential SQL injection vulnerabilities" severity: error tags: [security, sql] query: | import java import semmle.code.java.dataflow.DataFlow import semmle.code.java.dataflow.TaintTracking from MethodAccess call, Expr source where call.getMethod().hasName("executeQuery") and TaintTracking::localTaint(DataFlow::parameterNode(source), DataFlow::exprNode(call.getArgument(0))) select call, "Potential SQL injection from $@", source, source.toString() - id: "custom/log-injection" name: "Log Injection" description: "Detects user input in log statements" severity: warning tags: [security, logging] query: | import java import semmle.code.java.dataflow.DataFlow from MethodAccess logCall, Expr userInput where logCall.getMethod().getName().matches("info|warn|error|debug") and logCall.getMethod().getDeclaringType().getASupertype*().hasQualifiedName("org.slf4j", "Logger") and DataFlow::localFlow(DataFlow::parameterNode(userInput), DataFlow::exprNode(logCall.getArgument(0))) select logCall, "User input used in log statement: $@", userInput, userInput.toString()

3. LGTM API Client

package com.example.lgtm.client; import com.fasterxml.jackson.databind.ObjectMapper; import okhttp3.*; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.io.IOException; import java.util.List; import java.util.concurrent.TimeUnit; @Component public class LGTMApiClient { private static final String BASE_URL = "https://lgtm.com/api/v1.0"; private final OkHttpClient client; private final ObjectMapper objectMapper; @Value("${lgtm.api.token:}") private String apiToken; public LGTMApiClient() { this.client = new OkHttpClient.Builder() .connectTimeout(30, TimeUnit.SECONDS) .readTimeout(60, TimeUnit.SECONDS) .writeTimeout(30, TimeUnit.SECONDS) .build(); this.objectMapper = new ObjectMapper(); } public AnalysisResult getProjectAnalysis(String projectId) throws IOException { Request request = new Request.Builder() .url(BASE_URL + "/projects/" + projectId + "/analysis") .addHeader("Authorization", "Bearer " + apiToken) .build(); try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful()) { throw new IOException("Unexpected code: " + response); } return objectMapper.readValue(response.body().string(), AnalysisResult.class); } } public List<Alert> getProjectAlerts(String projectId, String severity) throws IOException { HttpUrl.Builder urlBuilder = HttpUrl.parse(BASE_URL + "/projects/" + projectId + "/alerts").newBuilder(); if (severity != null) { urlBuilder.addQueryParameter("severity", severity); } Request request = new Request.Builder() .url(urlBuilder.build()) .addHeader("Authorization", "Bearer " + apiToken) .build(); try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful()) { throw new IOException("Unexpected code: " + response); } return objectMapper.readValue( response.body().string(), objectMapper.getTypeFactory().constructCollectionType(List.class, Alert.class) ); } } public void triggerAnalysis(String projectId) throws IOException { Request request = new Request.Builder() .url(BASE_URL + "/projects/" + projectId + "/analyses") .post(RequestBody.create("", MediaType.get("application/json"))) .addHeader("Authorization", "Bearer " + apiToken) .build(); try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful()) { throw new IOException("Failed to trigger analysis: " + response); } } } public AnalysisStatus getAnalysisStatus(String analysisId) throws IOException { Request request = new Request.Builder() .url(BASE_URL + "/analyses/" + analysisId) .addHeader("Authorization", "Bearer " + apiToken) .build(); try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful()) { throw new IOException("Unexpected code: " + response); } return objectMapper.readValue(response.body().string(), AnalysisStatus.class); } } // Data models public static class AnalysisResult { private String id; private String status; private String projectId; private int alertCount; private String commitId; private String createdAt; private String completedAt; // Getters and setters public String getId() { return id; } public void setId(String id) { this.id = id; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public String getProjectId() { return projectId; } public void setProjectId(String projectId) { this.projectId = projectId; } public int getAlertCount() { return alertCount; } public void setAlertCount(int alertCount) { this.alertCount = alertCount; } public String getCommitId() { return commitId; } public void setCommitId(String commitId) { this.commitId = commitId; } public String getCreatedAt() { return createdAt; } public void setCreatedAt(String createdAt) { this.createdAt = createdAt; } public String getCompletedAt() { return completedAt; } public void setCompletedAt(String completedAt) { this.completedAt = completedAt; } } public static class Alert { private String id; private String queryId; private String file; private int line; private String severity; private String message; private String description; private String category; private boolean fixed; // Getters and setters public String getId() { return id; } public void setId(String id) { this.id = id; } public String getQueryId() { return queryId; } public void setQueryId(String queryId) { this.queryId = queryId; } public String getFile() { return file; } public void setFile(String file) { this.file = file; } public int getLine() { return line; } public void setLine(int line) { this.line = line; } public String getSeverity() { return severity; } public void setSeverity(String severity) { this.severity = severity; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public String getCategory() { return category; } public void setCategory(String category) { this.category = category; } public boolean isFixed() { return fixed; } public void setFixed(boolean fixed) { this.fixed = fixed; } } public static class AnalysisStatus { private String id; private String status; private String projectId; private int progress; private String startedAt; private String finishedAt; // Getters and setters public String getId() { return id; } public void setId(String id) { this.id = id; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public String getProjectId() { return projectId; } public void setProjectId(String projectId) { this.projectId = projectId; } public int getProgress() { return progress; } public void setProgress(int progress) { this.progress = progress; } public String getStartedAt() { return startedAt; } public void setStartedAt(String startedAt) { this.startedAt = startedAt; } public String getFinishedAt() { return finishedAt; } public void setFinishedAt(String finishedAt) { this.finishedAt = finishedAt; } } }

4. LGTM Analysis Service

package com.example.lgtm.service; import com.example.lgtm.client.LGTMApiClient; import com.example.lgtm.client.LGTMApiClient.Alert; import com.example.lgtm.client.LGTMApiClient.AnalysisResult; import com.example.lgtm.client.LGTMApiClient.AnalysisStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.io.*; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @Service public class LGTMAnalysisService { private static final Logger logger = LoggerFactory.getLogger(LGTMAnalysisService.class); private final LGTMApiClient lgtmClient; private final GitHubService githubService; private final NotificationService notificationService; @Value("${lgtm.project.id:}") private String projectId; @Value("${lgtm.analysis.timeout:600}") private int analysisTimeoutSeconds; public LGTMAnalysisService(LGTMApiClient lgtmClient, GitHubService githubService, NotificationService notificationService) { this.lgtmClient = lgtmClient; this.githubService = githubService; this.notificationService = notificationService; } /** * Trigger LGTM analysis and wait for completion */ public AnalysisResult triggerAndWaitForAnalysis() throws Exception { logger.info("Triggering LGTM analysis for project: {}", projectId); // Trigger analysis lgtmClient.triggerAnalysis(projectId); // Wait for analysis to complete AnalysisResult result = waitForAnalysisCompletion(); logger.info("LGTM analysis completed with {} alerts", result.getAlertCount()); return result; } /** * Get current analysis results */ public AnalysisResult getCurrentAnalysis() throws Exception { return lgtmClient.getProjectAnalysis(projectId); } /** * Get alerts filtered by severity */ public List<Alert> getAlertsBySeverity(String severity) throws Exception { return lgtmClient.getProjectAlerts(projectId, severity); } /** * Get alerts grouped by category */ public Map<String, List<Alert>> getAlertsByCategory() throws Exception { List<Alert> allAlerts = lgtmClient.getProjectAlerts(projectId, null); return allAlerts.stream() .collect(Collectors.groupingBy(Alert::getCategory)); } /** * Get alert statistics */ public AlertStatistics getAlertStatistics() throws Exception { List<Alert> allAlerts = lgtmClient.getProjectAlerts(projectId, null); AlertStatistics stats = new AlertStatistics(); stats.setTotalAlerts(allAlerts.size()); stats.setCriticalAlerts((int) allAlerts.stream().filter(a -> "error".equals(a.getSeverity())).count()); stats.setWarningAlerts((int) allAlerts.stream().filter(a -> "warning".equals(a.getSeverity())).count()); stats.setFixedAlerts((int) allAlerts.stream().filter(Alert::isFixed).count()); // Group by category Map<String, Long> categoryCounts = allAlerts.stream() .collect(Collectors.groupingBy(Alert::getCategory, Collectors.counting())); stats.setAlertsByCategory(categoryCounts); // Group by file Map<String, Long> fileCounts = allAlerts.stream() .collect(Collectors.groupingBy(Alert::getFile, Collectors.counting())); stats.setAlertsByFile(fileCounts); return stats; } /** * Analyze pull request and post comments */ public PullRequestAnalysis analyzePullRequest(String repo, int pullRequestNumber) throws Exception { logger.info("Analyzing pull request {}/{}", repo, pullRequestNumber); PullRequestAnalysis prAnalysis = new PullRequestAnalysis(); prAnalysis.setRepository(repo); prAnalysis.setPullRequestNumber(pullRequestNumber); // Get PR details PullRequestDetails prDetails = githubService.getPullRequestDetails(repo, pullRequestNumber); prAnalysis.setBaseCommit(prDetails.getBaseCommit()); prAnalysis.setHeadCommit(prDetails.getHeadCommit()); // Trigger analysis for the PR lgtmClient.triggerAnalysis(projectId); AnalysisResult analysis = waitForAnalysisCompletion(); // Get new alerts (compared to base) List<Alert> newAlerts = getNewAlerts(prDetails.getBaseCommit(), prDetails.getHeadCommit()); prAnalysis.setNewAlerts(newAlerts); // Get fixed alerts List<Alert> fixedAlerts = getFixedAlerts(prDetails.getBaseCommit(), prDetails.getHeadCommit()); prAnalysis.setFixedAlerts(fixedAlerts); // Post PR comments if (!newAlerts.isEmpty()) { postPRComments(repo, pullRequestNumber, newAlerts); } // Update PR status updatePRStatus(repo, pullRequestNumber, newAlerts.isEmpty()); // Send notifications sendAnalysisNotifications(prAnalysis); return prAnalysis; } /** * Generate analysis report */ public AnalysisReport generateReport() throws Exception { AnalysisResult currentAnalysis = getCurrentAnalysis(); AlertStatistics statistics = getAlertStatistics(); List<Alert> criticalAlerts = getAlertsBySeverity("error"); List<Alert> warningAlerts = getAlertsBySeverity("warning"); AnalysisReport report = new AnalysisReport(); report.setGeneratedAt(new Date()); report.setProjectId(projectId); report.setAnalysisId(currentAnalysis.getId()); report.setTotalAlerts(currentAnalysis.getAlertCount()); report.setStatistics(statistics); report.setCriticalAlerts(criticalAlerts); report.setWarningAlerts(warningAlerts); report.setTrend(getAlertTrend()); return report; } /** * Export alerts to SARIF format */ public String exportToSarif() throws Exception { List<Alert> allAlerts = lgtmClient.getProjectAlerts(projectId, null); SarifReport sarif = new SarifReport(); sarif.setVersion("2.1.0"); List<SarifReport.Run> runs = new ArrayList<>(); SarifReport.Run run = new SarifReport.Run(); List<SarifReport.Result> results = allAlerts.stream() .map(this::convertAlertToSarifResult) .collect(Collectors.toList()); run.setResults(results); runs.add(run); sarif.setRuns(runs); // Convert to JSON ObjectMapper mapper = new ObjectMapper(); return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(sarif); } /** * Run local analysis using LGTM CLI */ public LocalAnalysisResult runLocalAnalysis(Path projectPath) throws Exception { logger.info("Running local LGTM analysis on: {}", projectPath); LocalAnalysisResult result = new LocalAnalysisResult(); result.setProjectPath(projectPath.toString()); result.setStartedAt(new Date()); // Check if LGTM CLI is available if (!isLGTMCliAvailable()) { throw new IllegalStateException("LGTM CLI is not available"); } // Run analysis ProcessBuilder pb = new ProcessBuilder("lgtm", "analyze", "--format", "sarif", projectPath.toString()); pb.directory(projectPath.toFile()); Process process = pb.start(); boolean completed = process.waitFor(analysisTimeoutSeconds, TimeUnit.SECONDS); if (!completed) { process.destroy(); throw new RuntimeException("LGTM analysis timed out"); } result.setCompletedAt(new Date()); result.setExitCode(process.exitValue()); // Read results String output = readProcessOutput(process); result.setRawOutput(output); if (process.exitValue() == 0) { result.setSuccess(true); // Parse SARIF output result.setSarifResults(parseSarifOutput(output)); } else { result.setSuccess(false); result.setError(output); } return result; } private AnalysisResult waitForAnalysisCompletion() throws Exception { long startTime = System.currentTimeMillis(); long timeout = analysisTimeoutSeconds * 1000L; while (System.currentTimeMillis() - startTime < timeout) { AnalysisResult result = lgtmClient.getProjectAnalysis(projectId); if ("completed".equals(result.getStatus())) { return result; } else if ("failed".equals(result.getStatus())) { throw new RuntimeException("LGTM analysis failed"); } // Wait before polling again Thread.sleep(10000); // 10 seconds } throw new RuntimeException("LGTM analysis timed out"); } private List<Alert> getNewAlerts(String baseCommit, String headCommit) throws Exception { // This would compare alerts between two commits // Implementation depends on LGTM API capabilities return new ArrayList<>(); } private List<Alert> getFixedAlerts(String baseCommit, String headCommit) throws Exception { // This would identify alerts that were fixed between commits return new ArrayList<>(); } private void postPRComments(String repo, int prNumber, List<Alert> alerts) throws Exception { StringBuilder comment = new StringBuilder(); comment.append("## LGTM Analysis Results\n\n"); comment.append("Found ").append(alerts.size()).append(" new alerts:\n\n"); for (Alert alert : alerts) { comment.append(String.format("### %s\n", alert.getSeverity().toUpperCase())); comment.append(String.format("**File:** %s (Line %d)\n", alert.getFile(), alert.getLine())); comment.append(String.format("**Message:** %s\n", alert.getMessage())); comment.append(String.format("**Category:** %s\n\n", alert.getCategory())); } githubService.createPRComment(repo, prNumber, comment.toString()); } private void updatePRStatus(String repo, int prNumber, boolean passed) throws Exception { String status = passed ? "success" : "failure"; String description = passed ? "No new alerts found" : "New alerts found"; githubService.updatePRStatus(repo, prNumber, "lgtm-analysis", status, description); } private void sendAnalysisNotifications(PullRequestAnalysis analysis) { if (!analysis.getNewAlerts().isEmpty()) { notificationService.sendAlertNotification(analysis); } } private AlertTrend getAlertTrend() throws Exception { // This would fetch historical data and calculate trends // Implementation would depend on data storage return new AlertTrend(); } private boolean isLGTMCliAvailable() throws Exception { Process process = Runtime.getRuntime().exec("lgtm --version"); return process.waitFor() == 0; } private String readProcessOutput(Process process) throws IOException { try (BufferedReader reader = new BufferedReader( new InputStreamReader(process.getInputStream()))) { return reader.lines().collect(Collectors.joining("\n")); } } private SarifReport.Result convertAlertToSarifResult(Alert alert) { SarifReport.Result result = new SarifReport.Result(); result.setRuleId(alert.getQueryId()); result.setMessage(alert.getMessage()); SarifReport.Location location = new SarifReport.Location(); location.setUri(alert.getFile()); location.setLine(alert.getLine()); result.setLocations(List.of(location)); result.setLevel(mapSeverityToSarifLevel(alert.getSeverity())); return result; } private String mapSeverityToSarifLevel(String severity) { switch (severity) { case "error": return "error"; case "warning": return "warning"; default: return "note"; } } private Object parseSarifOutput(String output) { try { ObjectMapper mapper = new ObjectMapper(); return mapper.readValue(output, Object.class); } catch (Exception e) { logger.warn("Failed to parse SARIF output", e); return null; } } // Data models public static class AlertStatistics { private int totalAlerts; private int criticalAlerts; private int warningAlerts; private int fixedAlerts; private Map<String, Long> alertsByCategory; private Map<String, Long> alertsByFile; // Getters and setters public int getTotalAlerts() { return totalAlerts; } public void setTotalAlerts(int totalAlerts) { this.totalAlerts = totalAlerts; } public int getCriticalAlerts() { return criticalAlerts; } public void setCriticalAlerts(int criticalAlerts) { this.criticalAlerts = criticalAlerts; } public int getWarningAlerts() { return warningAlerts; } public void setWarningAlerts(int warningAlerts) { this.warningAlerts = warningAlerts; } public int getFixedAlerts() { return fixedAlerts; } public void setFixedAlerts(int fixedAlerts) { this.fixedAlerts = fixedAlerts; } public Map<String, Long> getAlertsByCategory() { return alertsByCategory; } public void setAlertsByCategory(Map<String, Long> alertsByCategory) { this.alertsByCategory = alertsByCategory; } public Map<String, Long> getAlertsByFile() { return alertsByFile; } public void setAlertsByFile(Map<String, Long> alertsByFile) { this.alertsByFile = alertsByFile; } } public static class PullRequestAnalysis { private String repository; private int pullRequestNumber; private String baseCommit; private String headCommit; private List<Alert> newAlerts; private List<Alert> fixedAlerts; private Date analyzedAt; // Getters and setters public String getRepository() { return repository; } public void setRepository(String repository) { this.repository = repository; } public int getPullRequestNumber() { return pullRequestNumber; } public void setPullRequestNumber(int pullRequestNumber) { this.pullRequestNumber = pullRequestNumber; } public String getBaseCommit() { return baseCommit; } public void setBaseCommit(String baseCommit) { this.baseCommit = baseCommit; } public String getHeadCommit() { return headCommit; } public void setHeadCommit(String headCommit) { this.headCommit = headCommit; } public List<Alert> getNewAlerts() { return newAlerts; } public void setNewAlerts(List<Alert> newAlerts) { this.newAlerts = newAlerts; } public List<Alert> getFixedAlerts() { return fixedAlerts; } public void setFixedAlerts(List<Alert> fixedAlerts) { this.fixedAlerts = fixedAlerts; } public Date getAnalyzedAt() { return analyzedAt; } public void setAnalyzedAt(Date analyzedAt) { this.analyzedAt = analyzedAt; } } public static class AnalysisReport { private Date generatedAt; private String projectId; private String analysisId; private int totalAlerts; private AlertStatistics statistics; private List<Alert> criticalAlerts; private List<Alert> warningAlerts; private AlertTrend trend; // Getters and setters... } public static class AlertTrend { private Map<Date, Integer> dailyCounts; private double trendSlope; private boolean improving; // Getters and setters... } public static class LocalAnalysisResult { private String projectPath; private Date startedAt; private Date completedAt; private int exitCode; private boolean success; private String rawOutput; private String error; private Object sarifResults; // Getters and setters... } public static class SarifReport { private String version; private List<Run> runs; // Getters and setters... public static class Run { private List<Result> results; // Getters and setters... } public static class Result { private String ruleId; private String message; private List<Location> locations; private String level; // Getters and setters... } public static class Location { private String uri; private int line; // Getters and setters... } } }

5. GitHub Integration Service

package com.example.lgtm.integration; import org.kohsuke.github.*; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.io.IOException; import java.util.List; @Service public class GitHubService { @Value("${github.token:}") private String githubToken; private GitHub github; public GitHub connect() throws IOException { if (github == null) { github = new GitHubBuilder().withOAuthToken(githubToken).build(); } return github; } public PullRequestDetails getPullRequestDetails(String repo, int prNumber) throws IOException { GHRepository repository = connect().getRepository(repo); GHPullRequest pr = repository.getPullRequest(prNumber); PullRequestDetails details = new PullRequestDetails(); details.setNumber(prNumber); details.setTitle(pr.getTitle()); details.setBaseCommit(pr.getBase().getSha()); details.setHeadCommit(pr.getHead().getSha()); details.setAuthor(pr.getUser().getLogin()); return details; } public void createPRComment(String repo, int prNumber, String comment) throws IOException { GHRepository repository = connect().getRepository(repo); GHPullRequest pr = repository.getPullRequest(prNumber); pr.comment(comment); } public void updatePRStatus(String repo, int prNumber, String context, String state, String description) throws IOException { GHRepository repository = connect().getRepository(repo); GHPullRequest pr = repository.getPullRequest(prNumber); pr.getHead().createCommitStatus( GHCommitState.valueOf(state.toUpperCase()), null, description, context ); } public List<GHPullRequest> getOpenPullRequests(String repo) throws IOException { GHRepository repository = connect().getRepository(repo); return repository.getPullRequests(GHIssueState.OPEN); } public static class PullRequestDetails { private int number; private String title; private String baseCommit; private String headCommit; private String author; // Getters and setters public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getBaseCommit() { return baseCommit; } public void setBaseCommit(String baseCommit) { this.baseCommit = baseCommit; } public String getHeadCommit() { return headCommit; } public void setHeadCommit(String headCommit) { this.headCommit = headCommit; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } } }

6. CI/CD Integration

GitHub Actions Workflow

# .github/workflows/lgtm-analysis.yml name: LGTM Analysis on: push: branches: [ main, develop ] pull_request: branches: [ main ] jobs: lgtm-analysis: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - name: Set up Java uses: actions/setup-java@v3 with: java-version: '11' distribution: 'temurin' cache: maven - name: Build with Maven run: mvn -B compile test-compile -DskipTests - name: Run LGTM Analysis uses: lgtm-com/lgtm-action@v1 with: project-token: ${{ secrets.LGTM_PROJECT_TOKEN }} output-format: sarif output-file: lgtm-results.sarif - name: Upload SARIF results uses: github/codeql-action/upload-sarif@v2 with: sarif_file: lgtm-results.sarif - name: Check for critical alerts run: | # Custom script to check results python scripts/check-lgtm-results.py - name: Notify on critical issues if: failure() uses: actions/github-script@v6 with: script: | github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: '🚨 Critical LGTM alerts found! Please review the analysis results.' })

Jenkins Pipeline

// Jenkinsfile pipeline { agent any environment { LGTM_PROJECT_TOKEN = credentials('lgtm-project-token') } stages { stage('Build') { steps { sh 'mvn clean compile test-compile -DskipTests' } } stage('LGTM Analysis') { steps { sh ''' # Install LGTM CLI curl -s https://lgtm.com/ci/lgtm-cli-installer.sh | sh # Run analysis lgtm analyze --format sarif --output lgtm-results.sarif . # Check results python scripts/check_lgtm_results.py ''' } post { always { archiveArtifacts artifacts: 'lgtm-results.sarif', fingerprint: true publishSARIF filePattern: 'lgtm-results.sarif' } failure { emailext ( subject: "LGTM Analysis Failed - ${env.JOB_NAME}", body: "Critical alerts found in ${env.BUILD_URL}", to: "[email protected]" ) } } } } }

7. REST API Controllers

package com.example.lgtm.controller; import com.example.lgtm.service.LGTMAnalysisService; import com.example.lgtm.service.LGTMAnalysisService.AnalysisReport; import com.example.lgtm.service.LGTMAnalysisService.AlertStatistics; import com.example.lgtm.service.LGTMAnalysisService.PullRequestAnalysis; import com.example.lgtm.client.LGTMApiClient.Alert; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.Map; @RestController @RequestMapping("/api/lgtm") public class LGTMController { private final LGTMAnalysisService analysisService; public LGTMController(LGTMAnalysisService analysisService) { this.analysisService = analysisService; } @PostMapping("/analyze") public ResponseEntity<String> triggerAnalysis() { try { analysisService.triggerAndWaitForAnalysis(); return ResponseEntity.ok("Analysis completed successfully"); } catch (Exception e) { return ResponseEntity.internalServerError().body("Analysis failed: " + e.getMessage()); } } @GetMapping("/alerts") public ResponseEntity<List<Alert>> getAlerts(@RequestParam(required = false) String severity) { try { List<Alert> alerts = analysisService.getAlertsBySeverity(severity); return ResponseEntity.ok(alerts); } catch (Exception e) { return ResponseEntity.internalServerError().build(); } } @GetMapping("/alerts/statistics") public ResponseEntity<AlertStatistics> getAlertStatistics() { try { AlertStatistics stats = analysisService.getAlertStatistics(); return ResponseEntity.ok(stats); } catch (Exception e) { return ResponseEntity.internalServerError().build(); } } @GetMapping("/alerts/categories") public ResponseEntity<Map<String, List<Alert>>> getAlertsByCategory() { try { Map<String, List<Alert>> alertsByCategory = analysisService.getAlertsByCategory(); return ResponseEntity.ok(alertsByCategory); } catch (Exception e) { return ResponseEntity.internalServerError().build(); } } @PostMapping("/pr/{repo}/{prNumber}/analyze") public ResponseEntity<PullRequestAnalysis> analyzePullRequest( @PathVariable String repo, @PathVariable int prNumber) { try { PullRequestAnalysis analysis = analysisService.analyzePullRequest(repo, prNumber); return ResponseEntity.ok(analysis); } catch (Exception e) { return ResponseEntity.internalServerError().build(); } } @GetMapping("/report") public ResponseEntity<AnalysisReport> generateReport() { try { AnalysisReport report = analysisService.generateReport(); return ResponseEntity.ok(report); } catch (Exception e) { return ResponseEntity.internalServerError().build(); } } @GetMapping("/export/sarif") public ResponseEntity<String> exportToSarif() { try { String sarif = analysisService.exportToSarif(); return ResponseEntity.ok() .header("Content-Type", "application/sarif+json") .header("Content-Disposition", "attachment; filename=lgtm-results.sarif") .body(sarif); } catch (Exception e) { return ResponseEntity.internalServerError().build(); } } @GetMapping("/health") public ResponseEntity<Map<String, String>> healthCheck() { try { analysisService.getCurrentAnalysis(); return ResponseEntity.ok(Map.of("status", "healthy")); } catch (Exception e) { return ResponseEntity.status(503) .body(Map.of("status", "unhealthy", "error", e.getMessage())); } } }

8. Application Configuration

# application.yml lgtm: api: token: ${LGTM_API_TOKEN} base-url: https://lgtm.com/api/v1.0 project: id: ${LGTM_PROJECT_ID} analysis: timeout: 600 fail-on-critical: true github: token: ${GITHUB_TOKEN} notification: slack: webhook-url: ${SLACK_WEBHOOK_URL} email: enabled: false recipients: [email protected] logging: level: com.example.lgtm: DEBUG

Best Practices

1. Query Optimization

// Custom query for specific project patterns public class CustomQueries { /** * Query to detect unsafe file operations */ public static final String UNSAFE_FILE_OPERATION = """ import java import semmle.code.java.dataflow.DataFlow import semmle.code.java.dataflow.TaintTracking from MethodAccess fileOp, Expr userInput where fileOp.getMethod().getName().matches("delete|rename|create") and fileOp.getMethod().getDeclaringType().hasQualifiedName("java.io", "File") and DataFlow::localFlow(DataFlow::parameterNode(userInput), DataFlow::exprNode(fileOp.getArgument(0))) select fileOp, "Unsafe file operation with user input: $@", userInput, userInput.toString() """; }

2. Alert Management

@Component public class AlertManagementService { public void autoCloseFalsePositives(List<Alert> alerts) { // Auto-close known false positives alerts.stream() .filter(this::isFalsePositive) .forEach(this::closeAlert); } private boolean isFalsePositive(Alert alert) { // Implement false positive detection logic return alert.getFile().contains("/test/") || alert.getMessage().contains("known false positive"); } }

Conclusion

This comprehensive LGTM integration provides:

  • Automated code analysis with LGTM.com
  • CI/CD integration with GitHub Actions and Jenkins
  • Pull request analysis with automatic comments
  • Custom query development for project-specific patterns
  • Comprehensive reporting with statistics and trends
  • SARIF export for tool integration
  • REST API for programmatic access

Key benefits:

  • Continuous code quality monitoring
  • Security vulnerability detection
  • Developer workflow integration
  • Customizable analysis with project-specific queries
  • Comprehensive reporting and trend analysis

This setup ensures code quality and security are maintained throughout the development lifecycle, with immediate feedback to developers and comprehensive reporting for teams.

Secure Java Dependency Management, Vulnerability Scanning & Software Supply Chain Protection (SBOM, SCA, CI Security & License Compliance)

https://macronepal.com/blog/github-code-scanning-in-java-complete-guide/
Explains GitHub Code Scanning for Java using tools like CodeQL to automatically analyze source code and detect security vulnerabilities directly inside CI/CD pipelines before deployment.

https://macronepal.com/blog/license-compliance-in-java-comprehensive-guide/
Explains software license compliance in Java projects, ensuring dependencies follow legal requirements (MIT, Apache, GPL, etc.) and preventing license violations in enterprise software.

https://macronepal.com/blog/container-security-for-java-uncovering-vulnerabilities-with-grype/
Explains using Grype to scan Java container images and filesystems for known CVEs in OS packages and application dependencies to improve container security.

https://macronepal.com/blog/syft-sbom-generation-in-java-comprehensive-software-bill-of-materials-for-jvm-applications/
Explains using Syft to generate SBOMs (Software Bill of Materials) for Java applications, listing all dependencies, libraries, and components for supply chain transparency.

https://macronepal.com/blog/comprehensive-dependency-analysis-generating-and-scanning-sboms-with-trivy-for-java/
Explains using Trivy to generate SBOMs and scan Java dependencies and container images for vulnerabilities, integrating security checks into CI/CD pipelines.

https://macronepal.com/blog/dependabot-for-java-in-java/
Explains GitHub Dependabot for Java projects, which automatically detects vulnerable dependencies and creates pull requests to update them securely.

https://macronepal.com/blog/parasoft-jtest-in-java-comprehensive-guide-to-code-analysis-and-testing/
Explains Parasoft Jtest, a static analysis and testing tool for Java that helps detect bugs, security issues, and code quality problems early in development.

https://macronepal.com/blog/snyk-open-source-in-java-comprehensive-dependency-vulnerability-management-2/
Explains Snyk Open Source for Java, which continuously scans dependencies for vulnerabilities and provides automated fix suggestions and monitoring.

https://macronepal.com/blog/owasp-dependency-check-in-java-complete-vulnerability-scanning-guide/
Explains OWASP Dependency-Check, which scans Java dependencies against the National Vulnerability Database (NVD) to detect known security vulnerabilities.

https://macronepal.com/blog/securing-your-dependencies-a-java-developers-guide-to-whitesource-mend-bolt/
Explains Mend (WhiteSource) Bolt for Java, a dependency management and SCA tool that provides vulnerability detection, license compliance, and security policy enforcement in enterprise environments.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper