Skip to content

Commit f429a45

Browse files
committed
cleanup and cache default mapper/validator
1 parent 6eb70cf commit f429a45

24 files changed

+226
-120
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package io.modelcontextprotocol.json;
2+
3+
import java.util.ServiceLoader;
4+
import java.util.concurrent.atomic.AtomicReference;
5+
import java.util.stream.Stream;
6+
7+
/**
8+
* Utility class for creating a default {@link McpJsonMapper} instance. This class
9+
* provides a single method to create a default mapper using the {@link ServiceLoader}
10+
* mechanism.
11+
*/
12+
final class McpJsonInternal {
13+
14+
private static McpJsonMapper defaultJsonMapper = null;
15+
16+
/**
17+
* Returns the cached default {@link McpJsonMapper} instance. If the default mapper
18+
* has not been created yet, it will be initialized using the
19+
* {@link #createDefaultMapper()} method.
20+
* @return the default {@link McpJsonMapper} instance
21+
* @throws IllegalStateException if no default {@link McpJsonMapper} implementation is
22+
* found
23+
*/
24+
static McpJsonMapper getDefaultMapper() {
25+
if (defaultJsonMapper == null) {
26+
defaultJsonMapper = McpJsonInternal.createDefaultMapper();
27+
}
28+
return defaultJsonMapper;
29+
}
30+
31+
/**
32+
* Creates a default {@link McpJsonMapper} instance using the {@link ServiceLoader}
33+
* mechanism. The default mapper is resolved by loading the first available
34+
* {@link McpJsonMapperSupplier} implementation on the classpath.
35+
* @return the default {@link McpJsonMapper} instance
36+
* @throws IllegalStateException if no default {@link McpJsonMapper} implementation is
37+
* found
38+
*/
39+
static McpJsonMapper createDefaultMapper() {
40+
AtomicReference<IllegalStateException> ex = new AtomicReference<>();
41+
return ServiceLoader.load(McpJsonMapperSupplier.class).stream().flatMap(p -> {
42+
try {
43+
McpJsonMapperSupplier supplier = p.get();
44+
return Stream.ofNullable(supplier);
45+
}
46+
catch (Exception e) {
47+
addException(ex, e);
48+
return Stream.empty();
49+
}
50+
}).flatMap(jsonMapperSupplier -> {
51+
try {
52+
return Stream.ofNullable(jsonMapperSupplier.get());
53+
}
54+
catch (Exception e) {
55+
addException(ex, e);
56+
return Stream.empty();
57+
}
58+
}).findFirst().orElseThrow(() -> {
59+
if (ex.get() != null) {
60+
return ex.get();
61+
}
62+
else {
63+
return new IllegalStateException("No default McpJsonMapper implementation found");
64+
}
65+
});
66+
}
67+
68+
private static void addException(AtomicReference<IllegalStateException> ref, Exception toAdd) {
69+
ref.updateAndGet(existing -> {
70+
if (existing == null) {
71+
return new IllegalStateException("Failed to initialize default McpJsonMapper", toAdd);
72+
}
73+
else {
74+
existing.addSuppressed(toAdd);
75+
return existing;
76+
}
77+
});
78+
}
79+
80+
}

mcp-json/src/main/java/io/modelcontextprotocol/json/McpJsonMapper.java

Lines changed: 11 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
package io.modelcontextprotocol.json;
22

33
import java.io.IOException;
4-
import java.util.ServiceLoader;
5-
import java.util.concurrent.atomic.AtomicReference;
6-
import java.util.stream.Stream;
74

85
/**
96
* Abstraction for JSON serialization/deserialization to decouple the SDK from any
@@ -87,50 +84,23 @@ public interface McpJsonMapper {
8784
byte[] writeValueAsBytes(Object value) throws IOException;
8885

8986
/**
90-
* Resolves the default {@link McpJsonMapper}.
87+
* Returns the default {@link McpJsonMapper}.
9188
* @return The default {@link McpJsonMapper}
9289
* @throws IllegalStateException If no {@link McpJsonMapper} implementation exists on
9390
* the classpath.
9491
*/
95-
static McpJsonMapper createDefault() {
96-
AtomicReference<IllegalStateException> ex = new AtomicReference<>();
97-
return ServiceLoader.load(McpJsonMapperSupplier.class).stream().flatMap(p -> {
98-
try {
99-
McpJsonMapperSupplier supplier = p.get();
100-
return Stream.ofNullable(supplier);
101-
}
102-
catch (Exception e) {
103-
addException(ex, e);
104-
return Stream.empty();
105-
}
106-
}).flatMap(jsonMapperSupplier -> {
107-
try {
108-
return Stream.of(jsonMapperSupplier.get());
109-
}
110-
catch (Exception e) {
111-
addException(ex, e);
112-
return Stream.empty();
113-
}
114-
}).findFirst().orElseThrow(() -> {
115-
if (ex.get() != null) {
116-
return ex.get();
117-
}
118-
else {
119-
return new IllegalStateException("No default McpJsonMapper implementation found");
120-
}
121-
});
92+
static McpJsonMapper getDefault() {
93+
return McpJsonInternal.getDefaultMapper();
12294
}
12395

124-
private static void addException(AtomicReference<IllegalStateException> ref, Exception toAdd) {
125-
ref.updateAndGet(existing -> {
126-
if (existing == null) {
127-
return new IllegalStateException("Failed to initialize default McpJsonMapper", toAdd);
128-
}
129-
else {
130-
existing.addSuppressed(toAdd);
131-
return existing;
132-
}
133-
});
96+
/**
97+
* Creates a new default {@link McpJsonMapper}.
98+
* @return The default {@link McpJsonMapper}
99+
* @throws IllegalStateException If no {@link McpJsonMapper} implementation exists on
100+
* the classpath.
101+
*/
102+
static McpJsonMapper createDefault() {
103+
return McpJsonInternal.createDefaultMapper();
134104
}
135105

136106
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package io.modelcontextprotocol.json.schema;
2+
3+
import java.util.ServiceLoader;
4+
import java.util.concurrent.atomic.AtomicReference;
5+
import java.util.stream.Stream;
6+
7+
/**
8+
* Internal utility class for creating a default {@link JsonSchemaValidator} instance.
9+
* This class uses the {@link ServiceLoader} to discover and instantiate a
10+
* {@link JsonSchemaValidatorSupplier} implementation.
11+
*/
12+
final class JsonSchemaInternal {
13+
14+
private static JsonSchemaValidator defaultValidator = null;
15+
16+
/**
17+
* Returns the default {@link JsonSchemaValidator} instance. If the default validator
18+
* has not been initialized, it will be created using the {@link ServiceLoader} to
19+
* discover and instantiate a {@link JsonSchemaValidatorSupplier} implementation.
20+
* @return The default {@link JsonSchemaValidator} instance.
21+
* @throws IllegalStateException If no {@link JsonSchemaValidatorSupplier}
22+
* implementation exists on the classpath or if an error occurs during instantiation.
23+
*/
24+
static JsonSchemaValidator getDefaultValidator() {
25+
if (defaultValidator == null) {
26+
defaultValidator = JsonSchemaInternal.getDefaultValidator();
27+
}
28+
return defaultValidator;
29+
}
30+
31+
/**
32+
* Creates a default {@link JsonSchemaValidator} instance by loading a
33+
* {@link JsonSchemaValidatorSupplier} implementation using the {@link ServiceLoader}.
34+
* @return A default {@link JsonSchemaValidator} instance.
35+
* @throws IllegalStateException If no {@link JsonSchemaValidatorSupplier}
36+
* implementation is found or if an error occurs during instantiation.
37+
*/
38+
static JsonSchemaValidator createDefaultValidator() {
39+
AtomicReference<IllegalStateException> ex = new AtomicReference<>();
40+
return ServiceLoader.load(JsonSchemaValidatorSupplier.class).stream().flatMap(p -> {
41+
try {
42+
JsonSchemaValidatorSupplier supplier = p.get();
43+
return Stream.ofNullable(supplier);
44+
}
45+
catch (Exception e) {
46+
addException(ex, e);
47+
return Stream.empty();
48+
}
49+
}).flatMap(jsonMapperSupplier -> {
50+
try {
51+
return Stream.of(jsonMapperSupplier.get());
52+
}
53+
catch (Exception e) {
54+
addException(ex, e);
55+
return Stream.empty();
56+
}
57+
}).findFirst().orElseThrow(() -> {
58+
if (ex.get() != null) {
59+
return ex.get();
60+
}
61+
else {
62+
return new IllegalStateException("No default JsonSchemaValidatorSupplier implementation found");
63+
}
64+
});
65+
}
66+
67+
private static void addException(AtomicReference<IllegalStateException> ref, Exception toAdd) {
68+
ref.updateAndGet(existing -> {
69+
if (existing == null) {
70+
return new IllegalStateException("Failed to initialize default JsonSchemaValidatorSupplier", toAdd);
71+
}
72+
else {
73+
existing.addSuppressed(toAdd);
74+
return existing;
75+
}
76+
});
77+
}
78+
79+
}

mcp-json/src/main/java/io/modelcontextprotocol/json/schema/JsonSchemaValidator.java

Lines changed: 10 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -46,50 +46,23 @@ public static ValidationResponse asInvalid(String message) {
4646
ValidationResponse validate(Map<String, Object> schema, Map<String, Object> structuredContent);
4747

4848
/**
49-
* Resolves the default {@link JsonSchemaValidator}.
49+
* Creates the default {@link JsonSchemaValidator}.
5050
* @return The default {@link JsonSchemaValidator}
5151
* @throws IllegalStateException If no {@link JsonSchemaValidator} implementation
5252
* exists on the classpath.
5353
*/
5454
static JsonSchemaValidator createDefault() {
55-
AtomicReference<IllegalStateException> ex = new AtomicReference<>();
56-
return ServiceLoader.load(JsonSchemaValidatorSupplier.class).stream().flatMap(p -> {
57-
try {
58-
JsonSchemaValidatorSupplier supplier = p.get();
59-
return Stream.ofNullable(supplier);
60-
}
61-
catch (Exception e) {
62-
addException(ex, e);
63-
return Stream.empty();
64-
}
65-
}).flatMap(jsonMapperSupplier -> {
66-
try {
67-
return Stream.of(jsonMapperSupplier.get());
68-
}
69-
catch (Exception e) {
70-
addException(ex, e);
71-
return Stream.empty();
72-
}
73-
}).findFirst().orElseThrow(() -> {
74-
if (ex.get() != null) {
75-
return ex.get();
76-
}
77-
else {
78-
return new IllegalStateException("No default JsonSchemaValidatorSupplier implementation found");
79-
}
80-
});
55+
return JsonSchemaInternal.createDefaultValidator();
8156
}
8257

83-
private static void addException(AtomicReference<IllegalStateException> ref, Exception toAdd) {
84-
ref.updateAndGet(existing -> {
85-
if (existing == null) {
86-
return new IllegalStateException("Failed to initialize default JsonSchemaValidatorSupplier", toAdd);
87-
}
88-
else {
89-
existing.addSuppressed(toAdd);
90-
return existing;
91-
}
92-
});
58+
/**
59+
* Returns the default {@link JsonSchemaValidator}.
60+
* @return The default {@link JsonSchemaValidator}
61+
* @throws IllegalStateException If no {@link JsonSchemaValidator} implementation
62+
* exists on the classpath.
63+
*/
64+
static JsonSchemaValidator getDefault() {
65+
return JsonSchemaInternal.getDefaultValidator();
9366
}
9467

9568
}

mcp-json/src/main/java/io/modelcontextprotocol/json/schema/JsonSchemaValidatorSupplier.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22

33
import java.util.function.Supplier;
44

5+
/**
6+
* A supplier interface that provides a {@link JsonSchemaValidator} instance.
7+
* Implementations of this interface are expected to return a new or cached instance of
8+
* {@link JsonSchemaValidator} when {@link #get()} is invoked.
9+
*
10+
* @see JsonSchemaValidator
11+
* @see Supplier
12+
*/
513
public interface JsonSchemaValidatorSupplier extends Supplier<JsonSchemaValidator> {
614

715
}

mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/client/transport/WebClientStreamableHttpTransport.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -554,7 +554,7 @@ public Builder openConnectionOnStartup(boolean openConnectionOnStartup) {
554554
* @return a new instance of {@link WebClientStreamableHttpTransport}
555555
*/
556556
public WebClientStreamableHttpTransport build() {
557-
return new WebClientStreamableHttpTransport(jsonMapper == null ? McpJsonMapper.createDefault() : jsonMapper,
557+
return new WebClientStreamableHttpTransport(jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper,
558558
webClientBuilder, endpoint, resumableStreams, openConnectionOnStartup);
559559
}
560560

mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/client/transport/WebFluxSseClientTransport.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ public Builder jsonMapper(McpJsonMapper jsonMapper) {
404404
*/
405405
public WebFluxSseClientTransport build() {
406406
return new WebFluxSseClientTransport(webClientBuilder,
407-
jsonMapper == null ? McpJsonMapper.createDefault() : jsonMapper, sseEndpoint);
407+
jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper, sseEndpoint);
408408
}
409409

410410
}

mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxSseServerTransportProvider.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -506,9 +506,8 @@ public Builder contextExtractor(McpTransportContextExtractor<ServerRequest> cont
506506
*/
507507
public WebFluxSseServerTransportProvider build() {
508508
Assert.notNull(messageEndpoint, "Message endpoint must be set");
509-
return new WebFluxSseServerTransportProvider(
510-
jsonMapper == null ? McpJsonMapper.createDefault() : jsonMapper, baseUrl, messageEndpoint,
511-
sseEndpoint, keepAliveInterval, contextExtractor);
509+
return new WebFluxSseServerTransportProvider(jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper,
510+
baseUrl, messageEndpoint, sseEndpoint, keepAliveInterval, contextExtractor);
512511
}
513512

514513
}

mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStatelessServerTransport.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ public Builder contextExtractor(McpTransportContextExtractor<ServerRequest> cont
213213
*/
214214
public WebFluxStatelessServerTransport build() {
215215
Assert.notNull(mcpEndpoint, "Message endpoint must be set");
216-
return new WebFluxStatelessServerTransport(jsonMapper == null ? McpJsonMapper.createDefault() : jsonMapper,
216+
return new WebFluxStatelessServerTransport(jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper,
217217
mcpEndpoint, contextExtractor);
218218
}
219219

mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStreamableServerTransportProvider.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,7 @@ public Builder keepAliveInterval(Duration keepAliveInterval) {
485485
public WebFluxStreamableServerTransportProvider build() {
486486
Assert.notNull(mcpEndpoint, "Message endpoint must be set");
487487
return new WebFluxStreamableServerTransportProvider(
488-
jsonMapper == null ? McpJsonMapper.createDefault() : jsonMapper, mcpEndpoint, contextExtractor,
488+
jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper, mcpEndpoint, contextExtractor,
489489
disallowDelete, keepAliveInterval);
490490
}
491491

0 commit comments

Comments
 (0)