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
2 changes: 1 addition & 1 deletion platform-annotation-processors/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>fielden</groupId>
<artifactId>platform-parent</artifactId>
<version>2.0.2-SNAPSHOT</version>
<version>2.1.0-SNAPSHOT</version>
</parent>

<artifactId>platform-annotation-processors</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion platform-annotations/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<parent>
<groupId>fielden</groupId>
<artifactId>platform-parent</artifactId>
<version>2.0.2-SNAPSHOT</version>
<version>2.1.0-SNAPSHOT</version>
</parent>

<artifactId>platform-annotations</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion platform-benchmark/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>fielden</groupId>
<artifactId>platform-parent</artifactId>
<version>2.0.2-SNAPSHOT</version>
<version>2.1.0-SNAPSHOT</version>
</parent>

<name>Trident Genesis Platform Microbenckmarks</name>
Expand Down
2 changes: 1 addition & 1 deletion platform-dao/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<parent>
<groupId>fielden</groupId>
<artifactId>platform-parent</artifactId>
<version>2.0.2-SNAPSHOT</version>
<version>2.1.0-SNAPSHOT</version>
</parent>

<artifactId>platform-dao</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion platform-db-evolution/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<parent>
<groupId>fielden</groupId>
<artifactId>platform-parent</artifactId>
<version>2.0.2-SNAPSHOT</version>
<version>2.1.0-SNAPSHOT</version>
</parent>

<artifactId>platform-db-evolution</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion platform-eql-grammar/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>fielden</groupId>
<artifactId>platform-parent</artifactId>
<version>2.0.2-SNAPSHOT</version>
<version>2.1.0-SNAPSHOT</version>
</parent>

<artifactId>platform-eql-grammar</artifactId>
Expand Down
35 changes: 35 additions & 0 deletions platform-metrics/platform-metrics-core/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>fielden</groupId>
<artifactId>platform-metrics</artifactId>
<version>2.1.0-SNAPSHOT</version>
</parent>

<name>Trident Genesis Platform Metrics Core</name>
<artifactId>platform-metrics-core</artifactId>
<packaging>jar</packaging>
<description>
The core metrics module.
</description>

<dependencies>

<dependency>
<groupId>fielden</groupId>
<artifactId>platform-pojo-bl</artifactId>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>

</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package fielden.platform.metrics;

import com.google.inject.Inject;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.binder.jvm.ClassLoaderMetrics;
import io.micrometer.core.instrument.binder.jvm.JvmGcMetrics;
import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics;
import io.micrometer.core.instrument.binder.jvm.JvmThreadMetrics;
import io.micrometer.core.instrument.binder.system.ProcessorMetrics;
import io.micrometer.core.instrument.binder.system.UptimeMetrics;
import ua.com.fielden.platform.ioc.AbstractPlatformIocModule;

/// This IoC module registers all built-in meters.
///
/// [MetricsCoreIocModule] is a required dependency.
///
/// If metrics are disabled, no meters will be registered.
///
public final class CommonMetricsIocModule extends AbstractPlatformIocModule {

@Override
protected void configure() {
requestStaticInjection(CommonMetricsIocModule.class);
}

@Inject
static void registerMetrics(final MetricsConfig config, final MeterRegistry meterRegistry) {
if (config.mode() == MetricsConfig.Mode.DISABLED) {
return;
}

new ClassLoaderMetrics().bindTo(meterRegistry);
new JvmGcMetrics().bindTo(meterRegistry);
new JvmMemoryMetrics().bindTo(meterRegistry);
new JvmThreadMetrics().bindTo(meterRegistry);
new ProcessorMetrics().bindTo(meterRegistry);
new UptimeMetrics().bindTo(meterRegistry);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package fielden.platform.metrics;

import jakarta.inject.Singleton;
import ua.com.fielden.platform.parser.IValueParser;

import java.util.Properties;

import static ua.com.fielden.platform.parser.IValueParser.enumIgnoreCaseParser;
import static ua.com.fielden.platform.parser.IValueParser.propertyParser;

/// Configuration of the metrics system.
///
/// @param mode Optional (default: [Mode#DISABLED]).
/// Specified through application property [#PROPERTY_MODE], case-insensitive.
/// All parts of the metrics API should document their behaviour with respect to the mode.
///
@Singleton
public record MetricsConfig (Mode mode) {

public static final String PROPERTY_MODE = "metrics.mode";

public static MetricsConfig fromProperties(final Properties properties) {
final var mode = modeParser.apply(properties).getOrThrow();
return new MetricsConfig(mode);
}

public enum Mode { ENABLED, DISABLED }

public Mode assertMode(final Mode expected) {
if (!mode.equals(expected)) {
throw new MetricsException("Metrics mode must be [%s], but was [%s].".formatted(expected, mode));
}
else {
return mode;
}
}


private static final IValueParser<Properties, Mode> modeParser = propertyParser(PROPERTY_MODE, enumIgnoreCaseParser(Mode.values()), Mode.DISABLED);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package fielden.platform.metrics;

import com.google.inject.Inject;
import com.google.inject.Provides;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.prometheusmetrics.PrometheusConfig;
import io.micrometer.prometheusmetrics.PrometheusMeterRegistry;
import jakarta.inject.Singleton;
import org.apache.logging.log4j.Logger;
import ua.com.fielden.platform.ioc.AbstractPlatformIocModule;

import java.util.Properties;

import static org.apache.logging.log4j.LogManager.getLogger;

/// Core IoC module for the metrics system.
///
/// ### Bindings
///
/// * [#meterRegistry(MetricsConfig)]
/// * [MetricsConfig] -- configuration is read from application properties.
///
public final class MetricsCoreIocModule extends AbstractPlatformIocModule {

private static final Logger LOGGER = getLogger();

private final Properties properties;

public MetricsCoreIocModule(final Properties properties) {
this.properties = properties;
}

@Override
protected void configure() {
requestStaticInjection(MetricsCoreIocModule.class);

bind(MetricsConfig.class).toInstance(MetricsConfig.fromProperties(properties));
}

/// If metrics are enabled, binds a [PrometheusMeterRegistry].
/// Otherwise, binds a noop registry.
///
@Provides
@Singleton
MeterRegistry meterRegistry(final MetricsConfig config) {
return switch (config.mode()) {
case ENABLED -> new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
case DISABLED -> {
final var registry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
registry.close();
yield registry;
}
};
}

@Inject
static void logMetricsConfig(final MetricsConfig metricsConfig) {
LOGGER.info("Metrics configuration: %s".formatted(metricsConfig));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package fielden.platform.metrics;

import ua.com.fielden.platform.exceptions.AbstractPlatformRuntimeException;

public class MetricsException extends AbstractPlatformRuntimeException {

public MetricsException(final String message) {
super(message);
}

public MetricsException(final String message, final Throwable cause) {
super(message, cause);
}

}
43 changes: 43 additions & 0 deletions platform-metrics/platform-metrics-web-server/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>fielden</groupId>
<artifactId>platform-metrics</artifactId>
<version>2.1.0-SNAPSHOT</version>
</parent>

<name>Trident Genesis Platform Metrics (Web Server)</name>
<artifactId>platform-metrics-web-server</artifactId>
<packaging>jar</packaging>
<description>
This module provides metrics for web servers.
</description>

<dependencies>

<dependency>
<groupId>fielden</groupId>
<artifactId>platform-metrics-core</artifactId>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>fielden</groupId>
<artifactId>platform-web-resources</artifactId>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>org.restlet</groupId>
<artifactId>org.restlet</artifactId>
<version>${restlet.version}</version>
</dependency>

</dependencies>

</project>

Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package fielden.platform.metrics.web_server;

import com.google.inject.Provides;
import com.google.inject.name.Named;
import fielden.platform.metrics.MetricsConfig;
import jakarta.inject.Singleton;
import ua.com.fielden.platform.basic.config.exceptions.ApplicationConfigurationException;
import ua.com.fielden.platform.ioc.AbstractPlatformIocModule;

import java.util.Optional;

/// Configures authentication for the metrics web resource.
///
/// Applications that use [MetricsResourceFactory] must install this module.
///
/// ### Bindings
///
/// * [#apiKey(MetricsConfig)]
///
public class MetricsAuthenticationIocModule extends AbstractPlatformIocModule {

/// Name of a binding for the API key.
///
public static final String TG_METRICS_API_KEY = "tg.metrics.apiKey";

/// If metrics are enabled, an API key must be specified as a System property or an environment variable.
/// Otherwise, an empty optional will be bound.
///
@Provides
@Named(TG_METRICS_API_KEY)
@Singleton
Optional<String> apiKey(final MetricsConfig config) {
return switch (config.mode()) {
case DISABLED -> Optional.empty();
case ENABLED -> {
final var value = System.getProperty(TG_METRICS_API_KEY, System.getenv(TG_METRICS_API_KEY));
if (value == null) {
throw new ApplicationConfigurationException("%s is missing.".formatted(TG_METRICS_API_KEY));
}
yield Optional.of(value);
}
};
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package fielden.platform.metrics.web_server;

import jakarta.inject.Inject;
import jakarta.inject.Named;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.data.Status;
import org.restlet.security.Authenticator;

import javax.annotation.Nullable;
import java.util.Optional;

import static fielden.platform.metrics.web_server.MetricsAuthenticationIocModule.TG_METRICS_API_KEY;

/// Implements authentication for the metrics resource.
///
/// For a request to be authenticated, it must contain header [#API_KEY_HEADER] that specifies a valid [API key][MetricsAuthenticationIocModule#TG_METRICS_API_KEY].
///
/// If metrics are disabled, authentication will always fail.
///
public class MetricsAuthenticator extends Authenticator {

private static final String API_KEY_HEADER = "X-API-Key";

private final Optional<String> maybeApiKey;

@Inject
private MetricsAuthenticator(final @Named(TG_METRICS_API_KEY) Optional<String> maybeApiKey) {
super(null); // Context is not used by this class
this.maybeApiKey = maybeApiKey;
}

@Override
protected boolean authenticate(final Request request, final Response response) {
return maybeApiKey
.map(apiKey -> {
final @Nullable String requestApiKey = request.getHeaders().getFirstValue(API_KEY_HEADER, true);
return apiKey.equals(requestApiKey) || forbid(response);
})
.orElseGet(() -> forbid(response));
}

private boolean forbid(final Response response) {
response.setStatus(Status.CLIENT_ERROR_UNAUTHORIZED);
return false;
}

}
Loading