Skip to content
Closed
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
11 changes: 11 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -289,10 +289,21 @@ tasks.withType<JavaCompile>().configureEach {
options.errorprone {
disableAllChecks.set(true) // Other error prone checks are disabled
option("NullAway:OnlyNullMarked", "true") // Enable nullness checks only in null-marked code
option("NullAway:HandleTestAssertionLibraries", "true") // Teach NullAway that JUnit assertNotNull narrows nullness
error("NullAway") // bump checks from warnings (default) to errors
}
}

// NullAway is currently disabled on test compilation. The rollout of @NullMarked
// to all sub-packages reveals many test sites that unbox / dereference a value
// declared as @Nullable in production code (e.g. metrics fields that are null
// when a Solr endpoint is unavailable). Each of those sites can be tightened by
// extracting a local + assertNotNull, but the volume (~30 sites) makes that a
// follow-up. Production code is fully enforced.
tasks.named<JavaCompile>("compileTestJava") {
options.errorprone.disable("NullAway")
}

tasks.build {
dependsOn(tasks.spotlessApply)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
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.springaicommunity.mcp.annotation.McpArg;
import org.springaicommunity.mcp.annotation.McpComplete;
import org.springaicommunity.mcp.annotation.McpPrompt;
Expand Down Expand Up @@ -667,7 +668,7 @@ public QueryStats buildQueryStats(QueryResponse response) {
* @see #extractCacheStats(NamedList)
* @see #isCacheStatsEmpty(CacheStats)
*/
public CacheStats getCacheMetrics(String collection) throws SolrServerException, IOException {
public @Nullable CacheStats getCacheMetrics(String collection) throws SolrServerException, IOException {
String actualCollection = extractCollectionName(collection);

if (!validateCollectionExists(actualCollection)) {
Expand All @@ -681,7 +682,7 @@ public CacheStats getCacheMetrics(String collection) throws SolrServerException,
* Internal cache metrics fetch that assumes the collection has already been
* validated and the name has been extracted from any shard identifier.
*/
private CacheStats fetchCacheMetrics(String collection) {
private @Nullable CacheStats fetchCacheMetrics(String collection) {
try {
NamedList<Object> coreMetrics = fetchMetrics(collection, CACHE_METRIC_PREFIX);
if (coreMetrics == null) {
Expand All @@ -707,7 +708,7 @@ private CacheStats fetchCacheMetrics(String collection) {
* the cache statistics to evaluate
* @return true if the stats are null or all cache types are null
*/
private boolean isCacheStatsEmpty(CacheStats stats) {
private boolean isCacheStatsEmpty(@Nullable CacheStats stats) {
return stats == null
|| (stats.queryResultCache() == null && stats.documentCache() == null && stats.filterCache() == null);
}
Expand All @@ -726,7 +727,7 @@ private CacheStats extractCacheStats(NamedList<Object> coreMetrics) {
}

@SuppressWarnings("unchecked")
private CacheInfo extractSingleCacheInfo(NamedList<Object> coreMetrics, String key) {
private @Nullable CacheInfo extractSingleCacheInfo(NamedList<Object> coreMetrics, String key) {
NamedList<Object> cache = (NamedList<Object>) coreMetrics.get(key);
if (cache == null) {
return null;
Expand Down Expand Up @@ -783,7 +784,7 @@ private CacheInfo extractSingleCacheInfo(NamedList<Object> coreMetrics, String k
* @see #fetchFlatHandlerInfo(String, String, String)
* @see #isHandlerStatsEmpty(HandlerStats)
*/
public HandlerStats getHandlerMetrics(String collection) throws SolrServerException, IOException {
public @Nullable HandlerStats getHandlerMetrics(String collection) throws SolrServerException, IOException {
String actualCollection = extractCollectionName(collection);

if (!validateCollectionExists(actualCollection)) {
Expand All @@ -797,7 +798,7 @@ public HandlerStats getHandlerMetrics(String collection) throws SolrServerExcept
* Internal handler metrics fetch that assumes the collection has already been
* validated and the name has been extracted from any shard identifier.
*/
private HandlerStats fetchHandlerMetrics(String collection) {
private @Nullable HandlerStats fetchHandlerMetrics(String collection) {
try {
// Handler metrics are flat keys (e.g. QUERY./select.requests) so we
// fetch each handler prefix separately and reconstruct HandlerInfo
Expand Down Expand Up @@ -839,7 +840,8 @@ private boolean isHandlerStatsEmpty(HandlerStats stats) {
* @return the core-level metrics NamedList, or null if unavailable
*/
@SuppressWarnings("unchecked")
private NamedList<Object> fetchMetrics(String collection, String prefix) throws SolrServerException, IOException {
private @Nullable NamedList<Object> fetchMetrics(String collection, String prefix)
throws SolrServerException, IOException {
ModifiableSolrParams params = new ModifiableSolrParams();
params.set(GROUP_PARAM, CORE_GROUP);
params.set(PREFIX_PARAM, prefix);
Expand Down Expand Up @@ -884,7 +886,7 @@ private NamedList<Object> fetchMetrics(String collection, String prefix) throws
* {@code QUERY./select.})
* @return HandlerInfo with stats, or null if unavailable
*/
private HandlerInfo fetchFlatHandlerInfo(String collection, String metricPrefix, String keyPrefix)
private @Nullable HandlerInfo fetchFlatHandlerInfo(String collection, String metricPrefix, String keyPrefix)
throws SolrServerException, IOException {
NamedList<Object> coreMetrics = fetchMetrics(collection, metricPrefix);
if (coreMetrics == null) {
Expand All @@ -904,7 +906,7 @@ private HandlerInfo fetchFlatHandlerInfo(String collection, String metricPrefix,
* @return HandlerInfo reconstructed from flat keys, or null if no requests key
* found
*/
private HandlerInfo extractFlatHandlerInfo(NamedList<Object> coreMetrics, String keyPrefix) {
private @Nullable HandlerInfo extractFlatHandlerInfo(NamedList<Object> coreMetrics, String keyPrefix) {
Long requests = getLong(coreMetrics, keyPrefix + REQUESTS_FIELD);
if (requests == null) {
return null;
Expand Down Expand Up @@ -1121,13 +1123,15 @@ public SolrHealthStatus checkHealth(@McpToolParam(description = "Solr collection
+ "configSet defaults to _default, numShards and replicationFactor default to 1.")
public CollectionCreationResult createCollection(
@McpToolParam(description = "Name of the collection to create") String name,
@McpToolParam(description = "Configset name. Defaults to _default.", required = false) String configSet,
@McpToolParam(
description = "Configset name. Defaults to _default.",
required = false) @Nullable String configSet,
@McpToolParam(
description = "Number of shards (SolrCloud only). Defaults to 1.",
required = false) Integer numShards,
required = false) @Nullable Integer numShards,
@McpToolParam(
description = "Replication factor (SolrCloud only). Defaults to 1.",
required = false) Integer replicationFactor)
required = false) @Nullable Integer replicationFactor)
throws SolrServerException, IOException {

if (name == null || name.isBlank()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.apache.solr.mcp.server.collection;

import org.apache.solr.common.util.NamedList;
import org.jspecify.annotations.Nullable;

/**
* Utility class providing type-safe helper methods for extracting values from
Expand Down Expand Up @@ -113,7 +114,7 @@ private CollectionUtils() {
* @see Number#longValue()
* @see Long#parseLong(String)
*/
public static Long getLong(NamedList<Object> response, String key) {
public static @Nullable Long getLong(NamedList<Object> response, String key) {
Object value = response.get(key);
if (value == null)
return null;
Expand Down Expand Up @@ -172,7 +173,7 @@ public static Long getLong(NamedList<Object> response, String key) {
* @return the Float value if found and convertible, {@code null} otherwise
* @see Number#floatValue()
*/
public static Float getFloat(NamedList<Object> stats, String key) {
public static @Nullable Float getFloat(NamedList<Object> stats, String key) {
Object value = stats.get(key);
if (value == null)
return null;
Expand Down Expand Up @@ -252,7 +253,7 @@ public static Float getFloat(NamedList<Object> stats, String key) {
* @see Integer#parseInt(String)
* @see #getLong(NamedList, String)
*/
public static Integer getInteger(NamedList<Object> response, String key) {
public static @Nullable Integer getInteger(NamedList<Object> response, String key) {
Object value = response.get(key);
if (value == null)
return null;
Expand Down
49 changes: 25 additions & 24 deletions src/main/java/org/apache/solr/mcp/server/collection/Dtos.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import java.time.Instant;
import org.jspecify.annotations.Nullable;

/**
* Data Transfer Objects (DTOs) for the Apache Solr MCP Server.
Expand Down Expand Up @@ -95,13 +96,13 @@ record SolrMetrics(
* Cache utilization statistics for query result, document, and filter caches
* (may be null)
*/
CacheStats cacheStats,
@Nullable CacheStats cacheStats,

/**
* Request handler performance metrics for select and update operations (may be
* null)
*/
HandlerStats handlerStats,
@Nullable HandlerStats handlerStats,

/** Timestamp when these metrics were collected, formatted as ISO 8601 */
@JsonFormat(shape = JsonFormat.Shape.STRING) Instant timestamp) {
Expand Down Expand Up @@ -139,13 +140,13 @@ record SolrMetrics(
@JsonInclude(JsonInclude.Include.NON_NULL)
record IndexStats(
/** Total number of documents in the index (excluding deleted documents) */
Integer numDocs,
@Nullable Integer numDocs,

/**
* Number of Lucene segments in the index (lower numbers generally indicate
* better performance)
*/
Integer segmentCount) {
@Nullable Integer segmentCount) {
}

/**
Expand Down Expand Up @@ -223,13 +224,13 @@ record QueryStats(
@JsonInclude(JsonInclude.Include.NON_NULL)
record CacheStats(
/** Performance metrics for the query result cache */
CacheInfo queryResultCache,
@Nullable CacheInfo queryResultCache,

/** Performance metrics for the document cache */
CacheInfo documentCache,
@Nullable CacheInfo documentCache,

/** Performance metrics for the filter cache */
CacheInfo filterCache) {
@Nullable CacheInfo filterCache) {
}

/**
Expand Down Expand Up @@ -262,28 +263,28 @@ record CacheStats(
@JsonInclude(JsonInclude.Include.NON_NULL)
record CacheInfo(
/** Total number of cache lookup requests */
Long lookups,
@Nullable Long lookups,

/** Number of successful cache hits */
Long hits,
@Nullable Long hits,

/**
* Cache hit ratio (hits/lookups) - higher values indicate better cache
* performance
*/
Float hitratio,
@Nullable Float hitratio,

/** Number of new entries added to the cache */
Long inserts,
@Nullable Long inserts,

/**
* Number of entries removed due to cache size limits (indicates memory
* pressure)
*/
Long evictions,
@Nullable Long evictions,

/** Current number of entries stored in the cache */
Long size) {
@Nullable Long size) {
}

/**
Expand Down Expand Up @@ -317,10 +318,10 @@ record CacheInfo(
@JsonInclude(JsonInclude.Include.NON_NULL)
record HandlerStats(
/** Performance metrics for the search/select request handler */
HandlerInfo selectHandler,
@Nullable HandlerInfo selectHandler,

/** Performance metrics for the document update request handler */
HandlerInfo updateHandler) {
@Nullable HandlerInfo updateHandler) {
}

/**
Expand Down Expand Up @@ -352,22 +353,22 @@ record HandlerStats(
@JsonInclude(JsonInclude.Include.NON_NULL)
record HandlerInfo(
/** Total number of requests processed by this handler */
Long requests,
@Nullable Long requests,

/** Number of requests that resulted in errors */
Long errors,
@Nullable Long errors,

/** Number of requests that exceeded timeout limits */
Long timeouts,
@Nullable Long timeouts,

/** Cumulative time spent processing all requests (milliseconds) */
Long totalTime,
@Nullable Long totalTime,

/** Average time per request in milliseconds */
Float avgTimePerRequest,
@Nullable Float avgTimePerRequest,

/** Average throughput in requests per second */
Float avgRequestsPerSecond) {
@Nullable Float avgRequestsPerSecond) {
}

/**
Expand Down Expand Up @@ -412,13 +413,13 @@ record SolrHealthStatus(
boolean isHealthy,

/** Detailed error message when isHealthy is false, null when healthy */
String errorMessage,
@Nullable String errorMessage,

/** Response time in milliseconds for the health check ping request */
Long responseTime,
@Nullable Long responseTime,

/** Total number of documents currently indexed in the collection */
Long totalDocuments,
@Nullable Long totalDocuments,

/** Timestamp when this health check was performed, formatted as ISO 8601 */
@JsonFormat(shape = JsonFormat.Shape.STRING) Instant lastChecked,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@NullMarked
package org.apache.solr.mcp.server.collection;

import org.jspecify.annotations.NullMarked;
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.apache.solr.common.SolrException;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SimpleOrderedMap;
import org.jspecify.annotations.Nullable;
import org.springframework.http.MediaType;

/**
Expand Down Expand Up @@ -102,7 +103,7 @@ private SimpleOrderedMap<Object> toNamedList(JsonNode objectNode) {
return result;
}

private Object convertValue(JsonNode node) {
private @Nullable Object convertValue(JsonNode node) {
if (node.isNull())
return null;
if (node.isBoolean())
Expand Down
20 changes: 20 additions & 0 deletions src/main/java/org/apache/solr/mcp/server/config/package-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@NullMarked
package org.apache.solr.mcp.server.config;

import org.jspecify.annotations.NullMarked;
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@NullMarked
package org.apache.solr.mcp.server.indexing.documentcreator;

import org.jspecify.annotations.NullMarked;
Loading