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}