Skip to content

Commit

Permalink
More utilities to get ports in DropwizardConnectors (#452)
Browse files Browse the repository at this point in the history
Add several utility methods to get application and/or admin ports from either a Dropwizard
Configuration or DefaultServerFactory.

Also, add scheme() method to ConnectoType enum to get the scheme (http or https).

Closes #450
Closes #453
  • Loading branch information
sleberknight committed Apr 25, 2024
1 parent 001dfef commit bd800fb
Show file tree
Hide file tree
Showing 2 changed files with 653 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -1,22 +1,32 @@
package org.kiwiproject.dropwizard.util.server;

import static com.google.common.base.Preconditions.checkState;
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.toMap;
import static org.kiwiproject.base.KiwiPreconditions.checkArgumentNotNull;
import static org.kiwiproject.base.KiwiStrings.format;
import static org.kiwiproject.collect.KiwiLists.first;

import com.google.common.annotations.VisibleForTesting;
import io.dropwizard.core.Configuration;
import io.dropwizard.core.server.DefaultServerFactory;
import io.dropwizard.core.server.ServerFactory;
import io.dropwizard.jetty.ConnectorFactory;
import io.dropwizard.jetty.HttpConnectorFactory;
import io.dropwizard.jetty.HttpsConnectorFactory;
import lombok.Getter;
import lombok.experimental.Accessors;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.kiwiproject.registry.model.Port;
import org.kiwiproject.registry.model.Port.PortType;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;

/**
* Utility class that assists with setting up the server connectors in Dropwizard.
Expand All @@ -29,13 +39,27 @@ public class DropwizardConnectors {
* Enum defining the possible options for a connector type in Dropwizard.
*/
public enum ConnectorType {
HTTP, HTTPS;
HTTP("http"), HTTPS("https");

/**
* The scheme (in a URL) for this type of connector.
*/
@Getter
@Accessors(fluent = true)
final String scheme;

ConnectorType(String scheme) {
this.scheme = scheme;
}

/**
* Given an {@link HttpConnectorFactory} instance, determine whether it is for HTTP or HTTPS.
*
* @param factory the instance
* @return the ConnectorType
* @implNote Assumes there is only {@link HttpConnectorFactory} and one
* subclass, {@link HttpsConnectorFactory}. This is what has existed in Dropwizard for many
* years now. HTTPS is returned for {@link HttpsConnectorFactory}, and HTTP for anything else.
*/
static ConnectorType forHttpConnectorFactory(HttpConnectorFactory factory) {
checkArgumentNotNull(factory, "factory cannot be null");
Expand All @@ -48,6 +72,21 @@ static ConnectorType forHttpConnectorFactory(HttpConnectorFactory factory) {
}
}

/**
* Requires that the given {@link Configuration} contains a {@link ServerFactory} that is
* a {@link DefaultServerFactory}.
*
* @param <C> the type of the Dropwizard Configuration
* @param configuration the Dropwizard configuration
* @return the server factory from the configuration if it is an instance of {@link DefaultServerFactory}
* @throws IllegalStateException if serverFactory is not a {@link DefaultServerFactory}
*/
public static <C extends Configuration> DefaultServerFactory requireDefaultServerFactory(C configuration) {
checkArgumentNotNull(configuration, "configuration is required");

return requireDefaultServerFactory(configuration.getServerFactory());
}

/**
* Requires that the given {@link ServerFactory} is in fact a {@link DefaultServerFactory}.
*
Expand All @@ -68,6 +107,231 @@ public static DefaultServerFactory requireDefaultServerFactory(ServerFactory ser
throw new IllegalStateException(error);
}

/**
* Get all the ports.
*
* @param <C> the type of the Dropwizard Configuration
* @param configuration the Dropwizard configuration
* @return a list of all the ports in the Dropwizard configuration
*/
public static <C extends Configuration> List<Port> getPorts(C configuration) {
var serverFactory = requireDefaultServerFactory(configuration);
return getPorts(serverFactory);
}

/**
* Get all the ports.
*
* @param serverFactory the {@link DefaultServerFactory} to get the ports from
* @return a list of all the ports in the server factory
*/
public static List<Port> getPorts(DefaultServerFactory serverFactory) {
return Stream.of(getApplicationPorts(serverFactory), getAdminPorts(serverFactory))
.flatMap(Collection::stream)
.toList();
}

/**
* Get the single application port. If there is more than one, throw an exception.
*
* @param <C> the type of the Dropwizard Configuration
* @param configuration the Dropwizard configuration
* @return the single application port
* @throws IllegalStateException if there is more than one application port
*/
public static <C extends Configuration> Port getOnlyApplicationPort(C configuration) {
var serverFactory = requireDefaultServerFactory(configuration);
return getOnlyApplicationPort(serverFactory);
}

/**
* Get the single application port. If there is more than one, throw an exception.
*
* @param serverFactory the {@link DefaultServerFactory} to get the single application port from
* @return the application port
* @throws IllegalStateException if there is more than one port
*/
public static Port getOnlyApplicationPort(DefaultServerFactory serverFactory) {
var applicationPorts = getApplicationPorts(serverFactory);
checkExactlyOnePort("application", applicationPorts);
return first(applicationPorts);
}

/**
* Find only the application ports.
*
* @param <C> the type of the Dropwizard Configuration
* @param configuration the Dropwizard configuration
* @return the application ports
*/
public static <C extends Configuration> List<Port> getApplicationPorts(C configuration) {
var serverFactory = requireDefaultServerFactory(configuration);
return getApplicationPorts(serverFactory);
}

/**
* Find only the application ports.
*
* @param serverFactory the {@link DefaultServerFactory} to get the application ports from
* @return the application ports
*/
public static List<Port> getApplicationPorts(DefaultServerFactory serverFactory) {
return getPorts(serverFactory, PortType.APPLICATION);
}

/**
* Get the single admin port. If there is more than one, throw an exception.
*
* @param <C> the type of the Dropwizard Configuration
* @param configuration the Dropwizard configuration
* @return the admin port
* @throws IllegalStateException if there is more than one admin port
*/
public static <C extends Configuration> Port getOnlyAdminPort(C configuration) {
var serverFactory = requireDefaultServerFactory(configuration);
return getOnlyAdminPort(serverFactory);
}

/**
* Get the single admin port. If there is more than one, throw an exception.
*
* @param serverFactory the {@link DefaultServerFactory} to get single admin port from
* @return the admin port
* @throws IllegalStateException if there is more than one admin port
*/
public static Port getOnlyAdminPort(DefaultServerFactory serverFactory) {
var adminPorts = getAdminPorts(serverFactory);
checkExactlyOnePort("admin", adminPorts);
return first(adminPorts);
}

private static void checkExactlyOnePort(String portType, List<Port> ports) {
var numPorts = ports.size();
checkState(numPorts == 1, "expected only one %s port but found %s", portType, numPorts);
}

/**
* Find only the admin ports.
*
* @param <C> the type of the Dropwizard Configuration
* @param configuration the Dropwizard configuration
* @return the admin ports
*/
public static <C extends Configuration> List<Port> getAdminPorts(C configuration) {
var serverFactory = requireDefaultServerFactory(configuration);
return getPorts(serverFactory, PortType.ADMIN);
}

/**
* Find only the admin ports.
*
* @param serverFactory the {@link DefaultServerFactory} to get the admin ports from
* @return the admin ports
*/
public static List<Port> getAdminPorts(DefaultServerFactory serverFactory) {
return getPorts(serverFactory, PortType.ADMIN);
}

/**
* Find all the {@link Port}s having the given type.
*
* @param <C> the type of the Dropwizard Configuration
* @param configuration the Dropwizard configuration
* @param portType the type of port to find
* @return a list containing the matched ports
*/
public static <C extends Configuration> List<Port> getPorts(C configuration, PortType portType) {
var serverFactory = requireDefaultServerFactory(configuration);
return getPorts(serverFactory, portType);
}

/**
* Find all the {@link Port}s having the given type.
*
* @param serverFactory the {@link DefaultServerFactory} to get the ports from
* @param portType the type of port to find
* @return a list containing the matched ports
*/
public static List<Port> getPorts(DefaultServerFactory serverFactory, PortType portType) {
return Arrays.stream(ConnectorType.values())
.map(connectorType -> getPort(serverFactory, portType, connectorType))
.flatMap(Optional::stream)
.toList();
}

/**
* Get the {@link Port} having the given type and connector type.
*
* @param <C> the type of the Dropwizard Configuration
* @param configuration the Dropwizard configuration
* @param portType the type of port to find
* @param connectorType the connector type to find
* @return an {@link Optional} containing the matching port or {@code Optional#empty()}
*/
public static <C extends Configuration> Optional<Port> getPort(C configuration,
PortType portType,
ConnectorType connectorType) {
var serverFactory = requireDefaultServerFactory(configuration);
return getPort(serverFactory, portType, connectorType);
}

/**
* Get the {@link Port} having the given type and connector type.
*
* @param serverFactory the {@link DefaultServerFactory} to get the ports from
* @param portType the type of port to find
* @param connectorType the connector type to find
* @return an {@link Optional} containing the matching port or {@code Optional#empty()}
*/
public static Optional<Port> getPort(DefaultServerFactory serverFactory,
PortType portType,
ConnectorType connectorType) {

Optional<Integer> port = (portType == PortType.APPLICATION) ?
getApplicationPort(serverFactory, connectorType) : getAdminPort(serverFactory, connectorType);

return port.map(portNum -> newPort(portType, connectorType, portNum));
}

/**
* Get the application port from the given Dropwizard configuration that has the given connector type.
*
* @param <C> the type of the Dropwizard Configuration
* @param configuration the Dropwizard configuration
* @param connectorType the connector type to find
* @return an {@link Optional} containing the matching port number or {@code Optional#empty()}
*/
public static <C extends Configuration> Optional<Integer> getApplicationPort(C configuration, ConnectorType connectorType) {
var defaultServerFactory = requireDefaultServerFactory(configuration);
return getApplicationPort(defaultServerFactory, connectorType);
}

/**
* Get the admin port from the given Dropwizard configuration that has the given connector type.
*
* @param <C> the type of the Dropwizard Configuration
* @param configuration the Dropwizard configuration
* @param connectorType the connector type to find
* @return an {@link Optional} containing the matching port number or {@code Optional#empty()}
*/
public static <C extends Configuration> Optional<Integer> getAdminPort(C configuration, ConnectorType connectorType) {
var defaultServerFactory = requireDefaultServerFactory(configuration);
return getAdminPort(defaultServerFactory, connectorType);
}

/**
* Create a new {@link Port} instance.
*
* @param portType the type of port
* @param connectorType the connector type for the port
* @param portNumber the port number
* @return a new Port instance
*/
public static Port newPort(PortType portType, ConnectorType connectorType, Integer portNumber) {
checkArgumentNotNull(portNumber, "portNumber must not be null");
return Port.of(portNumber, portType, Port.Security.fromScheme(connectorType.name()));
}

/**
* Determines the application port for the Dropwizard server that matches the given connectorType
*
Expand Down Expand Up @@ -111,8 +375,8 @@ private static HttpConnectorFactory getConnectorFactory(ConnectorType connectorT
* This method assumes there will be only one connector factory for each connector type. For example, an application
* with HTTPS application and admin ports. Or an application with HTTP application and admin ports. Or even an
* application with both HTTPS and HTTP application and admin ports. So if the given list of connectors contains
* more than one connector factory of a given type, for example two HTTPS connector factories, we will always return
* the last one in the list. Our reasoning is that it doesn't make much sense to us for a Dropwizard (or any) web
* more than one connector factory of a given type, for example, two HTTPS connector factories, we will always return
* the last one in the list. Our reasoning is that it makes little sense to us for a Dropwizard (or any) web
* service to run on multiple HTTPS application ports. Maybe there is some good reason to do that, but we've never
* seen one. This is why the merge function below always returns the second factory.
*/
Expand Down
Loading

0 comments on commit bd800fb

Please sign in to comment.