001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.rest.client.mcp;
018
019import java.util.*;
020
021import org.apache.juneau.bean.jsonrpc.*;
022
023/**
024 * An opt-in, off-by-default response cache for MCP client results.
025 *
026 * <p>
027 * Keyed by an opaque {@code scope} (a partition key — a revision-specific adapter defines its own scope wire
028 * vocabulary, e.g. the {@code 2026-07-28} adapter's {@code org.apache.juneau.bean.mcp.v20260728.McpCacheScope}
029 * {@code "private"}/{@code "public"} cache-hint values; this neutral core treats the string as fully opaque and
030 * defines no scope constants of its own) plus a {@code key} (typically method + canonical params, computed by
031 * the caller).
032 *
033 * <p>
034 * <b>Stored value shape:</b> this SPI makes no assumption about what a cached {@code value} is — a caller may
035 * store the raw JSON-RPC {@code result} (e.g. via {@link #putResult(String, String, JsonRpcResponse, long)}) or
036 * a typed result bean it has already re-marshaled (e.g. the {@code 2026-07-28} client adapter, which caches the
037 * typed bean returned to its caller). Whatever shape a caller stores under a given {@code key} is exactly the
038 * shape it must expect back from {@link #get(String, String)} for that same {@code key} — this interface does
039 * not normalize or re-marshal on either side.
040 *
041 * <p>
042 * <b>Identity:</b> implementations must return the exact {@code value} instance passed to {@link #put(String,
043 * String, Object, long)} from a subsequent {@link #get(String, String)} for the same partition/key (process-local
044 * implementations naturally satisfy this; an external/serializing store would not, and a caller layering one in
045 * must re-marshal on read to preserve this contract). Cached values may be handed out to more than one caller
046 * concurrently; callers must not mutate a returned value.
047 *
048 * <p>
049 * Implementations are free to be process-local ({@link InMemoryMcpResponseCache}, the built-in default) or backed
050 * by an external store; the SPI itself makes no assumption about storage.
051 *
052 * @since 10.0.0
053 */
054public interface McpResponseCache {
055
056   /**
057    * Looks up a cached value.
058    *
059    * @param scope The cache-scope partition key. Must not be <jk>null</jk>.
060    * @param key The cache key (typically method + canonical params). Must not be <jk>null</jk>.
061    * @return The cached value, or {@link Optional#empty()} if absent or expired.
062    */
063   Optional<Object> get(String scope, String key);
064
065   /**
066    * Stores a value.
067    *
068    * @param scope The cache-scope partition key. Must not be <jk>null</jk>.
069    * @param key The cache key (typically method + canonical params). Must not be <jk>null</jk>.
070    * @param value The value to cache. Must not be <jk>null</jk>.
071    * @param ttlMs The time-to-live in milliseconds. A value {@code <= 0} means the entry never expires.
072    */
073   void put(String scope, String key, Object value, long ttlMs);
074
075   /**
076    * Convenience entry point for an adapter that wants to cache the raw JSON-RPC {@code result} straight off a
077    * {@link JsonRpcResponse}: stores it, or does nothing if the response is <jk>null</jk>, carries a JSON-RPC
078    * {@code error}, or has a <jk>null</jk> {@code result}.
079    *
080    * <p>
081    * Callers never need to check for an error response themselves before caching, since this method never
082    * forwards one to {@link #put(String, String, Object, long)}. Note that this caches the raw wire
083    * {@code result} (e.g. a {@code Map}), not a typed result bean — an adapter that instead caches an
084    * already-re-marshaled typed bean (as the {@code 2026-07-28} client adapter does) should call
085    * {@link #put(String, String, Object, long)} directly instead of this method.
086    *
087    * @param scope The cache-scope partition key. Must not be <jk>null</jk>.
088    * @param key The cache key (typically method + canonical params). Must not be <jk>null</jk>.
089    * @param response The JSON-RPC response to conditionally cache. May be <jk>null</jk>.
090    * @param ttlMs The time-to-live in milliseconds. A value {@code <= 0} means the entry never expires.
091    */
092   default void putResult(String scope, String key, JsonRpcResponse response, long ttlMs) {
093      if (response == null || response.getError() != null)
094         return;
095      var result = response.getResult();
096      if (result == null)
097         return;
098      put(scope, key, result, ttlMs);
099   }
100
101   /**
102    * Removes all cached entries across all scopes.
103    */
104   void clear();
105}