Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,14 @@
import org.apache.solr.client.solrj.response.LukeResponse;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.client.solrj.response.SolrPingResponse;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.mcp.server.config.SolrConfigurationProperties;
import org.apache.solr.mcp.server.util.PromptNames;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springaicommunity.mcp.annotation.McpArg;
import org.springaicommunity.mcp.annotation.McpComplete;
import org.springaicommunity.mcp.annotation.McpPrompt;
Expand Down Expand Up @@ -136,6 +139,8 @@
@Observed
public class CollectionService {

private static final Logger logger = LoggerFactory.getLogger(CollectionService.class);

// ========================================
// Constants for API Parameters and Paths
// ========================================
Expand Down Expand Up @@ -683,16 +688,17 @@ public QueryStats buildQueryStats(QueryResponse response) {
* Internal cache metrics fetch that assumes the collection has already been
* validated and the name has been extracted from any shard identifier.
*/
private @Nullable CacheStats fetchCacheMetrics(String collection) {
private @Nullable CacheStats fetchCacheMetrics(String collectionName) {
try {
NamedList<Object> coreMetrics = fetchMetrics(collection, CACHE_METRIC_PREFIX);
NamedList<Object> coreMetrics = fetchMetrics(collectionName, CACHE_METRIC_PREFIX);
if (coreMetrics == null) {
return null;
}

CacheStats stats = extractCacheStats(coreMetrics);
return isCacheStatsEmpty(stats) ? null : stats;
} catch (SolrServerException | IOException | RuntimeException _) {
} catch (SolrServerException | IOException | SolrException e) {
logger.debug("Cache metrics unavailable for collection: {}", collectionName, e);
return null;
}
}
Expand Down Expand Up @@ -799,18 +805,19 @@ private CacheStats extractCacheStats(NamedList<Object> coreMetrics) {
* Internal handler metrics fetch that assumes the collection has already been
* validated and the name has been extracted from any shard identifier.
*/
private @Nullable HandlerStats fetchHandlerMetrics(String collection) {
private @Nullable HandlerStats fetchHandlerMetrics(String collectionName) {
try {
// Handler metrics are flat keys (e.g. QUERY./select.requests) so we
// fetch each handler prefix separately and reconstruct HandlerInfo
HandlerInfo selectHandler = fetchFlatHandlerInfo(collection, SELECT_HANDLER_METRIC_PREFIX,
HandlerInfo selectHandler = fetchFlatHandlerInfo(collectionName, SELECT_HANDLER_METRIC_PREFIX,
SELECT_HANDLER_KEY);
HandlerInfo updateHandler = fetchFlatHandlerInfo(collection, UPDATE_HANDLER_METRIC_PREFIX,
HandlerInfo updateHandler = fetchFlatHandlerInfo(collectionName, UPDATE_HANDLER_METRIC_PREFIX,
UPDATE_HANDLER_KEY);

HandlerStats stats = new HandlerStats(selectHandler, updateHandler);
return isHandlerStatsEmpty(stats) ? null : stats;
} catch (SolrServerException | IOException | RuntimeException _) {
} catch (SolrServerException | IOException | SolrException e) {
logger.debug("Handler metrics unavailable for collection: {}", collectionName, e);
return null;
}
}
Expand Down Expand Up @@ -1080,6 +1087,7 @@ public SolrHealthStatus checkHealth(@McpToolParam(description = "Solr collection
statsResponse.getResults().getNumFound(), Instant.now(), actualCollection);

} catch (Exception e) {
logger.warn("Health check failed for collection: {}", collection, e);
return new SolrHealthStatus(false, e.getMessage(), null, null, Instant.now(), actualCollection);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
import org.apache.solr.mcp.server.indexing.documentcreator.IndexingDocumentCreator;
import org.apache.solr.mcp.server.util.PromptNames;
import org.apache.solr.mcp.server.util.PromptText;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springaicommunity.mcp.annotation.McpArg;
import org.springaicommunity.mcp.annotation.McpPrompt;
import org.springaicommunity.mcp.annotation.McpTool;
Expand Down Expand Up @@ -114,6 +116,8 @@
@Observed
public class IndexingService {

private static final Logger logger = LoggerFactory.getLogger(IndexingService.class);

private static final int DEFAULT_BATCH_SIZE = 1000;

/** SolrJ client for communicating with Solr server */
Expand Down Expand Up @@ -501,12 +505,14 @@ public int indexDocuments(String collection, List<SolrInputDocument> documents)
solrClient.add(collection, batch);
successCount += batch.size();
} catch (SolrServerException | IOException | RuntimeException e) {
logger.warn("Batch indexing failed, retrying individually", e);
// Try indexing documents individually to identify problematic ones
for (SolrInputDocument doc : batch) {
try {
solrClient.add(collection, doc);
successCount++;
} catch (SolrServerException | IOException | RuntimeException _) {
} catch (SolrServerException | IOException | RuntimeException e2) {
logger.debug("Failed to index individual document", e2);
// Document failed to index - this is expected behavior for problematic
// documents
// We continue processing the rest of the batch
Expand All @@ -515,7 +521,12 @@ public int indexDocuments(String collection, List<SolrInputDocument> documents)
}
}

solrClient.commit(collection);
try {
solrClient.commit(collection);
} catch (SolrServerException | IOException e) {
logger.error("Failed to commit after indexing to collection: {}", collection, e);
throw e;
}
return successCount;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
import org.apache.solr.client.solrj.request.schema.SchemaRequest;
import org.apache.solr.client.solrj.response.schema.SchemaRepresentation;
import org.apache.solr.mcp.server.util.PromptNames;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springaicommunity.mcp.annotation.McpArg;
import org.springaicommunity.mcp.annotation.McpPrompt;
import org.springaicommunity.mcp.annotation.McpResource;
Expand Down Expand Up @@ -137,6 +139,8 @@
@Observed
public class SchemaService {

private static final Logger logger = LoggerFactory.getLogger(SchemaService.class);

/** SolrJ client for communicating with Solr server */
private final SolrClient solrClient;

Expand Down Expand Up @@ -185,6 +189,7 @@ public String getSchemaResource(String collection) {
try {
return toJson(objectMapper, getSchema(collection));
} catch (Exception e) {
logger.error("Failed to get schema for collection: {}", collection, e);
// Serialise via Jackson rather than concatenating: an exception message
// containing a quote, backslash or newline would otherwise emit invalid
// JSON to the MCP client.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
import org.apache.solr.common.params.FacetParams;
import org.apache.solr.mcp.server.util.PromptNames;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springaicommunity.mcp.annotation.McpArg;
import org.springaicommunity.mcp.annotation.McpPrompt;
import org.springaicommunity.mcp.annotation.McpTool;
Expand Down Expand Up @@ -108,6 +110,8 @@
@Observed
public class SearchService {

private static final Logger logger = LoggerFactory.getLogger(SearchService.class);

/** Key for the field name within a sort clause map. */
public static final String SORT_ITEM = "item";
/**
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/org/apache/solr/mcp/server/util/JsonUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Utility class for JSON serialization operations.
Expand All @@ -31,6 +33,8 @@
*/
public final class JsonUtils {

private static final Logger logger = LoggerFactory.getLogger(JsonUtils.class);

private JsonUtils() {
// Utility class - prevent instantiation
}
Expand All @@ -52,6 +56,7 @@ public static String toJson(ObjectMapper objectMapper, Object obj) {
try {
return objectMapper.writeValueAsString(obj);
} catch (JsonProcessingException e) {
logger.error("Failed to serialize response", e);
return "{\"error\": \"Failed to serialize response\"}";
}
}
Expand Down