001/* 002 * Copyright 2022-2025 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 com.soklet.converter.ValueConverterRegistry; 020 021import javax.annotation.Nonnull; 022import javax.annotation.Nullable; 023import javax.annotation.concurrent.NotThreadSafe; 024import javax.annotation.concurrent.ThreadSafe; 025import java.util.Optional; 026 027import static java.util.Objects.requireNonNull; 028 029/** 030 * Defines how a Soklet system is configured. 031 * 032 * @author <a href="https://www.revetkn.com">Mark Allen</a> 033 */ 034@ThreadSafe 035public class SokletConfig { 036 @Nonnull 037 private final InstanceProvider instanceProvider; 038 @Nonnull 039 private final ValueConverterRegistry valueConverterRegistry; 040 @Nonnull 041 private final RequestBodyMarshaler requestBodyMarshaler; 042 @Nonnull 043 private final ResourceMethodResolver resourceMethodResolver; 044 @Nonnull 045 private final ResourceMethodParameterProvider resourceMethodParameterProvider; 046 @Nonnull 047 private final ResponseMarshaler responseMarshaler; 048 @Nonnull 049 private final LifecycleInterceptor lifecycleInterceptor; 050 @Nonnull 051 private final CorsAuthorizer corsAuthorizer; 052 @Nonnull 053 private final Server server; 054 @Nullable 055 private final ServerSentEventServer serverSentEventServer; 056 057 /** 058 * Vends a configuration builder for the given server. 059 * 060 * @param server the server necessary for construction 061 * @return a builder for {@link SokletConfig} instances 062 */ 063 @Nonnull 064 public static Builder withServer(@Nonnull Server server) { 065 requireNonNull(server); 066 return new Builder(server); 067 } 068 069 /** 070 * Vends a configuration builder with mock servers, suitable for unit and integration testing. 071 * 072 * @return a builder for {@link SokletConfig} instances 073 */ 074 @Nonnull 075 public static Builder forTesting() { 076 return new Builder(new Soklet.MockServer()).serverSentEventServer(new Soklet.MockServerSentEventServer()); 077 } 078 079 protected SokletConfig(@Nonnull Builder builder) { 080 requireNonNull(builder); 081 082 this.server = builder.server; 083 this.serverSentEventServer = builder.serverSentEventServer; 084 this.instanceProvider = builder.instanceProvider != null ? builder.instanceProvider : InstanceProvider.withDefaults(); 085 this.valueConverterRegistry = builder.valueConverterRegistry != null ? builder.valueConverterRegistry : ValueConverterRegistry.sharedInstance(); 086 this.requestBodyMarshaler = builder.requestBodyMarshaler != null ? builder.requestBodyMarshaler : RequestBodyMarshaler.withValueConverterRegistry(getValueConverterRegistry()); 087 this.resourceMethodResolver = builder.resourceMethodResolver != null ? builder.resourceMethodResolver : ResourceMethodResolver.withDefaults(); 088 this.resourceMethodParameterProvider = builder.resourceMethodParameterProvider != null ? builder.resourceMethodParameterProvider : ResourceMethodParameterProvider.with(getInstanceProvider(), getValueConverterRegistry(), getRequestBodyMarshaler()); 089 this.responseMarshaler = builder.responseMarshaler != null ? builder.responseMarshaler : ResponseMarshaler.withDefaults(); 090 this.lifecycleInterceptor = builder.lifecycleInterceptor != null ? builder.lifecycleInterceptor : LifecycleInterceptor.withDefaults(); 091 this.corsAuthorizer = builder.corsAuthorizer != null ? builder.corsAuthorizer : CorsAuthorizer.withRejectAllPolicy(); 092 } 093 094 /** 095 * Vends a mutable copy of this instance's configuration, suitable for building new instances. 096 * 097 * @return a mutable copy of this instance's configuration 098 */ 099 @Nonnull 100 public Copier copy() { 101 return new Copier(this); 102 } 103 104 /** 105 * How Soklet will perform <a href="https://www.soklet.com/docs/instance-creation">instance creation</a>. 106 * 107 * @return the instance responsible for instance creation 108 */ 109 @Nonnull 110 public InstanceProvider getInstanceProvider() { 111 return this.instanceProvider; 112 } 113 114 /** 115 * How Soklet will perform <a href="https://www.soklet.com/docs/value-conversions">conversions from one Java type to another</a>, like a {@link String} to a {@link java.time.LocalDate}. 116 * 117 * @return the instance responsible for value conversions 118 */ 119 @Nonnull 120 public ValueConverterRegistry getValueConverterRegistry() { 121 return this.valueConverterRegistry; 122 } 123 124 /** 125 * How Soklet will <a href="https://www.soklet.com/docs/request-handling#request-body">marshal request bodies to Java types</a>. 126 * 127 * @return the instance responsible for request body marshaling 128 */ 129 @Nonnull 130 public RequestBodyMarshaler getRequestBodyMarshaler() { 131 return this.requestBodyMarshaler; 132 } 133 134 /** 135 * How Soklet performs <a href="https://www.soklet.com/docs/request-handling#resource-method-resolution"><em>Resource Method</em> resolution</a> (experts only!) 136 * 137 * @return the instance responsible for <em>Resource Method</em> resolution 138 */ 139 @Nonnull 140 public ResourceMethodResolver getResourceMethodResolver() { 141 return this.resourceMethodResolver; 142 } 143 144 /** 145 * How Soklet performs <a href="https://www.soklet.com/docs/request-handling#resource-method-parameter-injection"><em>Resource Method</em> parameter injection</a> (experts only!) 146 * 147 * @return the instance responsible for <em>Resource Method</em> parameter injection 148 */ 149 @Nonnull 150 public ResourceMethodParameterProvider getResourceMethodParameterProvider() { 151 return this.resourceMethodParameterProvider; 152 } 153 154 /** 155 * How Soklet will <a href="https://www.soklet.com/docs/response-writing">marshal response bodies to bytes suitable for transmission over the wire</a>. 156 * 157 * @return the instance responsible for response body marshaling 158 */ 159 @Nonnull 160 public ResponseMarshaler getResponseMarshaler() { 161 return this.responseMarshaler; 162 } 163 164 /** 165 * How Soklet will <a href="https://www.soklet.com/docs/request-lifecycle">perform custom behavior during server and request lifecycle events</a>. 166 * 167 * @return the instance responsible for performing lifecycle event customization 168 */ 169 @Nonnull 170 public LifecycleInterceptor getLifecycleInterceptor() { 171 return this.lifecycleInterceptor; 172 } 173 174 /** 175 * How Soklet handles <a href="https://www.soklet.com/docs/cors">Cross-Origin Resource Sharing (CORS)</a>. 176 * 177 * @return the instance responsible for CORS-related processing 178 */ 179 @Nonnull 180 public CorsAuthorizer getCorsAuthorizer() { 181 return this.corsAuthorizer; 182 } 183 184 /** 185 * The server managed by Soklet. 186 * 187 * @return the server instance 188 */ 189 @Nonnull 190 public Server getServer() { 191 return this.server; 192 } 193 194 /** 195 * The SSE server managed by Soklet, if configured. 196 * 197 * @return the SSE server instance, or {@link Optional#empty()} is none was configured 198 */ 199 @Nonnull 200 public Optional<ServerSentEventServer> getServerSentEventServer() { 201 return Optional.ofNullable(this.serverSentEventServer); 202 } 203 204 /** 205 * Builder used to construct instances of {@link SokletConfig}. 206 * <p> 207 * Instances are created by invoking {@link SokletConfig#withServer(Server)} or {@link SokletConfig#forTesting()}. 208 * <p> 209 * This class is intended for use by a single thread. 210 * 211 * @author <a href="https://www.revetkn.com">Mark Allen</a> 212 */ 213 @NotThreadSafe 214 public static class Builder { 215 @Nonnull 216 private Server server; 217 @Nullable 218 private ServerSentEventServer serverSentEventServer; 219 @Nullable 220 private InstanceProvider instanceProvider; 221 @Nullable 222 private ValueConverterRegistry valueConverterRegistry; 223 @Nullable 224 private RequestBodyMarshaler requestBodyMarshaler; 225 @Nullable 226 private ResourceMethodResolver resourceMethodResolver; 227 @Nullable 228 private ResourceMethodParameterProvider resourceMethodParameterProvider; 229 @Nullable 230 private ResponseMarshaler responseMarshaler; 231 @Nullable 232 private LifecycleInterceptor lifecycleInterceptor; 233 @Nullable 234 private CorsAuthorizer corsAuthorizer; 235 236 @Nonnull 237 protected Builder(@Nonnull Server server) { 238 requireNonNull(server); 239 this.server = server; 240 } 241 242 @Nonnull 243 public Builder server(@Nonnull Server server) { 244 requireNonNull(server); 245 this.server = server; 246 return this; 247 } 248 249 @Nonnull 250 public Builder serverSentEventServer(@Nullable ServerSentEventServer serverSentEventServer) { 251 this.serverSentEventServer = serverSentEventServer; 252 return this; 253 } 254 255 @Nonnull 256 public Builder instanceProvider(@Nullable InstanceProvider instanceProvider) { 257 this.instanceProvider = instanceProvider; 258 return this; 259 } 260 261 @Nonnull 262 public Builder valueConverterRegistry(@Nullable ValueConverterRegistry valueConverterRegistry) { 263 this.valueConverterRegistry = valueConverterRegistry; 264 return this; 265 } 266 267 @Nonnull 268 public Builder requestBodyMarshaler(@Nullable RequestBodyMarshaler requestBodyMarshaler) { 269 this.requestBodyMarshaler = requestBodyMarshaler; 270 return this; 271 } 272 273 @Nonnull 274 public Builder resourceMethodResolver(@Nullable ResourceMethodResolver resourceMethodResolver) { 275 this.resourceMethodResolver = resourceMethodResolver; 276 return this; 277 } 278 279 @Nonnull 280 public Builder resourceMethodParameterProvider(@Nullable ResourceMethodParameterProvider resourceMethodParameterProvider) { 281 this.resourceMethodParameterProvider = resourceMethodParameterProvider; 282 return this; 283 } 284 285 @Nonnull 286 public Builder responseMarshaler(@Nullable ResponseMarshaler responseMarshaler) { 287 this.responseMarshaler = responseMarshaler; 288 return this; 289 } 290 291 @Nonnull 292 public Builder lifecycleInterceptor(@Nullable LifecycleInterceptor lifecycleInterceptor) { 293 this.lifecycleInterceptor = lifecycleInterceptor; 294 return this; 295 } 296 297 @Nonnull 298 public Builder corsAuthorizer(@Nullable CorsAuthorizer corsAuthorizer) { 299 this.corsAuthorizer = corsAuthorizer; 300 return this; 301 } 302 303 @Nonnull 304 public SokletConfig build() { 305 return new SokletConfig(this); 306 } 307 } 308 309 /** 310 * Builder used to copy instances of {@link SokletConfig}. 311 * <p> 312 * Instances are created by invoking {@link SokletConfig#copy()}. 313 * <p> 314 * This class is intended for use by a single thread. 315 * 316 * @author <a href="https://www.revetkn.com">Mark Allen</a> 317 */ 318 @NotThreadSafe 319 public static class Copier { 320 @Nonnull 321 private final Builder builder; 322 323 Copier(@Nonnull SokletConfig sokletConfig) { 324 requireNonNull(sokletConfig); 325 326 this.builder = new Builder(sokletConfig.getServer()) 327 .serverSentEventServer(sokletConfig.getServerSentEventServer().orElse(null)) 328 .instanceProvider(sokletConfig.getInstanceProvider()) 329 .valueConverterRegistry(sokletConfig.valueConverterRegistry) 330 .requestBodyMarshaler(sokletConfig.requestBodyMarshaler) 331 .resourceMethodResolver(sokletConfig.resourceMethodResolver) 332 .resourceMethodParameterProvider(sokletConfig.resourceMethodParameterProvider) 333 .responseMarshaler(sokletConfig.responseMarshaler) 334 .lifecycleInterceptor(sokletConfig.lifecycleInterceptor) 335 .corsAuthorizer(sokletConfig.corsAuthorizer); 336 } 337 338 @Nonnull 339 public Copier server(@Nonnull Server server) { 340 requireNonNull(server); 341 this.builder.server = server; 342 return this; 343 } 344 345 @Nonnull 346 public Copier serverSentEventServer(@Nullable ServerSentEventServer serverSentEventServer) { 347 this.builder.serverSentEventServer = serverSentEventServer; 348 return this; 349 } 350 351 @Nonnull 352 public Copier instanceProvider(@Nullable InstanceProvider instanceProvider) { 353 this.builder.instanceProvider = instanceProvider; 354 return this; 355 } 356 357 @Nonnull 358 public Copier valueConverterRegistry(@Nullable ValueConverterRegistry valueConverterRegistry) { 359 this.builder.valueConverterRegistry = valueConverterRegistry; 360 return this; 361 } 362 363 @Nonnull 364 public Copier requestBodyMarshaler(@Nullable RequestBodyMarshaler requestBodyMarshaler) { 365 this.builder.requestBodyMarshaler = requestBodyMarshaler; 366 return this; 367 } 368 369 @Nonnull 370 public Copier resourceMethodResolver(@Nullable ResourceMethodResolver resourceMethodResolver) { 371 this.builder.resourceMethodResolver = resourceMethodResolver; 372 return this; 373 } 374 375 @Nonnull 376 public Copier resourceMethodParameterProvider(@Nullable ResourceMethodParameterProvider resourceMethodParameterProvider) { 377 this.builder.resourceMethodParameterProvider = resourceMethodParameterProvider; 378 return this; 379 } 380 381 @Nonnull 382 public Copier responseMarshaler(@Nullable ResponseMarshaler responseMarshaler) { 383 this.builder.responseMarshaler = responseMarshaler; 384 return this; 385 } 386 387 @Nonnull 388 public Copier lifecycleInterceptor(@Nullable LifecycleInterceptor lifecycleInterceptor) { 389 this.builder.lifecycleInterceptor = lifecycleInterceptor; 390 return this; 391 } 392 393 @Nonnull 394 public Copier corsAuthorizer(@Nullable CorsAuthorizer corsAuthorizer) { 395 this.builder.corsAuthorizer = corsAuthorizer; 396 return this; 397 } 398 399 @Nonnull 400 public SokletConfig finish() { 401 return this.builder.build(); 402 } 403 } 404}