diff --git a/README.md b/README.md index 885e1a66..5e62a82b 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,7 @@ libraryDependencies += "io.github.hakky54" % "sslcontext-kickstart" % "8.3.7" - [Global SSL configuration](#global-ssl-configuration) - [Logging certificate validation](#logging-detailed-certificate-validation) - [Logging detailed KeyManager flow, input and output](#logging-detailed-keymanager-flow-input-and-output) + - [Fluently mapping SSLFactory](#fluently-mapping-sslfactory) - [Returnable values from the SSLFactory](#returnable-values-from-the-sslfactory) 3. [Additional mappers for specific libraries](#additional-mappers-for-specific-libraries) - [Netty](#netty) @@ -1096,6 +1097,37 @@ KeyIdentifier [ ]] ``` +### Fluently mapping SSLFactory +The SSLFactory can be mapped easily if it needs additional intermediate steps before it can be used fully used, such as using it with a Netty or Apache client. Below is an example for Netty with Spring WebClient. + +```java +import io.netty.handler.ssl.SslContext; +import nl.altindag.ssl.SSLFactory; +import nl.altindag.ssl.netty.util.NettySslUtils; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.netty.http.client.HttpClient; + +import javax.net.ssl.SSLException; + +public class App { + + public static void main(String[] args) throws SSLException { + WebClient webClient = SSLFactory.builder() + .withDefaultTrustMaterial() + .build() + .map(NettySslUtils::forClient) + .map(SslContextBuilder::build) + .map(sslContext -> HttpClient.create().secure(sslSpec -> sslSpec.sslContext(sslContext))) + .map(ReactorClientHttpConnector::new) + .map(httpConnector -> WebClient.builder().clientConnector(httpConnector).build()) + .get(); + } + +} +``` + + ### Returnable values from the SSLFactory The SSLFactory provides different kinds of returnable values, see below for all the options: diff --git a/sslcontext-kickstart/src/main/java/nl/altindag/ssl/SSLFactory.java b/sslcontext-kickstart/src/main/java/nl/altindag/ssl/SSLFactory.java index 02b48b42..45b14178 100644 --- a/sslcontext-kickstart/src/main/java/nl/altindag/ssl/SSLFactory.java +++ b/sslcontext-kickstart/src/main/java/nl/altindag/ssl/SSLFactory.java @@ -25,8 +25,10 @@ import nl.altindag.ssl.trustmanager.trustoptions.TrustAnchorTrustOptions; import nl.altindag.ssl.trustmanager.trustoptions.TrustStoreTrustOptions; import nl.altindag.ssl.util.HostnameVerifierUtils; +import nl.altindag.ssl.util.Function; import nl.altindag.ssl.util.KeyManagerUtils; import nl.altindag.ssl.util.KeyStoreUtils; +import nl.altindag.ssl.util.Box; import nl.altindag.ssl.util.SSLContextUtils; import nl.altindag.ssl.util.SSLParametersUtils; import nl.altindag.ssl.util.SSLSessionUtils; @@ -155,6 +157,15 @@ public SSLEngine getSSLEngine(String peerHost, Integer peerPort) { } } + /** + * Returns a cardboard box to further process the SSLFactory instance. + * The helper {@link Box} class provides a mapping method to map the + * source in a functional way. + */ + public Box map(Function mapper) { + return Box.of(this).map(mapper); + } + public static Builder builder() { return new Builder(); } diff --git a/sslcontext-kickstart/src/main/java/nl/altindag/ssl/util/Box.java b/sslcontext-kickstart/src/main/java/nl/altindag/ssl/util/Box.java new file mode 100644 index 00000000..378a99fe --- /dev/null +++ b/sslcontext-kickstart/src/main/java/nl/altindag/ssl/util/Box.java @@ -0,0 +1,64 @@ +/* + * Copyright 2019 Thunderberry. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package nl.altindag.ssl.util; + +import nl.altindag.ssl.exception.GenericException; + +import java.util.function.Supplier; + +/** + * @author Hakan Altindag + */ +@FunctionalInterface +public interface Box { + + ValueHolder valueHolder(); + + static Box of(T value) { + return () -> ValueHolder.wrap(() -> value); + } + + default Box map(Function mapper) { + return () -> ValueHolder.wrap(() -> { + final T value = valueHolder().get(); + + try { + return mapper.apply(value); + } catch (Exception e) { + throw new GenericException(e); + } + }); + } + + default T get() { + return valueHolder().get(); + } + + @FunctionalInterface + interface ValueHolder { + + Supplier valueSupplier(); + + default T get() { + return valueSupplier().get(); + } + + static ValueHolder wrap(Supplier supplier) { + return () -> supplier; + } + } + +} \ No newline at end of file diff --git a/sslcontext-kickstart/src/main/java/nl/altindag/ssl/util/Function.java b/sslcontext-kickstart/src/main/java/nl/altindag/ssl/util/Function.java new file mode 100644 index 00000000..2e95da44 --- /dev/null +++ b/sslcontext-kickstart/src/main/java/nl/altindag/ssl/util/Function.java @@ -0,0 +1,26 @@ +/* + * Copyright 2019 Thunderberry. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package nl.altindag.ssl.util; + +/** + * @author Hakan Altindag + */ +@FunctionalInterface +public interface Function { + + R apply(T t) throws Exception; + +} diff --git a/sslcontext-kickstart/src/test/java/nl/altindag/ssl/SSLFactoryShould.java b/sslcontext-kickstart/src/test/java/nl/altindag/ssl/SSLFactoryShould.java index 3afe937b..d7b8ce95 100644 --- a/sslcontext-kickstart/src/test/java/nl/altindag/ssl/SSLFactoryShould.java +++ b/sslcontext-kickstart/src/test/java/nl/altindag/ssl/SSLFactoryShould.java @@ -1949,6 +1949,18 @@ void haveConsistentParameterConfiguration() throws IOException { assertThat(sslEngine.getWantClientAuth()).isFalse(); } + @Test + void mapSslFactoryInternalsInFunctionalWay() { + SSLParameters sslParameters = SSLFactory.builder() + .withDefaultTrustMaterial() + .build() + .map(SSLFactory::getSSLEngine) + .map(SSLEngine::getSSLParameters) + .get(); + + assertThat(sslParameters).isNotNull(); + } + @Test void throwIllegalArgumentExceptionWhenCertificateIsAbsent() { List certificates = Collections.emptyList(); diff --git a/sslcontext-kickstart/src/test/java/nl/altindag/ssl/util/BoxShould.java b/sslcontext-kickstart/src/test/java/nl/altindag/ssl/util/BoxShould.java new file mode 100644 index 00000000..a89025be --- /dev/null +++ b/sslcontext-kickstart/src/test/java/nl/altindag/ssl/util/BoxShould.java @@ -0,0 +1,39 @@ +/* + * Copyright 2019 Thunderberry. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package nl.altindag.ssl.util; + +import nl.altindag.ssl.exception.GenericException; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * @author Hakan Altindag + */ +class BoxShould { + + @Test + void wrapAnyExceptionInGenericException() { + Box stringBox = Box.of("") + .map(value -> { + throw new RuntimeException("KABOOM"); + }); + + assertThatThrownBy(stringBox::get) + .isInstanceOf(GenericException.class) + .hasRootCauseMessage("KABOOM"); + } +}