SearchResource.java
/*******************************************************************************
* Copyright (c) 2026 Carsten Hammer.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Carsten Hammer
*******************************************************************************/
package org.eclipse.jgit.server.rest;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.eclipse.jgit.storage.hibernate.config.HibernateSessionFactoryProvider;
import org.eclipse.jgit.storage.hibernate.entity.FilePathHistory;
import org.eclipse.jgit.storage.hibernate.entity.GitCommitIndex;
import org.eclipse.jgit.storage.hibernate.entity.JavaBlobIndex;
import org.eclipse.jgit.storage.hibernate.search.EmbeddingService;
import org.eclipse.jgit.storage.hibernate.service.ApiChangeEntry;
import org.eclipse.jgit.storage.hibernate.service.ApiDiffResult;
import org.eclipse.jgit.storage.hibernate.service.GitDatabaseQueryService;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/**
* REST endpoint for full-text search over Git commit data and Java source.
* <ul>
* <li>{@code GET /api/search/commits?repo=...&q=...} — search commit
* messages</li>
* <li>{@code GET /api/search/paths?repo=...&q=...} — search changed
* paths</li>
* <li>{@code GET /api/search/types?repo=...&q=...} — search Java
* types by name or FQN</li>
* <li>{@code GET /api/search/symbols?repo=...&q=...} — search methods
* and fields</li>
* <li>{@code GET /api/search/hierarchy?repo=...&q=...} — find types
* by supertype</li>
* <li>{@code GET /api/search/source?repo=...&q=...} — full-text
* search across source snippets</li>
* <li>{@code GET /api/search/semantic?repo=...&q=...} — semantic
* (vector) search using sentence embeddings</li>
* <li>{@code GET /api/search/hybrid?repo=...&q=...} — combined
* full-text + semantic search with rank fusion</li>
* <li>{@code GET /api/search/similar?repo=...&blobId=...} — find
* semantically similar code to a given blob</li>
* </ul>
*/
public class SearchResource extends HttpServlet {
private static final long serialVersionUID = 1L;
private static final Logger LOG = Logger
.getLogger(SearchResource.class.getName());
private final HibernateSessionFactoryProvider provider;
private final EmbeddingService embeddingService;
private final Gson gson = new Gson();
private static final int MAX_QUERY_LENGTH = 1000;
private static final int MAX_LIMIT = 100;
private static final int DEFAULT_LIMIT = 20;
/**
* Create a search endpoint.
*
* @param provider
* the session factory provider
*/
public SearchResource(HibernateSessionFactoryProvider provider) {
this.provider = provider;
this.embeddingService = new EmbeddingService();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
resp.setContentType("application/json"); //$NON-NLS-1$
resp.setCharacterEncoding("UTF-8"); //$NON-NLS-1$
String pathInfo = req.getPathInfo();
if (pathInfo == null) {
pathInfo = "/"; //$NON-NLS-1$
}
String repo = req.getParameter("repo"); //$NON-NLS-1$
String query = req.getParameter("q"); //$NON-NLS-1$
int offset = parseIntParam(req, "offset", 0); //$NON-NLS-1$
int limit = parseIntParam(req, "limit", DEFAULT_LIMIT); //$NON-NLS-1$
// Paths that only need 'repo' (no 'q' required)
boolean repoOnly = pathInfo.startsWith("/migration/deprecated") //$NON-NLS-1$
|| pathInfo.startsWith("/deprecation-timeline") //$NON-NLS-1$
|| pathInfo.startsWith("/analytics/authors") //$NON-NLS-1$
|| pathInfo.startsWith("/analytics/monster-classes") //$NON-NLS-1$
|| pathInfo.startsWith("/analytics/dead-code") //$NON-NLS-1$
|| pathInfo.startsWith("/analytics/test-ratio"); //$NON-NLS-1$
// Paths that need 'repo', 'commitA', 'commitB'
boolean commitDiff = pathInfo.startsWith("/api-diff"); //$NON-NLS-1$
if (repo == null || repo.isEmpty()) {
resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
try (PrintWriter w = resp.getWriter()) {
w.write("{\"error\":\"Parameter 'repo' is required\"}"); //$NON-NLS-1$
}
return;
}
if (!repoOnly && !commitDiff
&& (query == null || query.isEmpty())) {
resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
try (PrintWriter w = resp.getWriter()) {
w.write("{\"error\":\"Parameters 'repo' and 'q' are required\"}"); //$NON-NLS-1$
}
return;
}
// Input validation: limit query length to prevent abuse
if (query != null && query.length() > MAX_QUERY_LENGTH) {
resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
try (PrintWriter w = resp.getWriter()) {
w.write("{\"error\":\"Query too long (max " //$NON-NLS-1$
+ MAX_QUERY_LENGTH + " characters)\"}"); //$NON-NLS-1$
}
return;
}
// Enforce pagination limits
if (offset < 0) {
offset = 0;
}
if (limit < 1) {
limit = DEFAULT_LIMIT;
}
if (limit > MAX_LIMIT) {
limit = MAX_LIMIT;
}
// Sanitize repo and query parameters
repo = sanitizeInput(repo);
if (query != null) {
query = sanitizeInput(query);
}
try {
GitDatabaseQueryService queryService = new GitDatabaseQueryService(
provider.getSessionFactory());
queryService.setEmbeddingService(embeddingService);
if (pathInfo.startsWith("/commits")) { //$NON-NLS-1$
handleCommitSearch(queryService, repo, query, offset,
limit, resp);
} else if (pathInfo.startsWith("/paths")) { //$NON-NLS-1$
handlePathSearch(queryService, repo, query, offset,
limit, resp);
} else if (pathInfo.startsWith("/types")) { //$NON-NLS-1$
String module = req.getParameter("module"); //$NON-NLS-1$
if (module != null && !module.isEmpty()) {
module = sanitizeInput(module);
handleTypeSearchWithModule(queryService, repo, query,
module, offset, limit, resp);
} else {
handleTypeSearch(queryService, repo, query, offset,
limit, resp);
}
} else if (pathInfo.startsWith("/symbols")) { //$NON-NLS-1$
handleSymbolSearch(queryService, repo, query, offset,
limit, resp);
} else if (pathInfo.startsWith("/hierarchy")) { //$NON-NLS-1$
handleHierarchySearch(queryService, repo, query, offset,
limit, resp);
} else if (pathInfo.startsWith("/source")) { //$NON-NLS-1$
handleSourceSearch(queryService, repo, query, offset,
limit, resp);
} else if (pathInfo.startsWith("/annotations")) { //$NON-NLS-1$
handleAnnotationSearch(queryService, repo, query, offset,
limit, resp);
} else if (pathInfo.startsWith("/docs")) { //$NON-NLS-1$
handleSearch(queryService, repo, query, offset, limit,
resp, "docs"); //$NON-NLS-1$
} else if (pathInfo.startsWith("/references")) { //$NON-NLS-1$
handleSearch(queryService, repo, query, offset, limit,
resp, "references"); //$NON-NLS-1$
} else if (pathInfo.startsWith("/strings")) { //$NON-NLS-1$
handleSearch(queryService, repo, query, offset, limit,
resp, "strings"); //$NON-NLS-1$
} else if (pathInfo.startsWith("/filepaths")) { //$NON-NLS-1$
handleFilePathSearch(queryService, repo, query, offset,
limit, resp);
} else if (pathInfo.startsWith("/filehistory")) { //$NON-NLS-1$
String path = req.getParameter("path"); //$NON-NLS-1$
if (path != null && !path.isEmpty()) {
path = sanitizeInput(path);
handleFileHistory(queryService, repo, path, offset,
limit, resp);
} else {
handleFilePathSearch(queryService, repo, query,
offset, limit, resp);
}
} else if (pathInfo.startsWith("/fqn")) { //$NON-NLS-1$
String fqnFileType = req.getParameter("fileType"); //$NON-NLS-1$
if (fqnFileType != null) {
fqnFileType = sanitizeInput(fqnFileType);
}
handleFqnSearch(queryService, repo, query, fqnFileType,
offset, limit, resp);
} else if (pathInfo.startsWith("/semantic")) { //$NON-NLS-1$
handleSemanticSearch(queryService, repo, query, limit,
resp);
} else if (pathInfo.startsWith("/hybrid")) { //$NON-NLS-1$
handleHybridSearch(queryService, repo, query, limit,
resp);
} else if (pathInfo.startsWith("/similar")) { //$NON-NLS-1$
String blobId = req.getParameter("blobId"); //$NON-NLS-1$
if (blobId == null || blobId.isEmpty()) {
resp.setStatus(
HttpServletResponse.SC_BAD_REQUEST);
try (PrintWriter w = resp.getWriter()) {
w.write("{\"error\":\"Parameter 'blobId' is required for similar search\"}"); //$NON-NLS-1$
}
} else {
blobId = sanitizeInput(blobId);
handleSimilarSearch(queryService, repo, blobId,
limit, resp);
}
} else if (pathInfo.startsWith("/migration/summary")) { //$NON-NLS-1$
handleMigrationImpactSummary(queryService, repo, query,
resp);
} else if (pathInfo.startsWith("/migration/deprecated")) { //$NON-NLS-1$
handleDeprecatedApiUsage(queryService, repo, offset,
limit, resp);
} else if (pathInfo.startsWith("/migration")) { //$NON-NLS-1$
handleMigrationImpact(queryService, repo, query, offset,
limit, resp);
} else if (pathInfo.startsWith("/api-diff/public")) { //$NON-NLS-1$
handlePublicApiChanges(queryService, repo, req, resp);
} else if (pathInfo.startsWith("/api-diff")) { //$NON-NLS-1$
handleApiDiff(queryService, repo, req, resp);
} else if (pathInfo.startsWith("/deprecation-timeline")) { //$NON-NLS-1$
handleDeprecationTimeline(queryService, repo, offset,
limit, resp);
} else if (pathInfo.startsWith("/analytics/authors")) { //$NON-NLS-1$
handleAuthorTypeStatistics(queryService, repo, resp);
} else if (pathInfo.startsWith("/analytics/complexity")) { //$NON-NLS-1$
handleCodeComplexityTrend(queryService, repo, query,
resp);
} else if (pathInfo.startsWith("/analytics/monster-classes")) { //$NON-NLS-1$
int threshold = parseIntParam(req, "threshold", 500); //$NON-NLS-1$
handleMonsterClasses(queryService, repo, threshold,
offset, limit, resp);
} else if (pathInfo.startsWith("/analytics/dead-code")) { //$NON-NLS-1$
handleDeadCodeCandidates(queryService, repo, offset,
limit, resp);
} else if (pathInfo.startsWith("/analytics/test-ratio")) { //$NON-NLS-1$
handleTestCoverageProxy(queryService, repo, resp);
} else {
// Default: search commits
handleCommitSearch(queryService, repo, query, offset,
limit, resp);
}
} catch (RuntimeException | IOException e) {
LOG.log(Level.WARNING, "Search error", e); //$NON-NLS-1$
resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
try (PrintWriter w = resp.getWriter()) {
w.write("{\"error\":\"Search failed\"}"); //$NON-NLS-1$
}
}
}
private void handleCommitSearch(GitDatabaseQueryService queryService,
String repo, String query, int offset, int limit,
HttpServletResponse resp) throws IOException {
List<GitCommitIndex> results = queryService
.searchCommitMessages(repo, query, offset, limit);
JsonObject response = new JsonObject();
response.addProperty("query", query); //$NON-NLS-1$
response.addProperty("repository", repo); //$NON-NLS-1$
response.addProperty("totalResults", results.size()); //$NON-NLS-1$
JsonArray items = new JsonArray();
for (GitCommitIndex ci : results) {
JsonObject item = new JsonObject();
item.addProperty("objectId", ci.getObjectId()); //$NON-NLS-1$
item.addProperty("message", ci.getCommitMessage()); //$NON-NLS-1$
item.addProperty("author", ci.getAuthorName()); //$NON-NLS-1$
item.addProperty("authorEmail", ci.getAuthorEmail()); //$NON-NLS-1$
items.add(item);
}
response.add("results", items); //$NON-NLS-1$
resp.setStatus(HttpServletResponse.SC_OK);
try (PrintWriter w = resp.getWriter()) {
w.write(gson.toJson(response));
}
}
private void handlePathSearch(GitDatabaseQueryService queryService,
String repo, String query, int offset, int limit,
HttpServletResponse resp) throws IOException {
List<GitCommitIndex> results = queryService
.searchByChangedPath(repo, query, offset, limit);
JsonObject response = new JsonObject();
response.addProperty("query", query); //$NON-NLS-1$
response.addProperty("repository", repo); //$NON-NLS-1$
response.addProperty("totalResults", results.size()); //$NON-NLS-1$
JsonArray items = new JsonArray();
for (GitCommitIndex ci : results) {
JsonObject item = new JsonObject();
item.addProperty("objectId", ci.getObjectId()); //$NON-NLS-1$
item.addProperty("message", ci.getCommitMessage()); //$NON-NLS-1$
item.addProperty("changedPaths", ci.getChangedPaths()); //$NON-NLS-1$
items.add(item);
}
response.add("results", items); //$NON-NLS-1$
resp.setStatus(HttpServletResponse.SC_OK);
try (PrintWriter w = resp.getWriter()) {
w.write(gson.toJson(response));
}
}
private void handleTypeSearch(GitDatabaseQueryService queryService,
String repo, String query, int offset, int limit,
HttpServletResponse resp) throws IOException {
List<JavaBlobIndex> results = queryService.searchByType(repo,
query, offset, limit);
writeJavaBlobResponse(results, repo, query, offset, limit, resp);
}
private void handleTypeSearchWithModule(
GitDatabaseQueryService queryService, String repo,
String query, String module, int offset, int limit,
HttpServletResponse resp) throws IOException {
List<JavaBlobIndex> results = queryService
.searchByTypeWithModule(repo, query, module, offset, limit);
writeJavaBlobResponse(results, repo, query, offset, limit, resp);
}
private void handleAnnotationSearch(
GitDatabaseQueryService queryService, String repo,
String query, int offset, int limit,
HttpServletResponse resp) throws IOException {
List<JavaBlobIndex> results = queryService
.searchByAnnotation(repo, query, offset, limit);
writeJavaBlobResponse(results, repo, query, offset, limit, resp);
}
private void handleSymbolSearch(GitDatabaseQueryService queryService,
String repo, String query, int offset, int limit,
HttpServletResponse resp) throws IOException {
List<JavaBlobIndex> results = queryService.searchBySymbol(repo,
query, offset, limit);
writeJavaBlobResponse(results, repo, query, offset, limit, resp);
}
private void handleHierarchySearch(GitDatabaseQueryService queryService,
String repo, String query, int offset, int limit,
HttpServletResponse resp) throws IOException {
List<JavaBlobIndex> results = queryService.searchByHierarchy(repo,
query, offset, limit);
writeJavaBlobResponse(results, repo, query, offset, limit, resp);
}
private void handleSourceSearch(GitDatabaseQueryService queryService,
String repo, String query, int offset, int limit,
HttpServletResponse resp) throws IOException {
List<JavaBlobIndex> results = queryService
.searchSourceContent(repo, query, offset, limit);
writeJavaBlobResponse(results, repo, query, offset, limit, resp);
}
private void handleSearch(GitDatabaseQueryService service,
String repo, String query, int offset, int limit,
HttpServletResponse resp, String type) throws IOException {
List<JavaBlobIndex> results;
switch (type) {
case "docs": //$NON-NLS-1$
results = service.searchByDocumentation(repo, query,
offset, limit);
break;
case "references": //$NON-NLS-1$
results = service.searchByReferencedType(repo, query,
offset, limit);
break;
case "strings": //$NON-NLS-1$
results = service.searchByStringLiteral(repo, query,
offset, limit);
break;
default:
results = List.of();
break;
}
writeJavaBlobResponse(results, repo, query, offset, limit, resp);
}
private void handleFilePathSearch(GitDatabaseQueryService queryService,
String repo, String query, int offset, int limit,
HttpServletResponse resp) throws IOException {
List<FilePathHistory> results = queryService
.searchFilePath(repo, query, offset, limit);
writeFilePathResponse(results, repo, query, offset, limit, resp);
}
private void handleFileHistory(GitDatabaseQueryService queryService,
String repo, String path, int offset, int limit,
HttpServletResponse resp) throws IOException {
List<FilePathHistory> results = queryService
.getFileHistory(repo, path, offset, limit);
writeFilePathResponse(results, repo, path, offset, limit, resp);
}
private void handleFqnSearch(GitDatabaseQueryService queryService,
String repo, String query, String fileType, int offset,
int limit, HttpServletResponse resp) throws IOException {
List<JavaBlobIndex> results = queryService
.searchFqnAcrossTypes(repo, query, fileType, offset, limit);
writeJavaBlobResponse(results, repo, query, offset, limit, resp);
}
private void handleSemanticSearch(
GitDatabaseQueryService queryService, String repo,
String query, int topK, HttpServletResponse resp)
throws IOException {
List<JavaBlobIndex> results = queryService.semanticSearch(repo,
query, topK);
writeJavaBlobResponse(results, repo, query, 0, topK, resp);
}
private void handleHybridSearch(
GitDatabaseQueryService queryService, String repo,
String query, int topK, HttpServletResponse resp)
throws IOException {
List<JavaBlobIndex> results = queryService.hybridSearch(repo,
query, topK);
writeJavaBlobResponse(results, repo, query, 0, topK, resp);
}
private void handleSimilarSearch(
GitDatabaseQueryService queryService, String repo,
String blobId, int topK, HttpServletResponse resp)
throws IOException {
List<JavaBlobIndex> results = queryService.findSimilarCode(repo,
blobId, topK);
writeJavaBlobResponse(results, repo, blobId, 0, topK, resp);
}
private void handleMigrationImpact(GitDatabaseQueryService queryService,
String repo, String importPrefix, int offset, int limit,
HttpServletResponse resp) throws IOException {
List<JavaBlobIndex> results = queryService.getMigrationImpact(
repo, importPrefix, offset, limit);
writeJavaBlobResponse(results, repo, importPrefix, offset, limit,
resp);
}
private void handleMigrationImpactSummary(
GitDatabaseQueryService queryService, String repo,
String importPrefix, HttpServletResponse resp)
throws IOException {
Map<String, Object> summary = queryService
.getMigrationImpactSummary(repo, importPrefix);
JsonObject response = new JsonObject();
response.addProperty("repository", repo); //$NON-NLS-1$
response.addProperty("importPrefix", importPrefix); //$NON-NLS-1$
for (Map.Entry<String, Object> entry : summary.entrySet()) {
Object val = entry.getValue();
if (val instanceof Number) {
response.addProperty(entry.getKey(),
((Number) val).longValue());
} else if (val != null) {
response.addProperty(entry.getKey(), val.toString());
} else {
response.add(entry.getKey(), null);
}
}
resp.setStatus(HttpServletResponse.SC_OK);
try (PrintWriter w = resp.getWriter()) {
w.write(gson.toJson(response));
}
}
private void handleDeprecatedApiUsage(
GitDatabaseQueryService queryService, String repo,
int offset, int limit, HttpServletResponse resp)
throws IOException {
List<JavaBlobIndex> results = queryService.getDeprecatedApiUsage(
repo, offset, limit);
writeJavaBlobResponse(results, repo, "Deprecated", offset, limit, //$NON-NLS-1$
resp);
}
private void handleApiDiff(GitDatabaseQueryService queryService,
String repo, HttpServletRequest req,
HttpServletResponse resp) throws IOException {
String commitA = req.getParameter("commitA"); //$NON-NLS-1$
String commitB = req.getParameter("commitB"); //$NON-NLS-1$
if (commitA == null || commitA.isEmpty() || commitB == null
|| commitB.isEmpty()) {
resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
try (PrintWriter w = resp.getWriter()) {
w.write("{\"error\":\"Parameters 'commitA' and 'commitB' are required\"}"); //$NON-NLS-1$
}
return;
}
commitA = sanitizeInput(commitA);
commitB = sanitizeInput(commitB);
ApiDiffResult diff = queryService.getApiDiff(repo, commitA,
commitB);
writeApiDiffResponse(diff, resp);
}
private void handlePublicApiChanges(GitDatabaseQueryService queryService,
String repo, HttpServletRequest req,
HttpServletResponse resp) throws IOException {
String commitA = req.getParameter("commitA"); //$NON-NLS-1$
String commitB = req.getParameter("commitB"); //$NON-NLS-1$
if (commitA == null || commitA.isEmpty() || commitB == null
|| commitB.isEmpty()) {
resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
try (PrintWriter w = resp.getWriter()) {
w.write("{\"error\":\"Parameters 'commitA' and 'commitB' are required\"}"); //$NON-NLS-1$
}
return;
}
commitA = sanitizeInput(commitA);
commitB = sanitizeInput(commitB);
ApiDiffResult diff = queryService.getPublicApiChanges(repo,
commitA, commitB);
writeApiDiffResponse(diff, resp);
}
private void handleDeprecationTimeline(
GitDatabaseQueryService queryService, String repo,
int offset, int limit, HttpServletResponse resp)
throws IOException {
List<JavaBlobIndex> results = queryService.getDeprecationTimeline(
repo, offset, limit);
writeJavaBlobResponse(results, repo, "deprecation-timeline", //$NON-NLS-1$
offset, limit, resp);
}
private void handleAuthorTypeStatistics(
GitDatabaseQueryService queryService, String repo,
HttpServletResponse resp) throws IOException {
List<Object[]> rows = queryService.getAuthorTypeStatistics(repo);
JsonObject response = new JsonObject();
response.addProperty("repository", repo); //$NON-NLS-1$
response.addProperty("totalResults", rows.size()); //$NON-NLS-1$
JsonArray items = new JsonArray();
for (Object[] row : rows) {
JsonObject item = new JsonObject();
item.addProperty("author", //$NON-NLS-1$
row[0] != null ? row[0].toString() : null);
item.addProperty("typeKind", //$NON-NLS-1$
row[1] != null ? row[1].toString() : null);
item.addProperty("count", //$NON-NLS-1$
row[2] != null ? ((Number) row[2]).longValue() : 0L);
items.add(item);
}
response.add("results", items); //$NON-NLS-1$
resp.setStatus(HttpServletResponse.SC_OK);
try (PrintWriter w = resp.getWriter()) {
w.write(gson.toJson(response));
}
}
private void handleCodeComplexityTrend(
GitDatabaseQueryService queryService, String repo,
String packagePrefix, HttpServletResponse resp)
throws IOException {
List<Object[]> rows = queryService.getCodeComplexityTrend(repo,
packagePrefix);
JsonObject response = new JsonObject();
response.addProperty("repository", repo); //$NON-NLS-1$
response.addProperty("packagePrefix", packagePrefix); //$NON-NLS-1$
response.addProperty("totalResults", rows.size()); //$NON-NLS-1$
JsonArray items = new JsonArray();
for (Object[] row : rows) {
JsonObject item = new JsonObject();
item.addProperty("commitDate", //$NON-NLS-1$
row[0] != null ? row[0].toString() : null);
item.addProperty("avgLineCount", //$NON-NLS-1$
row[1] != null ? ((Number) row[1]).doubleValue() : 0.0);
items.add(item);
}
response.add("results", items); //$NON-NLS-1$
resp.setStatus(HttpServletResponse.SC_OK);
try (PrintWriter w = resp.getWriter()) {
w.write(gson.toJson(response));
}
}
private void handleMonsterClasses(GitDatabaseQueryService queryService,
String repo, int threshold, int offset, int limit,
HttpServletResponse resp) throws IOException {
List<JavaBlobIndex> results = queryService.getMonsterClasses(repo,
threshold, offset, limit);
writeJavaBlobResponse(results, repo,
"lineCount>=" + threshold, offset, limit, resp); //$NON-NLS-1$
}
private void handleDeadCodeCandidates(
GitDatabaseQueryService queryService, String repo,
int offset, int limit,
HttpServletResponse resp) throws IOException {
List<JavaBlobIndex> results = queryService
.getDeadCodeCandidates(repo, offset, limit);
writeJavaBlobResponse(results, repo, "dead-code", offset, //$NON-NLS-1$
limit, resp);
}
private void handleTestCoverageProxy(
GitDatabaseQueryService queryService, String repo,
HttpServletResponse resp) throws IOException {
List<Object[]> rows = queryService.getTestCoverageProxy(repo);
JsonObject response = new JsonObject();
response.addProperty("repository", repo); //$NON-NLS-1$
response.addProperty("totalResults", rows.size()); //$NON-NLS-1$
JsonArray items = new JsonArray();
for (Object[] row : rows) {
JsonObject item = new JsonObject();
item.addProperty("packageName", //$NON-NLS-1$
row[0] != null ? row[0].toString() : null);
item.addProperty("testTypeCount", //$NON-NLS-1$
row[1] != null ? ((Number) row[1]).longValue() : 0L);
item.addProperty("totalTypeCount", //$NON-NLS-1$
row[2] != null ? ((Number) row[2]).longValue() : 0L);
items.add(item);
}
response.add("results", items); //$NON-NLS-1$
resp.setStatus(HttpServletResponse.SC_OK);
try (PrintWriter w = resp.getWriter()) {
w.write(gson.toJson(response));
}
}
private void writeApiDiffResponse(ApiDiffResult diff,
HttpServletResponse resp) throws IOException {
JsonObject response = new JsonObject();
JsonArray added = new JsonArray();
for (JavaBlobIndex jbi : diff.getAddedFiles()) {
added.add(toJsonObject(jbi));
}
response.add("addedFiles", added); //$NON-NLS-1$
JsonArray removed = new JsonArray();
for (JavaBlobIndex jbi : diff.getRemovedFiles()) {
removed.add(toJsonObject(jbi));
}
response.add("removedFiles", removed); //$NON-NLS-1$
JsonArray changed = new JsonArray();
for (ApiChangeEntry ce : diff.getChangedFiles()) {
JsonObject item = new JsonObject();
item.add("before", toJsonObject(ce.getBefore())); //$NON-NLS-1$
item.add("after", toJsonObject(ce.getAfter())); //$NON-NLS-1$
item.addProperty("changeDescription", //$NON-NLS-1$
ce.getChangeDescription());
changed.add(item);
}
response.add("changedFiles", changed); //$NON-NLS-1$
resp.setStatus(HttpServletResponse.SC_OK);
try (PrintWriter w = resp.getWriter()) {
w.write(gson.toJson(response));
}
}
private static JsonObject toJsonObject(JavaBlobIndex jbi) {
if (jbi == null) {
return new JsonObject();
}
JsonObject item = new JsonObject();
item.addProperty("blobObjectId", jbi.getBlobObjectId()); //$NON-NLS-1$
item.addProperty("commitObjectId", jbi.getCommitObjectId()); //$NON-NLS-1$
item.addProperty("filePath", jbi.getFilePath()); //$NON-NLS-1$
item.addProperty("packageName", jbi.getPackageName()); //$NON-NLS-1$
item.addProperty("declaredTypes", jbi.getDeclaredTypes()); //$NON-NLS-1$
item.addProperty("fullyQualifiedNames", //$NON-NLS-1$
jbi.getFullyQualifiedNames());
item.addProperty("declaredMethods", jbi.getDeclaredMethods()); //$NON-NLS-1$
item.addProperty("visibility", jbi.getVisibility()); //$NON-NLS-1$
return item;
}
private void writeFilePathResponse(List<FilePathHistory> results,
String repo, String query, int offset, int limit,
HttpServletResponse resp) throws IOException {
JsonObject response = new JsonObject();
response.addProperty("query", query); //$NON-NLS-1$
response.addProperty("repository", repo); //$NON-NLS-1$
response.addProperty("offset", offset); //$NON-NLS-1$
response.addProperty("limit", limit); //$NON-NLS-1$
response.addProperty("totalResults", results.size()); //$NON-NLS-1$
JsonArray items = new JsonArray();
for (FilePathHistory fph : results) {
JsonObject item = new JsonObject();
item.addProperty("commitObjectId", //$NON-NLS-1$
fph.getCommitObjectId());
item.addProperty("filePath", fph.getFilePath()); //$NON-NLS-1$
item.addProperty("blobObjectId", //$NON-NLS-1$
fph.getBlobObjectId());
item.addProperty("fileType", fph.getFileType()); //$NON-NLS-1$
if (fph.getCommitTime() != null) {
item.addProperty("commitTime", //$NON-NLS-1$
fph.getCommitTime().toString());
}
items.add(item);
}
response.add("results", items); //$NON-NLS-1$
resp.setStatus(HttpServletResponse.SC_OK);
try (PrintWriter w = resp.getWriter()) {
w.write(gson.toJson(response));
}
}
private void writeJavaBlobResponse(List<JavaBlobIndex> results,
String repo, String query, int offset, int limit,
HttpServletResponse resp) throws IOException {
JsonObject response = new JsonObject();
response.addProperty("query", query); //$NON-NLS-1$
response.addProperty("repository", repo); //$NON-NLS-1$
response.addProperty("offset", offset); //$NON-NLS-1$
response.addProperty("limit", limit); //$NON-NLS-1$
response.addProperty("totalResults", results.size()); //$NON-NLS-1$
JsonArray items = new JsonArray();
for (JavaBlobIndex jbi : results) {
JsonObject item = new JsonObject();
item.addProperty("blobObjectId", jbi.getBlobObjectId()); //$NON-NLS-1$
item.addProperty("commitObjectId", //$NON-NLS-1$
jbi.getCommitObjectId());
item.addProperty("filePath", jbi.getFilePath()); //$NON-NLS-1$
item.addProperty("packageName", jbi.getPackageName()); //$NON-NLS-1$
item.addProperty("declaredTypes", //$NON-NLS-1$
jbi.getDeclaredTypes());
item.addProperty("fullyQualifiedNames", //$NON-NLS-1$
jbi.getFullyQualifiedNames());
item.addProperty("declaredMethods", //$NON-NLS-1$
jbi.getDeclaredMethods());
item.addProperty("declaredFields", //$NON-NLS-1$
jbi.getDeclaredFields());
item.addProperty("extendsTypes", //$NON-NLS-1$
jbi.getExtendsTypes());
item.addProperty("implementsTypes", //$NON-NLS-1$
jbi.getImplementsTypes());
items.add(item);
}
response.add("results", items); //$NON-NLS-1$
resp.setStatus(HttpServletResponse.SC_OK);
try (PrintWriter w = resp.getWriter()) {
w.write(gson.toJson(response));
}
}
private static int parseIntParam(HttpServletRequest req,
String paramName, int defaultValue) {
String value = req.getParameter(paramName);
if (value == null || value.isEmpty()) {
return defaultValue;
}
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
return defaultValue;
}
}
/**
* Sanitize user input by stripping control characters.
*
* @param input
* the raw input string
* @return the sanitized string
*/
private static String sanitizeInput(String input) {
if (input == null) {
return null;
}
// Strip control characters except common whitespace
return input.replaceAll("[\\p{Cntrl}&&[^\t\n\r]]", ""); //$NON-NLS-1$ //$NON-NLS-2$
}
}