JdkHttpServerProviderTest.java

/*
 * Copyright (c) 2021, 2023 Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2018 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 org.glassfish.jersey.jdkhttp;

import static java.lang.Boolean.FALSE;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.instanceOf;

import java.security.AccessController;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.net.ssl.SSLContext;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.SeBootstrap;
import jakarta.ws.rs.SeBootstrap.Configuration.SSLClientAuthentication;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.core.Application;
import jakarta.ws.rs.core.UriBuilder;

import org.glassfish.jersey.internal.util.PropertiesHelper;
import org.glassfish.jersey.server.ServerProperties;
import org.glassfish.jersey.server.spi.Container;
import org.glassfish.jersey.server.spi.WebServer;
import org.glassfish.jersey.server.spi.WebServerProvider;

import com.sun.net.httpserver.HttpServer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;

/**
 * Unit tests for {@link JdkHttpServerProvider}.
 *
 * @author Markus KARG (markus@headcrashing.eu)
 * @since 3.1.0
 */
public final class JdkHttpServerProviderTest {

    @Test
    @Timeout(value = 15000L, unit = TimeUnit.MILLISECONDS)
    public void shouldProvideServer() throws InterruptedException, ExecutionException {
        // given
        final Resource resource = new Resource();
        shouldProvideServer(ShouldProvideServerApplication.class, resource);
    }

    @Test
    @Timeout(value = 15000L, unit = TimeUnit.MILLISECONDS)
    public void shouldProvideServerWithClass() throws InterruptedException, ExecutionException {
        // given
        final Resource resource = new Resource();
        final Application application = new ShouldProvideServerApplication();
        shouldProvideServer(application.getClass(), resource);
    }

    private void shouldProvideServer(final Object application, final Resource resource)
            throws InterruptedException, ExecutionException {
        // given
        final WebServerProvider webServerProvider = new JdkHttpServerProvider();
        final SeBootstrap.Configuration configuration = configuration(getPort());

        // when
        final WebServer webServer = Application.class.isInstance(application)
                ? webServerProvider.createServer(WebServer.class, (Application) application, configuration)
                : webServerProvider.createServer(WebServer.class, (Class<Application>) application, configuration);
        final Object nativeHandle = webServer.unwrap(Object.class);
        final CompletionStage<?> start = webServer.start();
        final Object startResult = start.toCompletableFuture().get();
        final Container container = webServer.container();
        final int port = webServer.port();
        final String entity = ClientBuilder.newClient()
                .target(UriBuilder.newInstance().scheme("http").host("localhost").port(port).build()).request()
                .get(String.class);
        final CompletionStage<?> stop = webServer.stop();
        final Object stopResult = stop.toCompletableFuture().get();

        // then
        assertThat(webServer, is(instanceOf(JdkHttpServer.class)));
        assertThat(nativeHandle, is(instanceOf(HttpServer.class)));
        assertThat(startResult, is(nullValue()));
        assertThat(container, is(instanceOf(JdkHttpHandlerContainer.class)));
        assertThat(port, is(greaterThan(0)));
        assertThat(entity, is(resource.toString()));
        assertThat(stopResult, is(nullValue()));
    }

    @Path("/")
    protected static final class Resource {
        @GET
        @Override
        public String toString() {
            return Resource.class.getName();
        }
    }

    protected static class ShouldProvideServerApplication extends Application {
        @Override
        public Set<Object> getSingletons() {
            return Collections.singleton(new Resource());
        }
    }

    private static final Logger LOGGER = Logger.getLogger(JdkHttpServerProviderTest.class.getName());

    private static final int DEFAULT_PORT = 0;

    private static final int getPort() {
        final String value = AccessController
                .doPrivileged(PropertiesHelper.getSystemProperty("jersey.config.test.container.port"));
        if (value != null) {
            try {
                final int i = Integer.parseInt(value);
                if (i < 0) {
                    throw new NumberFormatException("Value is negative.");
                }
                return i;
            } catch (final NumberFormatException e) {
                LOGGER.log(Level.CONFIG,
                        "Value of 'jersey.config.test.container.port'"
                                + " property is not a valid non-negative integer [" + value + "]."
                                + " Reverting to default [" + DEFAULT_PORT + "].",
                        e);
            }
        }

        return DEFAULT_PORT;
    }

    @Test
    @Timeout(value = 15000L, unit = TimeUnit.MILLISECONDS)
    public final void shouldScanFreePort() throws InterruptedException, ExecutionException {
        // given
        final WebServerProvider webServerProvider = new JdkHttpServerProvider();
        final Application application = new Application();
        final SeBootstrap.Configuration configuration = configuration(SeBootstrap.Configuration.FREE_PORT);

        // when
        final WebServer webServer = webServerProvider.createServer(WebServer.class, application, configuration);

        // then
        assertThat(webServer.port(), is(greaterThan(0)));
    }

    private SeBootstrap.Configuration configuration(int port) {
        return (SeBootstrap.Configuration) name -> {
            switch (name) {
                case SeBootstrap.Configuration.PROTOCOL:
                    return "HTTP";
                case SeBootstrap.Configuration.HOST:
                    return "localhost";
                case SeBootstrap.Configuration.PORT:
                    return port;
                case SeBootstrap.Configuration.ROOT_PATH:
                    return "/";
                case SeBootstrap.Configuration.SSL_CLIENT_AUTHENTICATION:
                    return SSLClientAuthentication.NONE;
                case SeBootstrap.Configuration.SSL_CONTEXT:
                    try {
                        return SSLContext.getDefault();
                    } catch (final NoSuchAlgorithmException e) {
                        throw new RuntimeException(e);
                    }
                case ServerProperties.WEBSERVER_AUTO_START:
                    return FALSE;
                default:
                    return null;
            }
        };
    }
}