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}