001/* 002 * Copyright 2022-2026 Revetware LLC. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package com.soklet; 018 019import org.jspecify.annotations.NonNull; 020 021import javax.annotation.concurrent.NotThreadSafe; 022import javax.annotation.concurrent.ThreadSafe; 023import java.util.Collections; 024import java.util.EnumSet; 025import java.util.Set; 026 027import static java.util.Objects.requireNonNull; 028 029/** 030 * Immutable endpoint-scoped MCP subscription configuration. 031 * <p> 032 * The configured notification types are the subscription-change families the 033 * endpoint may support. The publisher is application-owned and may provide 034 * either process-local or distributed broadcast delivery. This value does not 035 * transfer publisher lifecycle ownership to Soklet. 036 * 037 * <p>Instances intentionally retain reference identity because the publisher 038 * is a live application-owned capability, not immutable value data. Code that 039 * compares configurations should compare notification types and publisher 040 * identity according to its own lifecycle model. 041 * 042 * @author <a href="https://www.revetkn.com">Mark Allen</a> 043 */ 044@ThreadSafe 045public final class McpSubscriptionConfig { 046 @NonNull 047 private final McpSubscriptionEventPublisher eventPublisher; 048 @NonNull 049 private final Set<@NonNull McpSubscriptionNotificationType> notificationTypes; 050 051 /** 052 * Vends a builder primed with its required construction values. 053 * 054 * @param subscriptionEventPublisher application-owned broadcast publisher 055 * @param subscriptionNotificationTypes nonempty supported notification families 056 * @return a subscription-configuration builder 057 * @throws NullPointerException if an argument or notification type is null 058 * @throws IllegalArgumentException if {@code subscriptionNotificationTypes} is empty 059 */ 060 @NonNull 061 public static Builder withEventPublisherAndNotificationTypes( 062 @NonNull McpSubscriptionEventPublisher subscriptionEventPublisher, 063 @NonNull Set<@NonNull McpSubscriptionNotificationType> 064 subscriptionNotificationTypes) { 065 return new Builder(subscriptionEventPublisher, subscriptionNotificationTypes); 066 } 067 068 private McpSubscriptionConfig(@NonNull Builder builder) { 069 requireNonNull(builder); 070 this.eventPublisher = builder.eventPublisher; 071 this.notificationTypes = requireNotificationTypes(builder.notificationTypes); 072 } 073 074 @NonNull 075 private static Set<@NonNull McpSubscriptionNotificationType> requireNotificationTypes( 076 @NonNull Set<@NonNull McpSubscriptionNotificationType> 077 subscriptionNotificationTypes) { 078 EnumSet<McpSubscriptionNotificationType> copiedTypes = EnumSet.noneOf( 079 McpSubscriptionNotificationType.class); 080 for (McpSubscriptionNotificationType subscriptionNotificationType 081 : requireNonNull(subscriptionNotificationTypes)) 082 copiedTypes.add(requireNonNull(subscriptionNotificationType)); 083 if (copiedTypes.isEmpty()) 084 throw new IllegalArgumentException( 085 "At least one MCP subscription notification type must be configured."); 086 return Collections.unmodifiableSet(copiedTypes); 087 } 088 089 /** 090 * Returns the application-owned broadcast publisher. 091 * 092 * @return event publisher 093 */ 094 @NonNull 095 public McpSubscriptionEventPublisher getEventPublisher() { 096 return this.eventPublisher; 097 } 098 099 /** 100 * Returns the endpoint's supported subscription-notification families. 101 * 102 * @return immutable nonempty notification-type set in enum declaration order 103 */ 104 @NonNull 105 public Set<@NonNull McpSubscriptionNotificationType> getNotificationTypes() { 106 return this.notificationTypes; 107 } 108 109 /** 110 * Builder for immutable {@link McpSubscriptionConfig} values. 111 * <p> 112 * This class is intended for use by a single thread. 113 * 114 * @author <a href="https://www.revetkn.com">Mark Allen</a> 115 */ 116 @NotThreadSafe 117 public static final class Builder { 118 @NonNull 119 private final McpSubscriptionEventPublisher eventPublisher; 120 @NonNull 121 private Set<@NonNull McpSubscriptionNotificationType> 122 notificationTypes; 123 124 private Builder( 125 @NonNull McpSubscriptionEventPublisher subscriptionEventPublisher, 126 @NonNull Set<@NonNull McpSubscriptionNotificationType> 127 subscriptionNotificationTypes) { 128 this.eventPublisher = requireNonNull(subscriptionEventPublisher); 129 this.notificationTypes = requireNotificationTypes(subscriptionNotificationTypes); 130 } 131 132 /** 133 * Replaces the supported subscription-notification families. 134 * A failed replacement leaves the previous families unchanged. 135 * 136 * @param subscriptionNotificationTypes nonempty notification families 137 * @return this builder 138 * @throws NullPointerException if the set or any item is null 139 * @throws IllegalArgumentException if the set is empty 140 */ 141 @NonNull 142 public Builder notificationTypes( 143 @NonNull Set<@NonNull McpSubscriptionNotificationType> 144 subscriptionNotificationTypes) { 145 this.notificationTypes = requireNotificationTypes(subscriptionNotificationTypes); 146 return this; 147 } 148 149 /** 150 * Builds an immutable endpoint subscription configuration. 151 * 152 * @return subscription configuration 153 * @throws IllegalStateException if no notification type was configured 154 */ 155 @NonNull 156 public McpSubscriptionConfig build() { 157 if (this.notificationTypes.isEmpty()) 158 throw new IllegalStateException( 159 "At least one MCP subscription notification type must be configured."); 160 return new McpSubscriptionConfig(this); 161 } 162 } 163}