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