diff --git a/documentation/src/docs/asciidoc/user-guide/extensions.adoc b/documentation/src/docs/asciidoc/user-guide/extensions.adoc index 4d7c5020d6ad..59318c7ef5fe 100644 --- a/documentation/src/docs/asciidoc/user-guide/extensions.adoc +++ b/documentation/src/docs/asciidoc/user-guide/extensions.adoc @@ -1,3 +1,6 @@ +:testDir: ../../../../src/test/java +:kotlinTestDir: ../../../../src/test/kotlin + [[extensions]] == Extension Model @@ -693,7 +696,6 @@ Please refer to the implementations of <> or <> which use this extension point to provide their functionality. - [[extensions-keeping-state]] === Keeping State in Extensions @@ -714,6 +716,35 @@ extension context lifecycle ends it closes its associated store. All stored valu that are instances of `CloseableResource` are notified by an invocation of their `close()` method in the inverse order they were added in. +An example implementation of `CloseableResource` is shown below, using an `HttpServer` +resource. + +[source,java,indent=0] +.`HttpServer` resource implementing `CloseableResource` +---- +include::{testDir}/example/extensions/HttpServerResource.java[tags=user_guide] +---- + +This resource can then be stored in the desired `ExtensionContext`. It may be stored at +class or method level, if desired, but this may add unnecessary overhead for this type of +resource. For this example it might be prudent to store it at root level and instantiate +it lazily to ensure it's only created once per test run and reused across different test +classes and methods. + +[source,java,indent=0] +.Lazily storing in root context with `Store.getOrComputeIfAbsent` +---- +include::{testDir}/example/extensions/HttpServerExtension.java[tags=user_guide] +---- + +[source,java,indent=0] +.A test case using the `HttpServerExtension` +---- +include::{testDir}/example/HttpServerDemo.java[tags=user_guide] +---- + +[[extensions-conditional-test-execution]] + [[extensions-supported-utilities]] === Supported Utilities in Extensions diff --git a/documentation/src/test/java/example/HttpServerDemo.java b/documentation/src/test/java/example/HttpServerDemo.java new file mode 100644 index 000000000000..d961793db5a0 --- /dev/null +++ b/documentation/src/test/java/example/HttpServerDemo.java @@ -0,0 +1,58 @@ +/* + * Copyright 2015-2024 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URL; + +import com.sun.net.httpserver.HttpServer; + +import example.extensions.HttpServerExtension; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +// tag::user_guide[] +@ExtendWith(HttpServerExtension.class) +public class HttpServerDemo { + + // end::user_guide[] + @SuppressWarnings("HttpUrlsUsage") + // tag::user_guide[] + @Test + void httpCall(HttpServer server) throws Exception { + String hostName = server.getAddress().getHostName(); + int port = server.getAddress().getPort(); + String rawUrl = String.format("http://%s:%d/example", hostName, port); + URL requestUrl = URI.create(rawUrl).toURL(); + + String responseBody = sendRequest(requestUrl); + + assertEquals("This is a test", responseBody); + } + + private static String sendRequest(URL url) throws IOException { + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + int contentLength = connection.getContentLength(); + try (InputStream response = url.openStream()) { + byte[] content = new byte[contentLength]; + assertEquals(contentLength, response.read(content)); + return new String(content, UTF_8); + } + } +} +// end::user_guide[] diff --git a/documentation/src/test/java/example/extensions/HttpServerExtension.java b/documentation/src/test/java/example/extensions/HttpServerExtension.java new file mode 100644 index 000000000000..29318152228f --- /dev/null +++ b/documentation/src/test/java/example/extensions/HttpServerExtension.java @@ -0,0 +1,50 @@ +/* + * Copyright 2015-2024 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.extensions; + +import java.io.IOException; +import java.io.UncheckedIOException; + +import com.sun.net.httpserver.HttpServer; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolver; + +// tag::user_guide[] +public class HttpServerExtension implements ParameterResolver { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return HttpServer.class.equals(parameterContext.getParameter().getType()); + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + + ExtensionContext rootContext = extensionContext.getRoot(); + ExtensionContext.Store store = rootContext.getStore(Namespace.GLOBAL); + String key = HttpServerResource.class.getName(); + HttpServerResource resource = store.getOrComputeIfAbsent(key, __ -> { + try { + HttpServerResource serverResource = new HttpServerResource(0); + serverResource.start(); + return serverResource; + } + catch (IOException e) { + throw new UncheckedIOException("Failed to create HttpServerResource", e); + } + }, HttpServerResource.class); + return resource.getHttpServer(); + } +} +// end::user_guide[] diff --git a/documentation/src/test/java/example/extensions/HttpServerResource.java b/documentation/src/test/java/example/extensions/HttpServerResource.java new file mode 100644 index 000000000000..2f4e2e52afc3 --- /dev/null +++ b/documentation/src/test/java/example/extensions/HttpServerResource.java @@ -0,0 +1,74 @@ +/* + * Copyright 2015-2024 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.extensions; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.InetSocketAddress; + +import com.sun.net.httpserver.HttpServer; + +import org.junit.jupiter.api.extension.ExtensionContext.Store.CloseableResource; + +/** + * Demonstrates an implementation of {@link CloseableResource} using an {@link HttpServer}. + */ +// tag::user_guide[] +class HttpServerResource implements CloseableResource { + + private final HttpServer httpServer; + + // end::user_guide[] + + /** + * Initializes the Http server resource, using the given port. + * + * @param port (int) The port number for the server, must be in the range 0-65535. + * @throws IOException if an IOException occurs during initialization. + */ + // tag::user_guide[] + HttpServerResource(int port) throws IOException { + InetAddress loopbackAddress = InetAddress.getLoopbackAddress(); + this.httpServer = HttpServer.create(new InetSocketAddress(loopbackAddress, port), 0); + } + + HttpServer getHttpServer() { + return httpServer; + } + + // end::user_guide[] + + /** + * Starts the Http server with an example handler. + */ + // tag::user_guide[] + void start() { + // Example handler + httpServer.createContext("/example", exchange -> { + String body = "This is a test"; + exchange.sendResponseHeaders(200, body.length()); + try (OutputStream os = exchange.getResponseBody()) { + os.write(body.getBytes(UTF_8)); + } + }); + httpServer.setExecutor(null); + httpServer.start(); + } + + @Override + public void close() { + httpServer.stop(0); + } +} +// end::user_guide[]