All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ee.jakarta.tck.ws.rs.sebootstrap.SeBootstrapIT Maven / Gradle / Ivy

/*
 * Copyright (c) 2020 Markus Karg. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package ee.jakarta.tck.ws.rs.sebootstrap;

import static java.util.concurrent.TimeUnit.HOURS;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.is;

import static ee.jakarta.tck.ws.rs.common.util.JaxrsUtil.freePort;

import java.io.IOException;
import java.util.Collections;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.api.Tag;

import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.SeBootstrap;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.core.Application;
import jakarta.ws.rs.core.UriBuilder;

/**
 * Compliance Test for Java SE Bootstrap API of Jakarta REST API
 *
 * @author Markus KARG ([email protected])
 * @since 3.1
 */
@Timeout(value = 1, unit = HOURS)
public final class SeBootstrapIT {

    /**
     * Verifies that an instance will boot using default configuration.
     * 
     * @throws ExecutionException   if the instance didn't boot correctly
     * @throws InterruptedException if the test took much longer than usually
     *                              expected
     */
    @Test
    @Tag("se_bootstrap")
    public final void shouldBootInstanceUsingDefaults() throws InterruptedException, ExecutionException {
        // given
        final int expectedResponse = mockInt();
        final Application application = new StaticApplication(expectedResponse);
        final SeBootstrap.Configuration.Builder bootstrapConfigurationBuilder = SeBootstrap.Configuration.builder();
        final SeBootstrap.Configuration requestedConfiguration = bootstrapConfigurationBuilder.build();

        // when
        final CompletionStage completionStage = SeBootstrap.start(application,
                requestedConfiguration);
        final SeBootstrap.Instance instance = completionStage.toCompletableFuture().get();
        final SeBootstrap.Configuration actualConfiguration = instance.configuration();
        final int actualResponse = client.target(UriBuilder.newInstance().scheme(actualConfiguration.protocol())
                .host(actualConfiguration.host()).port(actualConfiguration.port()).path(actualConfiguration.rootPath())
                .path("application/resource")).request().get(int.class);

        // then
        assertThat(actualResponse, is(expectedResponse));
        assertThat(actualConfiguration.protocol(), is("HTTP"));
        assertThat(actualConfiguration.host(), is("localhost"));
        assertThat(actualConfiguration.port(), is(greaterThan(0)));
        assertThat(actualConfiguration.rootPath(), is("/"));
        instance.stop().toCompletableFuture().get();
    }

    /**
     * Verifies that an instance will boot using explicit configuration given by
     * properties.
     * 
     * @throws ExecutionException   if the instance didn't boot correctly
     * @throws InterruptedException if the test took much longer than usually
     *                              expected
     * @throws IOException          if no IP port was free
     */
    @Test
    @Tag("se_bootstrap")
    public final void shouldBootInstanceUsingProperties() throws InterruptedException, ExecutionException, IOException {
        // given
        final int expectedResponse = mockInt();
        final Application application = new StaticApplication(expectedResponse);
        final SeBootstrap.Configuration.Builder bootstrapConfigurationBuilder = SeBootstrap.Configuration.builder();
        final SeBootstrap.Configuration requestedConfiguration = bootstrapConfigurationBuilder
                .property(SeBootstrap.Configuration.PROTOCOL, "HTTP")
                .property(SeBootstrap.Configuration.HOST, "localhost")
                .property(SeBootstrap.Configuration.PORT, someFreeIpPort())
                .property(SeBootstrap.Configuration.ROOT_PATH, "/root/path").build();

        // when
        final CompletionStage completionStage = SeBootstrap.start(application,
                requestedConfiguration);
        final SeBootstrap.Instance instance = completionStage.toCompletableFuture().get();
        final SeBootstrap.Configuration actualConfiguration = instance.configuration();
        final int actualResponse = client.target(UriBuilder.newInstance().scheme(actualConfiguration.protocol())
                .host(actualConfiguration.host()).port(actualConfiguration.port()).path(actualConfiguration.rootPath())
                .path("application/resource")).request().get(int.class);

        // then
        assertThat(actualResponse, is(expectedResponse));
        assertThat(actualConfiguration.protocol(), is(requestedConfiguration.protocol()));
        assertThat(actualConfiguration.host(), is(requestedConfiguration.host()));
        assertThat(actualConfiguration.port(), is(requestedConfiguration.port()));
        assertThat(actualConfiguration.rootPath(), is(requestedConfiguration.rootPath()));
        instance.stop().toCompletableFuture().get();
    }

    /**
     * Verifies that an instance will boot using explicit configuration given by
     * convenience methods.
     * 
     * @throws ExecutionException   if the instance didn't boot correctly
     * @throws InterruptedException if the test took much longer than usually
     *                              expected
     * @throws IOException          if no IP port was free
     */
    @Test
    @Tag("se_bootstrap")
    public final void shouldBootInstanceUsingConvenienceMethods()
            throws InterruptedException, ExecutionException, IOException {
        // given
        final int expectedResponse = mockInt();
        final Application application = new StaticApplication(expectedResponse);
        final SeBootstrap.Configuration.Builder bootstrapConfigurationBuilder = SeBootstrap.Configuration.builder();
        final SeBootstrap.Configuration requestedConfiguration = bootstrapConfigurationBuilder.protocol("HTTP")
                .host("localhost").port(someFreeIpPort()).rootPath("/root/path").build();

        // when
        final CompletionStage completionStage = SeBootstrap.start(application,
                requestedConfiguration);
        final SeBootstrap.Instance instance = completionStage.toCompletableFuture().get();
        final SeBootstrap.Configuration actualConfiguration = instance.configuration();
        final int actualResponse = client.target(UriBuilder.newInstance().scheme(actualConfiguration.protocol())
                .host(actualConfiguration.host()).port(actualConfiguration.port()).path(actualConfiguration.rootPath())
                .path("application/resource")).request().get(int.class);

        // then
        assertThat(actualResponse, is(expectedResponse));
        assertThat(actualConfiguration.protocol(), is(requestedConfiguration.protocol()));
        assertThat(actualConfiguration.host(), is(requestedConfiguration.host()));
        assertThat(actualConfiguration.port(), is(requestedConfiguration.port()));
        assertThat(actualConfiguration.rootPath(), is(requestedConfiguration.rootPath()));
        instance.stop().toCompletableFuture().get();
    }

    /**
     * Verifies that an instance will boot using external configuration.
     * 
     * @throws ExecutionException   if the instance didn't boot correctly
     * @throws InterruptedException if the test took much longer than usually
     *                              expected
     * @throws IOException          if no IP port was free
     */
    @Test
    @Tag("se_bootstrap")
    public final void shouldBootInstanceUsingExternalConfiguration()
            throws InterruptedException, ExecutionException, IOException {
        // given
        final int someFreeIpPort = someFreeIpPort();
        final int expectedResponse = mockInt();
        final Application application = new StaticApplication(expectedResponse);
        final SeBootstrap.Configuration.Builder bootstrapConfigurationBuilder = SeBootstrap.Configuration.builder();
        final SeBootstrap.Configuration requestedConfiguration = bootstrapConfigurationBuilder
                .from((property, type) -> {
                    switch (property) {
                        case SeBootstrap.Configuration.PROTOCOL:
                            return Optional.of("HTTP");
                        case SeBootstrap.Configuration.HOST:
                            return Optional.of("localhost");
                        case SeBootstrap.Configuration.PORT:
                            return Optional.of(someFreeIpPort);
                        case SeBootstrap.Configuration.ROOT_PATH:
                            return Optional.of("/root/path");
                        default:
                            return Optional.empty();
                    }
                }).build();

        // when
        final CompletionStage completionStage = SeBootstrap.start(application,
                requestedConfiguration);
        final SeBootstrap.Instance instance = completionStage.toCompletableFuture().get();
        final SeBootstrap.Configuration actualConfiguration = instance.configuration();
        final int actualResponse = client.target(UriBuilder.newInstance().scheme(actualConfiguration.protocol())
                .host(actualConfiguration.host()).port(actualConfiguration.port()).path(actualConfiguration.rootPath())
                .path("application/resource")).request().get(int.class);

        // then
        assertThat(actualResponse, is(expectedResponse));
        assertThat(actualConfiguration.protocol(), is(requestedConfiguration.protocol()));
        assertThat(actualConfiguration.host(), is(requestedConfiguration.host()));
        assertThat(actualConfiguration.port(), is(requestedConfiguration.port()));
        assertThat(actualConfiguration.rootPath(), is(requestedConfiguration.rootPath()));
        instance.stop().toCompletableFuture().get();
    }

    /**
     * Verifies that an instance will ignore unknown configuration parameters.
     * 
     * @throws ExecutionException   if the instance didn't boot correctly
     * @throws InterruptedException if the test took much longer than usually
     *                              expected
     * @throws IOException          if no IP port was free
     */
    @Test
    @Tag("se_bootstrap")
    public final void shouldBootInstanceDespiteUnknownConfigurationParameters()
            throws InterruptedException, ExecutionException, IOException {
        // given
        final int expectedResponse = mockInt();
        final Application application = new StaticApplication(expectedResponse);
        final SeBootstrap.Configuration.Builder bootstrapConfigurationBuilder = SeBootstrap.Configuration.builder();
        final SeBootstrap.Configuration requestedConfiguration = bootstrapConfigurationBuilder.protocol("HTTP")
                .host("localhost").port(someFreeIpPort()).rootPath("/root/path").from((property, type) -> {
                    switch (property) {
                        case "ee.jakarta.tck.ws.rs.sebootstrap.SeBootstrapIT$Unknown_1":
                            return Optional.of("Silently ignored value A");
                        default:
                            return Optional.empty();
                    }
                }).property("ee.jakarta.tck.ws.rs.sebootstrap.SeBootstrapIT$Unknown_2", "Silently ignored value B")
                .from(new Object()).build();

        // when
        final CompletionStage completionStage = SeBootstrap.start(application,
                requestedConfiguration);
        final SeBootstrap.Instance instance = completionStage.toCompletableFuture().get();
        final SeBootstrap.Configuration actualConfiguration = instance.configuration();
        final int actualResponse = client.target(UriBuilder.newInstance().scheme(actualConfiguration.protocol())
                .host(actualConfiguration.host()).port(actualConfiguration.port()).path(actualConfiguration.rootPath())
                .path("application/resource")).request().get(int.class);

        // then
        assertThat(actualResponse, is(expectedResponse));
        assertThat(actualConfiguration.protocol(), is(requestedConfiguration.protocol()));
        assertThat(actualConfiguration.host(), is(requestedConfiguration.host()));
        assertThat(actualConfiguration.port(), is(requestedConfiguration.port()));
        assertThat(actualConfiguration.rootPath(), is(requestedConfiguration.rootPath()));
        instance.stop().toCompletableFuture().get();
    }

    /**
     * Verifies that an instance will boot using a self-detected free IP port.
     * 
     * @throws ExecutionException   if the instance didn't boot correctly
     * @throws InterruptedException if the test took much longer than usually
     *                              expected
     */
    @Test
    @Tag("se_bootstrap")
    public final void shouldBootInstanceUsingSelfDetectedFreeIpPort()
            throws InterruptedException, ExecutionException {
        // given
        final int expectedResponse = mockInt();
        final Application application = new StaticApplication(expectedResponse);
        final SeBootstrap.Configuration.Builder bootstrapConfigurationBuilder = SeBootstrap.Configuration.builder();
        final SeBootstrap.Configuration requestedConfiguration = bootstrapConfigurationBuilder.protocol("HTTP")
                .host("localhost").port(SeBootstrap.Configuration.FREE_PORT).rootPath("/root/path").build();

        // when
        final CompletionStage completionStage = SeBootstrap.start(application,
                requestedConfiguration);
        final SeBootstrap.Instance instance = completionStage.toCompletableFuture().get();
        final SeBootstrap.Configuration actualConfiguration = instance.configuration();
        final int actualResponse = client.target(UriBuilder.newInstance().scheme(actualConfiguration.protocol())
                .host(actualConfiguration.host()).port(actualConfiguration.port()).path(actualConfiguration.rootPath())
                .path("application/resource")).request().get(int.class);

        // then
        assertThat(actualResponse, is(expectedResponse));
        assertThat(actualConfiguration.protocol(), is(requestedConfiguration.protocol()));
        assertThat(actualConfiguration.host(), is(requestedConfiguration.host()));
        assertThat(actualConfiguration.port(), is(greaterThan(0)));
        assertThat(actualConfiguration.rootPath(), is(requestedConfiguration.rootPath()));
        instance.stop().toCompletableFuture().get();
    }

    /**
     * Verifies that an instance will boot using the implementation's default IP
     * port.
     * 
     * @throws ExecutionException   if the instance didn't boot correctly
     * @throws InterruptedException if the test took much longer than usually
     *                              expected
     */
    @Test
    @Tag("se_bootstrap")
    public final void shouldBootInstanceUsingImplementationsDefaultIpPort()
            throws InterruptedException, ExecutionException {
        // given
        final int expectedResponse = mockInt();
        final Application application = new StaticApplication(expectedResponse);
        final SeBootstrap.Configuration.Builder bootstrapConfigurationBuilder = SeBootstrap.Configuration.builder();
        final SeBootstrap.Configuration requestedConfiguration = bootstrapConfigurationBuilder.protocol("HTTP")
                .host("localhost").port(SeBootstrap.Configuration.DEFAULT_PORT).rootPath("/root/path").build();

        // when
        final CompletionStage completionStage = SeBootstrap.start(application,
                requestedConfiguration);
        final SeBootstrap.Instance instance = completionStage.toCompletableFuture().get();
        final SeBootstrap.Configuration actualConfiguration = instance.configuration();
        final int actualResponse = client.target(UriBuilder.newInstance().scheme(actualConfiguration.protocol())
                .host(actualConfiguration.host()).port(actualConfiguration.port()).path(actualConfiguration.rootPath())
                .path("application/resource")).request().get(int.class);

        // then
        assertThat(actualResponse, is(expectedResponse));
        assertThat(actualConfiguration.protocol(), is(requestedConfiguration.protocol()));
        assertThat(actualConfiguration.host(), is(requestedConfiguration.host()));
        assertThat(actualConfiguration.port(), is(greaterThan(0)));
        assertThat(actualConfiguration.rootPath(), is(requestedConfiguration.rootPath()));
        instance.stop().toCompletableFuture().get();
    }

    private static Client client;

    @BeforeAll
    static void createClient() {
        SeBootstrapIT.client = ClientBuilder.newClient();
    }

    @AfterAll
    static void disposeClient() {
        SeBootstrapIT.client.close();
    }

    @ApplicationPath("application")
    public static final class StaticApplication extends Application {

        private final StaticResource staticResource;

        private StaticApplication(final long staticResponse) {
            this.staticResource = new StaticResource(staticResponse);
        }

        @Override
        public final Set getSingletons() {
            return Collections.singleton(staticResource);
        }

        @Path("resource")
        public static final class StaticResource {

            private final long staticResponse;

            private StaticResource(final long staticResponse) {
                this.staticResponse = staticResponse;
            }

            @GET
            public final long staticResponse() {
                return this.staticResponse;
            }
        }
    };

    private static final int someFreeIpPort() throws IOException {
        return freePort();
    }

    private static final int mockInt() {
        return (int) Math.round(Integer.MAX_VALUE * Math.random());
    }
}