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.server.mcp.v20260728; 018 019/** 020 * Binding-owned MCP {@code subscriptions/listen} (SEP-2575) configuration for a v2 {@link McpRevision}. 021 * 022 * <p> 023 * Mirrors {@link McpMrtrConfig}/{@link McpCacheConfig}'s exact placement precedent: {@code subscriptions/listen} 024 * is a {@code 2026-07-28}-only feature, so its configuration lives here, on the v2 adapter, never on the 025 * revision-neutral {@code org.apache.juneau.rest.server.mcp.McpServerConfig}. 026 * 027 * <p> 028 * <b>Mutable during setup, effectively immutable once published.</b> Builder-less mutable-setup bean, 029 * following the same contract as {@link McpMrtrConfig}: configure fully before handing to a binding, never 030 * mutate afterward. 031 * 032 * <p> 033 * Unlike the {@code org.apache.juneau.rest.server.mcp.McpSubscriptionBroker} singleton (which holds live 034 * per-connection state), this type is knobs-only — every field is a tunable numeric limit with no embedded 035 * broker reference. It is one of {@link McpOptions}'s nested configs (see {@link McpOptions#getSubscriptions()}), 036 * memoized once per binding along with the rest of {@link McpOptions}; the broker itself is derived and 037 * memoized separately, sized from this config's {@link #getQueueSize() queueSize} (see 038 * {@link McpOptions#resolveSubscriptionBroker()}). 039 */ 040public class McpSubscriptionsConfig { 041 042 /** Default cap on concurrently open {@code subscriptions/listen} streams per broker. */ 043 public static final int DEFAULT_MAX_CONCURRENT_SUBSCRIPTIONS = 256; 044 045 /** Default per-subscription drop-oldest queue bound; matches {@code SseBroadcaster.DEFAULT_QUEUE_SIZE}. */ 046 public static final int DEFAULT_QUEUE_SIZE = 1024; 047 048 /** Default heartbeat/keepalive interval in milliseconds. */ 049 public static final long DEFAULT_HEARTBEAT_INTERVAL_MS = 15_000L; 050 051 /** Default idle timeout in milliseconds; {@code 0} disables idle-timeout reaping. */ 052 public static final long DEFAULT_IDLE_TIMEOUT_MS = 0L; 053 054 private int maxConcurrentSubscriptions = DEFAULT_MAX_CONCURRENT_SUBSCRIPTIONS; 055 private int queueSize = DEFAULT_QUEUE_SIZE; 056 private long heartbeatIntervalMs = DEFAULT_HEARTBEAT_INTERVAL_MS; 057 private long idleTimeoutMs = DEFAULT_IDLE_TIMEOUT_MS; 058 059 /** 060 * The cap on concurrently open {@code subscriptions/listen} streams per broker. 061 * 062 * @return The cap. Always {@code > 0}. 063 */ 064 public int getMaxConcurrentSubscriptions() { 065 return maxConcurrentSubscriptions; 066 } 067 068 /** 069 * Sets the cap on concurrently open {@code subscriptions/listen} streams per broker. 070 * 071 * @param value The new value. Must be {@code > 0}. 072 * @return This object. 073 * @throws IllegalArgumentException If {@code value} is not {@code > 0}. 074 */ 075 public McpSubscriptionsConfig setMaxConcurrentSubscriptions(int value) { 076 if (value <= 0) 077 throw new IllegalArgumentException("maxConcurrentSubscriptions " + value + " must be > 0"); 078 maxConcurrentSubscriptions = value; 079 return this; 080 } 081 082 /** 083 * The per-subscription drop-oldest queue bound. 084 * 085 * @return The bound. Always {@code > 0}. 086 */ 087 public int getQueueSize() { 088 return queueSize; 089 } 090 091 /** 092 * Sets the per-subscription drop-oldest queue bound. 093 * 094 * @param value The new value. Must be {@code > 0}. 095 * @return This object. 096 * @throws IllegalArgumentException If {@code value} is not {@code > 0}. 097 */ 098 public McpSubscriptionsConfig setQueueSize(int value) { 099 if (value <= 0) 100 throw new IllegalArgumentException("queueSize " + value + " must be > 0"); 101 queueSize = value; 102 return this; 103 } 104 105 /** 106 * The heartbeat/keepalive interval in milliseconds. 107 * 108 * @return The interval. Always {@code > 0}. 109 */ 110 public long getHeartbeatIntervalMs() { 111 return heartbeatIntervalMs; 112 } 113 114 /** 115 * Sets the heartbeat/keepalive interval. 116 * 117 * @param value The new value. Must be {@code > 0}. 118 * @return This object. 119 * @throws IllegalArgumentException If {@code value} is not {@code > 0}. 120 */ 121 public McpSubscriptionsConfig setHeartbeatIntervalMs(long value) { 122 if (value <= 0) 123 throw new IllegalArgumentException("heartbeatIntervalMs " + value + " must be > 0"); 124 heartbeatIntervalMs = value; 125 return this; 126 } 127 128 /** 129 * The idle timeout in milliseconds. 130 * 131 * @return The timeout. Always {@code >= 0} ({@code 0} means disabled). 132 */ 133 public long getIdleTimeoutMs() { 134 return idleTimeoutMs; 135 } 136 137 /** 138 * Sets the idle timeout. 139 * 140 * <p> 141 * <b>Keep this greater than {@link #getHeartbeatIntervalMs() heartbeatIntervalMs}.</b> A heartbeat 142 * counts as activity, so as long as {@code heartbeatIntervalMs < idleTimeoutMs}, heartbeats alone keep 143 * an otherwise-quiet stream alive indefinitely. Configuring {@code idleTimeoutMs} at or below 144 * {@code heartbeatIntervalMs} defeats that: the watchdog can fire between two heartbeats and silently 145 * reap a perfectly healthy connection. This setter accepts any non-negative value without checking 146 * against the currently-configured heartbeat interval (the two may be set in either order), so this is 147 * a configuration contract, not one this class enforces for you. 148 * 149 * @param value The new value. Must be {@code >= 0} ({@code 0} disables idle-timeout reaping). 150 * @return This object. 151 * @throws IllegalArgumentException If {@code value} is negative. 152 */ 153 public McpSubscriptionsConfig setIdleTimeoutMs(long value) { 154 if (value < 0) 155 throw new IllegalArgumentException("idleTimeoutMs " + value + " must be >= 0"); 156 idleTimeoutMs = value; 157 return this; 158 } 159}