Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions core/runtime/src/main/java/io/quarkus/runtime/RuntimeValues.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package io.quarkus.runtime;

import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
* Provides a low-level API for registering and accessing runtime values generated by Quarkus or Quarkus
* extensions. Typically, such values describe a fully configured application, known only at application startup,
* such as the HTTP port.
* <p>
* These values should not be exposed by the Config mechanism directly, as that would require mutating the Config
* system, which should be immutable, and cause hard-to-debug issues. Instead, RuntimeValues should be preferred for
* exposing such values.
*/
public class RuntimeValues {
private static final Map<String, Object> values = new ConcurrentHashMap<>();

/**
* Registers a value with a specified key.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the value associated with the specified key
* @throws IllegalArgumentException if the specified key already has an associated value
*/
@SuppressWarnings("unchecked")
public static <T> T register(final RuntimeKey<T> key, final T value) {
Object mapValue = values.putIfAbsent(key.key(), value);
if (mapValue != null && !mapValue.equals(value)) {
throw new IllegalArgumentException("Key already registered " + key.key() + " with value " + mapValue);
}
return (T) mapValue;
}

/**
* Returns the value to which the specified key is mapped.
*
* @param key the key whose associated value is to be returned
* @return the value to which the specified key is mapped
* @throws java.lang.IllegalArgumentException if the specified key has no associated value
*/
@SuppressWarnings("unchecked")
public static <T> T get(final RuntimeKey<T> key) {
T t = (T) values.get(key.key());
if (t == null) {
throw new IllegalArgumentException("Key " + key.key() + " not found");
}
return t;
}

/**
* Returns the value to which the specified key is mapped.
*
* @param key the key whose associated value is to be returned
* @param defaultValue the default mapping of the key
* @return the value to which the specified key is mapped, or {@code defaultValue} if the key has no value
*/
@SuppressWarnings("unchecked")
public static <T> T get(final RuntimeKey<T> key, final T defaultValue) {
return (T) values.getOrDefault(key.key(), defaultValue);
}

static String getValue(final String propertyName) {
Object value = values.get(propertyName);
// TODO - We may require to convert this to the expected config string
return value != null ? value.toString() : null;
}

public static final class RuntimeKey<T> {
private final String key;

RuntimeKey(String key) {
this.key = key;
}

public String key() {
return key;
}

@Override
public boolean equals(Object o) {
if (!(o instanceof RuntimeKey<?> that))
return false;
return Objects.equals(key, that.key);
}

@Override
public int hashCode() {
return Objects.hashCode(key);
}

public static RuntimeKey<String> key(final String key) {
return new RuntimeKey<>(key);
}

public static RuntimeKey<Integer> intKey(final String key) {
return new RuntimeKey<>(key);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.quarkus.runtime;

import java.util.Set;

import io.smallrye.config.common.AbstractConfigSource;

/**
* A <code>ConfigSource</code> to bridge between <code>Config</code> and {@link RuntimeValues}.
* <p>
* While {@link RuntimeValues} shouldn't be exposed as <code>Config</code>, this is intended to
* work as a temporary compatibility layer, since until the introduction of {@link RuntimeValues},
* the norm was to use <code>Config</code> to relay this kind of information.
* <p>
* This should be kept until we decide on an alternate solution in the discussion
* <a href="https://github.com/quarkusio/quarkus/discussions/46915">#46915</a>.
*/
public class RuntimeValuesConfigSource extends AbstractConfigSource {
public RuntimeValuesConfigSource() {
super(RuntimeValuesConfigSource.class.getName(), Integer.MAX_VALUE - 10);
}

@Override
public Set<String> getPropertyNames() {
return Set.of();
}

@Override
public String getValue(String propertyName) {
return RuntimeValues.getValue(propertyName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import org.eclipse.microprofile.config.spi.ConfigSource;

import io.quarkus.runtime.RuntimeValuesConfigSource;
import io.smallrye.config.SmallRyeConfigBuilder;
import io.smallrye.config.SmallRyeConfigBuilderCustomizer;

Expand All @@ -15,6 +16,7 @@ public class RuntimeConfigBuilder implements SmallRyeConfigBuilderCustomizer {
@Override
public void configBuilder(final SmallRyeConfigBuilder builder) {
new QuarkusConfigBuilderCustomizer().configBuilder(builder);
builder.withSources(new RuntimeValuesConfigSource());
builder.withSources(new UuidConfigSource());

builder.forClassLoader(Thread.currentThread().getContextClassLoader())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
import io.quarkus.vertx.http.runtime.CurrentRequestProducer;
import io.quarkus.vertx.http.runtime.CurrentVertxRequest;
import io.quarkus.vertx.http.runtime.HttpCertificateUpdateEventListener;
import io.quarkus.vertx.http.runtime.HttpServerProducer;
import io.quarkus.vertx.http.runtime.VertxConfigBuilder;
import io.quarkus.vertx.http.runtime.VertxHttpBuildTimeConfig;
import io.quarkus.vertx.http.runtime.VertxHttpConfig.InsecureRequests;
Expand Down Expand Up @@ -161,6 +162,7 @@ FilterBuildItem cors(CORSRecorder recorder,
AdditionalBeanBuildItem additionalBeans() {
return AdditionalBeanBuildItem.builder()
.setUnremovable()
.addBeanClass(HttpServerProducer.class)
.addBeanClass(CurrentVertxRequest.class)
.addBeanClass(CurrentRequestProducer.class)
.addBeanClass(HttpCertificateUpdateEventListener.class)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.quarkus.vertx.http;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import jakarta.inject.Inject;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;
import io.quarkus.vertx.http.runtime.HttpServer;

public class HttpServerTest {
@RegisterExtension
static final QuarkusUnitTest CONFIG = new QuarkusUnitTest();

@Inject
HttpServer webServer;

@Test
void ports() {
assertTrue(webServer.getPort() > 0);
assertEquals(-1, webServer.getSecurePort());
assertEquals(-1, webServer.getManagementPort());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@
import java.net.URL;

import jakarta.enterprise.event.Observes;
import jakarta.inject.Inject;

import org.eclipse.microprofile.config.ConfigProvider;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.hamcrest.Matchers;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;
import io.quarkus.test.common.http.TestHTTPResource;
import io.quarkus.vertx.http.runtime.VertxHttpConfig;
import io.restassured.RestAssured;
import io.vertx.core.Handler;
import io.vertx.ext.web.Router;
Expand All @@ -30,6 +31,8 @@ public class RandomPortTest {

@TestHTTPResource("test")
URL url;
@Inject
VertxHttpConfig vertxHttpConfig;

@Test
public void portShouldNotBeZero() {
Expand All @@ -42,16 +45,17 @@ public void testActualPortAccessibleToApp() {
RestAssured.get("/app").then().body(Matchers.equalTo(Integer.toString(url.getPort())));
}

public static class AppClass {

@ConfigProperty(name = "quarkus.http.port")
String port;
@Test
void mappingPortIsZero() {
assertThat(vertxHttpConfig.testPort()).isZero();
}

public static class AppClass {
public void route(@Observes Router router) {
router.route("/test").handler(new Handler<RoutingContext>() {
@Override
public void handle(RoutingContext event) {
event.response().end(System.getProperty("quarkus.http.test-port"));
event.response().end(ConfigProvider.getConfig().getValue("quarkus.http.test-port", String.class));
}
});
router.route("/app").handler(new Handler<RoutingContext>() {
Expand All @@ -62,5 +66,4 @@ public void handle(RoutingContext event) {
});
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package io.quarkus.vertx.http.runtime;

import io.quarkus.runtime.RuntimeValues;
import io.quarkus.runtime.RuntimeValues.RuntimeKey;

public class HttpServer {
private final static HttpServer INSTANCE = new HttpServer();

public static final RuntimeKey<Integer> HTTP_PORT = RuntimeKey.intKey("quarkus.http.port");
public static final RuntimeKey<Integer> HTTP_TEST_PORT = RuntimeKey.intKey("quarkus.http.test-port");
public static final RuntimeKey<Integer> HTTPS_PORT = RuntimeKey.intKey("quarkus.http.ssl-port");
public static final RuntimeKey<Integer> HTTPS_TEST_PORT = RuntimeKey.intKey("quarkus.http.test-ssl-port");
public static final RuntimeKey<Integer> MANAGEMENT_PORT = RuntimeKey.intKey("quarkus.management.port");
public static final RuntimeKey<Integer> MANAGEMENT_TEST_PORT = RuntimeKey.intKey("quarkus.management.test-port");
Comment on lines +9 to +14
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't we want to store the actual socket address(es) rather than just the port number?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is something we can add. This was just to showcase it with the port number.


private HttpServer() {
}

/**
* Return the http port that Quarkus is listening on.
*
* @return the port or <code>-1</code> if Quarkus is not set to listen to the http port.
*/
public int getPort() {
// TODO - What if this method gets called before being set? Should this be an event instead?
return RuntimeValues.get(HTTP_PORT, -1);
}

static void setPort(int port) {
RuntimeValues.register(HTTP_PORT, port);
}

static void setTestPort(int testPort) {
RuntimeValues.register(HTTP_TEST_PORT, testPort);
}

/**
* Return the https port that Quarkus is listening on.
*
* @return the port or <code>-1</code> if Quarkus is not set to listen to the https port.
*/
public int getSecurePort() {
return RuntimeValues.get(HTTPS_PORT, -1);
}

static void setSecurePort(int port) {
RuntimeValues.register(HTTPS_PORT, port);
}

static void setSecureTestPort(int testPort) {
RuntimeValues.register(HTTPS_TEST_PORT, testPort);
}

/**
* Return the management http port that Quarkus is listening on.
*
* @return the port or <code>-1</code> if Quarkus is not set to listen to the management http port.
*/
public int getManagementPort() {
return RuntimeValues.get(MANAGEMENT_PORT, -1);
}

static void setManagementPort(int managementPort) {
RuntimeValues.register(MANAGEMENT_PORT, managementPort);
}

static void setManagementTestPort(int testPort) {
RuntimeValues.register(MANAGEMENT_TEST_PORT, testPort);
}

public static HttpServer instance() {
return INSTANCE;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.quarkus.vertx.http.runtime;

import jakarta.enterprise.inject.Produces;
import jakarta.inject.Singleton;

@Singleton
public class HttpServerProducer {
@Produces
@Singleton
public HttpServer producer() {
return HttpServer.instance();
}
}
Loading
Loading