From 9b07cdc60458357b4da086b0e1719d9a020154bf Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Fri, 5 Jun 2026 16:00:38 +0200 Subject: [PATCH 01/11] feature: [172] Enable native build with GraalVM --- application/build.gradle | 50 + .../application/MqttBrokerApplication.java | 3 + .../config/NativeConfigurationHints.java | 66 ++ .../NativeImageVerificationTest.groovy | 903 ++++++++++++++++++ build.gradle | 1 + 5 files changed, 1023 insertions(+) create mode 100644 application/src/main/java/javasabr/mqtt/broker/application/config/NativeConfigurationHints.java create mode 100644 application/src/test/groovy/javasabr/mqtt/broker/application/NativeImageVerificationTest.groovy diff --git a/application/build.gradle b/application/build.gradle index 8ec3ebae..9e9707ca 100644 --- a/application/build.gradle +++ b/application/build.gradle @@ -3,6 +3,7 @@ plugins { id("configure-java") id("groovy") id("org.springframework.boot") + id 'org.graalvm.buildtools.native' } description = "Standard configuration of standalone version of MQTT Broker" @@ -18,6 +19,9 @@ dependencies { testImplementation projects.testSupport testImplementation testFixtures(projects.network) + runtimeOnly projects.credentialsSourceFile + runtimeOnly projects.authenticationProviderBasic + testImplementation projects.credentialsSourceFile testImplementation projects.authenticationProviderBasic } @@ -35,3 +39,49 @@ bootRun { bootJar { mainClass = "javasabr.mqtt.broker.application.MqttBrokerApplication" } + +jar { + from(sourceSets.aot.output) +} + +tasks.named('processAot') { + jvmArgs += '-Dauthentication.credentials-source.file.enabled=true' + jvmArgs += '-Dauthentication.provider.basic.enabled=true' +} + +graalvmNative { + toolchainDetection = true + binaries { + main { + sharedLibrary = false + mainClass = "javasabr.mqtt.broker.application.MqttBrokerApplication" + buildArgs.add("--enable-preview") + javaLauncher = javaToolchains.launcherFor { + languageVersion = JavaLanguageVersion.of(25) + vendor = JvmVendorSpec.matching("GraalVM Community") + } + } + } +} + +def nativeCompileDir = layout.buildDirectory.dir('native/nativeCompile').get().asFile.absolutePath + +tasks.register('copyNativeTestProperties', Copy) { + from('src/test/resources/application-test.properties') { + rename 'application-test.properties', 'application.properties' + filter { line -> line.replace('classpath:auth/credentials-test', "file://${nativeCompileDir}/auth/credentials-test") } + } + from('src/test/resources/log4j2-test.xml') { + rename 'log4j2-test.xml', 'log4j2.xml' + } + from('src/test/resources') { + include 'auth/credentials-test' + } + into layout.buildDirectory.dir('native/nativeCompile') +} + +tasks.named('nativeCompile') { + dependsOn(':authentication-provider-basic:jar') + dependsOn(':credentials-source-file:jar') + finalizedBy copyNativeTestProperties +} \ No newline at end of file diff --git a/application/src/main/java/javasabr/mqtt/broker/application/MqttBrokerApplication.java b/application/src/main/java/javasabr/mqtt/broker/application/MqttBrokerApplication.java index f5500602..fc6df6a7 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/MqttBrokerApplication.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/MqttBrokerApplication.java @@ -1,13 +1,16 @@ package javasabr.mqtt.broker.application; import javasabr.mqtt.broker.application.config.MqttBrokerSpringConfig; +import javasabr.mqtt.broker.application.config.NativeConfigurationHints; import lombok.RequiredArgsConstructor; import org.springframework.boot.SpringApplication; import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.ImportRuntimeHints; @Import({ MqttBrokerSpringConfig.class }) +@ImportRuntimeHints(NativeConfigurationHints.class) @RequiredArgsConstructor public class MqttBrokerApplication { static void main(String[] args) { diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/NativeConfigurationHints.java b/application/src/main/java/javasabr/mqtt/broker/application/config/NativeConfigurationHints.java new file mode 100644 index 00000000..d5950df0 --- /dev/null +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/NativeConfigurationHints.java @@ -0,0 +1,66 @@ +package javasabr.mqtt.broker.application.config; + +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.aot.hint.TypeReference; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; +import org.springframework.core.type.filter.AssignableTypeFilter; + +public class NativeConfigurationHints implements RuntimeHintsRegistrar { + + private static final String[] REFLECTION_PACKAGES = { + "javasabr.mqtt", + }; + + private static final String[] ARRAY_PACKAGES = { + "javasabr.mqtt", + "javasabr.rlib", + }; + + private static final String[] JDK_ARRAY_TYPES = { + "java.util.function.Consumer[]", + "java.util.function.BiConsumer[]", + "java.nio.ByteBuffer[]", + "reactor.core.publisher.FluxSink[]", + "java.lang.String[]", + "java.util.UUID[]", + }; + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + var scanner = new ClassPathScanningCandidateComponentProvider(false); + scanner.addIncludeFilter(new AssignableTypeFilter(Object.class)); + + for (String pkg : REFLECTION_PACKAGES) { + for (BeanDefinition candidate : scanner.findCandidateComponents(pkg)) { + hints + .reflection() + .registerType( + TypeReference.of(candidate.getBeanClassName()), + MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, + MemberCategory.INVOKE_PUBLIC_METHODS, + MemberCategory.INVOKE_DECLARED_METHODS, + MemberCategory.ACCESS_DECLARED_FIELDS); + } + } + + var arrayScanner = new ClassPathScanningCandidateComponentProvider(false); + arrayScanner.addIncludeFilter(new AssignableTypeFilter(Object.class)); + + for (String pkg : ARRAY_PACKAGES) { + for (var candidate : arrayScanner.findCandidateComponents(pkg)) { + hints + .reflection() + .registerType(TypeReference.of(candidate.getBeanClassName() + "[]")); + } + } + + for (String arrayType : JDK_ARRAY_TYPES) { + hints + .reflection() + .registerType(TypeReference.of(arrayType)); + } + } +} diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/NativeImageVerificationTest.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/NativeImageVerificationTest.groovy new file mode 100644 index 00000000..e0ab5938 --- /dev/null +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/NativeImageVerificationTest.groovy @@ -0,0 +1,903 @@ +package javasabr.mqtt.broker.application + +import com.hivemq.client.mqtt.MqttClient +import com.hivemq.client.mqtt.datatypes.MqttQos +import com.hivemq.client.mqtt.mqtt3.Mqtt3AsyncClient +import com.hivemq.client.mqtt.mqtt3.exceptions.Mqtt3ConnAckException +import com.hivemq.client.mqtt.mqtt3.message.Mqtt3MessageType +import com.hivemq.client.mqtt.mqtt3.message.connect.connack.Mqtt3ConnAckReturnCode +import com.hivemq.client.mqtt.mqtt3.message.publish.Mqtt3Publish +import com.hivemq.client.mqtt.mqtt3.message.subscribe.suback.Mqtt3SubAckReturnCode +import com.hivemq.client.mqtt.mqtt5.Mqtt5AsyncClient +import com.hivemq.client.mqtt.mqtt5.exceptions.Mqtt5ConnAckException +import com.hivemq.client.mqtt.mqtt5.message.Mqtt5MessageType +import com.hivemq.client.mqtt.mqtt5.message.connect.connack.Mqtt5ConnAckReasonCode +import com.hivemq.client.mqtt.mqtt5.message.publish.Mqtt5PayloadFormatIndicator +import com.hivemq.client.mqtt.mqtt5.message.publish.Mqtt5Publish +import com.hivemq.client.mqtt.mqtt5.message.subscribe.suback.Mqtt5SubAckReasonCode +import groovy.util.logging.Slf4j +import javasabr.mqtt.model.* +import javasabr.mqtt.model.reason.code.ConnectAckReasonCode +import javasabr.mqtt.model.reason.code.PublishCompletedReasonCode +import javasabr.mqtt.model.reason.code.PublishReceivedReasonCode +import javasabr.mqtt.model.reason.code.SubscribeAckReasonCode +import javasabr.mqtt.model.subscription.Subscription +import javasabr.mqtt.model.topic.TopicFilter +import javasabr.mqtt.network.MqttConnection +import javasabr.mqtt.network.MqttMockClient +import javasabr.mqtt.network.message.in.ConnectAckMqttInMessage +import javasabr.mqtt.network.message.in.PublishMqttInMessage +import javasabr.mqtt.network.message.in.PublishReleaseMqttInMessage +import javasabr.mqtt.network.message.in.SubscribeAckMqttInMessage +import javasabr.mqtt.network.message.out.* +import javasabr.mqtt.network.user.ConfigurableNetworkMqttUser +import javasabr.rlib.collections.array.Array +import spock.lang.Ignore +import spock.lang.Shared +import spock.lang.Specification + +import java.nio.charset.StandardCharsets +import java.util.concurrent.CompletableFuture +import java.util.concurrent.CompletionException +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicReference + +import static javasabr.mqtt.broker.application.MqttClientFactory.generateClientId + +@Slf4j +class NativeImageVerificationTest extends Specification { + + private static final String BINARY_PATH = "build/native/nativeCompile/application" + private static final String NETWORK_READY_MARKER = "Started external MQTT network by address" + + public static final encoding = StandardCharsets.UTF_8 + public static final publishPayload = "publishPayload".getBytes(encoding) + public static final testClientId = "testClientId" + public static final keepAlive = 120 + + @Shared + Process brokerProcess + + def setupSpec() { + brokerProcess = startBroker() + } + + private static Process startBroker() { + def binaryPath = new File(BINARY_PATH).absolutePath + Process process = new ProcessBuilder() + .command([ + binaryPath, + //"-Dlog4j.configurationFile=classpath:log4j2.xml", + //"--debug" + ]) + .directory(new File("build/native/nativeCompile/")) + .redirectErrorStream(true) + .start() + + def networkReady = new CountDownLatch(1) + + Thread.startDaemon("broker-output-drainer") { + def reader = new BufferedReader(new InputStreamReader(process.getInputStream())) + String line + while ((line = reader.readLine()) != null) { + println(line) + if (line.contains(NETWORK_READY_MARKER)) { + networkReady.countDown() + } + } + } + + if (!networkReady.await(10, TimeUnit.SECONDS)) { + process.destroyForcibly() + throw new RuntimeException("Broker failed to start within 10 seconds: network not ready") + } + + return process + } + + def cleanupSpec() { + if (brokerProcess != null && brokerProcess.isAlive()) { + brokerProcess.destroy() + brokerProcess.waitFor(5, TimeUnit.SECONDS) + if (brokerProcess.isAlive()) { + brokerProcess.destroyForcibly() + } + } + } + + def buildExternalMqtt311Client() { + return buildExternalMqtt311Client(generateClientId("mqtt311")) + } + + def buildExternalMqtt5Client() { + return buildExternalMqtt5Client(generateClientId("mqtt5")) + } + + def buildExternalMqtt311Client(String clientId) { + return MqttClient.builder() + .identifier(clientId) + .serverHost("localhost") + .serverPort(1883) + .useMqttVersion3() + .addDisconnectedListener { + println "[$clientId|mqtt311] disconnected:[${it.cause?.message}]" + } + .buildAsync() + } + + def buildExternalMqtt5Client(String clientId) { + return MqttClient.builder() + .identifier(clientId) + .serverHost("localhost") + .serverPort(1883) + .useMqttVersion5() + .addDisconnectedListener { + println "[$clientId|mqtt5] disconnected:[${it.cause?.message}]" + } + .buildAsync() + } + + def connectWith(Mqtt3AsyncClient client, String user, String pass) { + return client.connectWith() + .simpleAuth() + .username(user) + .password(pass.getBytes(encoding)) + .applySimpleAuth() + .send() + .join() + } + + def connectWith(Mqtt5AsyncClient client, String user, String pass) { + return client.connectWith() + .simpleAuth() + .username(user) + .password(pass.getBytes(encoding)) + .applySimpleAuth() + .send() + .join() + } + + def buildMqtt5MockClient() { + return new MqttMockClient( + "localhost", + 1883, + mqtt5MockedConnection() + ) + } + + def buildMqtt311MockClient() { + return new MqttMockClient( + "localhost", + 1883, + mqtt311MockedConnection() + ) + } + + def mqtt5MockedConnection() { + def serverConnConfig = new MqttServerConnectionConfig( + QoS.EXACTLY_ONCE, + MqttProperties.MAX_MESSAGE_SIZE_DEFAULT, + MqttProperties.MAX_MESSAGE_SIZE_DEFAULT / 2 as int, + MqttProperties.MAX_MESSAGE_SIZE_DEFAULT, + 10, + MqttProperties.SERVER_KEEP_ALIVE_MIN, + 10, + MqttProperties.TOPIC_ALIAS_MAX, + true, + true, + true, + true, + true, + true + ) + MqttClientConnectionConfig clientConnConfig = new MqttClientConnectionConfig( + serverConnConfig, + serverConnConfig.maxQos(), + MqttVersion.MQTT_5, + null, + serverConnConfig.receiveMaxPublishes(), + serverConnConfig.maxMessageSize(), + serverConnConfig.topicAliasMaxValue(), + MqttProperties.SERVER_KEEP_ALIVE_DEFAULT, + false, + false) + def connectionRef = new AtomicReference() + def connection = Stub(MqttConnection) { + isSupported(MqttVersion.MQTT_5) >> true + isSupported(MqttVersion.MQTT_3_1_1) >> true + serverConnectionConfig() >> serverConnConfig + clientConnectionConfig() >> clientConnConfig + user() >> Stub(ConfigurableNetworkMqttUser) { + connectionConfig() >> clientConnConfig + connection() >> connectionRef.get() + clientId() >> testClientId + } + } + connectionRef.set(connection) + return connection + } + + def mqtt311MockedConnection() { + def serverConnConfig = new MqttServerConnectionConfig( + QoS.EXACTLY_ONCE, + MqttProperties.MAX_MESSAGE_SIZE_DEFAULT, + MqttProperties.MAX_MESSAGE_SIZE_DEFAULT / 2 as int, + MqttProperties.MAX_MESSAGE_SIZE_DEFAULT, + 10, + MqttProperties.SERVER_KEEP_ALIVE_MIN, + 10, + MqttProperties.TOPIC_ALIAS_MAX, + true, + true, + true, + true, + true, + true + ) + MqttClientConnectionConfig clientConnConfig = new MqttClientConnectionConfig( + serverConnConfig, + serverConnConfig.maxQos(), + MqttVersion.MQTT_3_1_1, + null, + serverConnConfig.receiveMaxPublishes(), + serverConnConfig.maxMessageSize(), + serverConnConfig.topicAliasMaxValue(), + MqttProperties.SERVER_KEEP_ALIVE_DEFAULT, + false, + false) + def connectionRef = new AtomicReference() + def connection = Stub(MqttConnection) { + isSupported(MqttVersion.MQTT_5) >> false + isSupported(MqttVersion.MQTT_3_1_1) >> true + serverConnectionConfig() >> serverConnConfig + clientConnectionConfig() >> clientConnConfig + user() >> Stub(ConfigurableNetworkMqttUser) { + connectionConfig() >> clientConnConfig + connection() >> connectionRef.get() + clientId() >> testClientId + } + } + connectionRef.set(connection) + return connection + } + + def "should connect to native image"() { + given: + def client = MqttClient.builder() + .identifier(generateClientId("TLS5")) + .serverHost("localhost") + .serverPort(1883) + .useMqttVersion5() + .addDisconnectedListener { + println "[mqtt5] disconnected:[${it.cause?.message}]" + } + .buildAsync() + when: + def result = client.connect().join() + then: + result.reasonCode == Mqtt5ConnAckReasonCode.SUCCESS + cleanup: + client.disconnect().join() + } + + def "should deliver publish message QoS 0 using mqtt 3.1.1"() { + given: + def deviceId = generateClientId("device") + def serviceId = generateClientId("service") + def serviceName = "ConnectSubscribePublishTest1" + def received = new CompletableFuture() + def subscriber = buildExternalMqtt311Client(serviceId) + def publisher = buildExternalMqtt311Client(deviceId) + when: + subscriber.connect().join() + publisher.connect().join() + def subscribeResult = subscribe(subscriber, "service/$serviceName/device/+", MqttQos.AT_MOST_ONCE, received) + def publishResult = publish(publisher, "service/$serviceName/device/$deviceId", MqttQos.AT_MOST_ONCE) + then: + noExceptionThrown() + subscribeResult != null + subscribeResult.returnCodes.contains(Mqtt3SubAckReturnCode.SUCCESS_MAXIMUM_QOS_0) + subscribeResult.type == Mqtt3MessageType.SUBACK + publishResult != null + publishResult.qos == MqttQos.AT_MOST_ONCE + publishResult.type == Mqtt3MessageType.PUBLISH + received.join() != null + received.join().qos == MqttQos.AT_MOST_ONCE + received.join().type == Mqtt3MessageType.PUBLISH + cleanup: + subscriber.disconnect().join() + publisher.disconnect().join() + } + + def "should deliver publish message QoS 0 using mqtt 5"() { + given: + def deviceId = generateClientId("device") + def serviceId = generateClientId("service") + def serviceName = "ConnectSubscribePublishTest2" + def received = new CompletableFuture() + def subscriber = buildExternalMqtt5Client(serviceId) + def publisher = buildExternalMqtt5Client(deviceId) + when: + subscriber.connect().join() + publisher.connect().join() + def subscribeResult = subscribe(subscriber, "service/$serviceName/device/+", MqttQos.AT_MOST_ONCE, received) + def publishResult = publish(publisher, "service/$serviceName/device/$deviceId", MqttQos.AT_MOST_ONCE) + then: + noExceptionThrown() + subscribeResult != null + subscribeResult.reasonCodes.contains(Mqtt5SubAckReasonCode.GRANTED_QOS_0) + subscribeResult.type == Mqtt5MessageType.SUBACK + publishResult != null + publishResult.publish.qos == MqttQos.AT_MOST_ONCE + publishResult.publish.type == Mqtt5MessageType.PUBLISH + received.join() != null + received.join().qos == MqttQos.AT_MOST_ONCE + received.join().type == Mqtt5MessageType.PUBLISH + cleanup: + subscriber.disconnect().join() + publisher.disconnect().join() + } + + def "should deliver publish message QoS 1 using mqtt 3.1.1"() { + given: + def deviceId = generateClientId("device") + def serviceId = generateClientId("service") + def serviceName = "ConnectSubscribePublishTest3" + def received = new CompletableFuture() + def subscriber = buildExternalMqtt311Client(serviceId) + def publisher = buildExternalMqtt311Client(deviceId) + when: + subscriber.connect().join() + publisher.connect().join() + def subscribeResult = subscribe(subscriber, "service/$serviceName/device/+", MqttQos.AT_LEAST_ONCE, received) + def publishResult = publish(publisher, "service/$serviceName/device/$deviceId", MqttQos.AT_LEAST_ONCE) + then: + noExceptionThrown() + subscribeResult != null + subscribeResult.returnCodes.contains(Mqtt3SubAckReturnCode.SUCCESS_MAXIMUM_QOS_1) + subscribeResult.type == Mqtt3MessageType.SUBACK + publishResult != null + publishResult.qos == MqttQos.AT_LEAST_ONCE + publishResult.type == Mqtt3MessageType.PUBLISH + received.join() != null + received.join().qos == MqttQos.AT_LEAST_ONCE + received.join().type == Mqtt3MessageType.PUBLISH + cleanup: + subscriber.disconnect().join() + publisher.disconnect().join() + } + + def "should deliver publish message QoS 1 using mqtt 5"() { + given: + def deviceId = generateClientId("device") + def serviceId = generateClientId("service") + def serviceName = "ConnectSubscribePublishTest4" + def received = new CompletableFuture() + def subscriber = buildExternalMqtt5Client(serviceId) + def publisher = buildExternalMqtt5Client(deviceId) + when: + subscriber.connect().join() + publisher.connect().join() + def subscribeResult = subscribe(subscriber, "service/$serviceName/device/+", MqttQos.AT_LEAST_ONCE, received) + def publishResult = publish(publisher, "service/$serviceName/device/$deviceId", MqttQos.AT_LEAST_ONCE) + then: + noExceptionThrown() + subscribeResult != null + subscribeResult.reasonCodes.contains(Mqtt5SubAckReasonCode.GRANTED_QOS_1) + subscribeResult.type == Mqtt5MessageType.SUBACK + publishResult != null + publishResult.publish.qos == MqttQos.AT_LEAST_ONCE + publishResult.publish.type == Mqtt5MessageType.PUBLISH + received.join() != null + received.join().qos == MqttQos.AT_LEAST_ONCE + received.join().type == Mqtt5MessageType.PUBLISH + cleanup: + subscriber.disconnect().join() + publisher.disconnect().join() + } + + def "should deliver publish message QoS 2 using mqtt 3.1.1"() { + given: + def deviceId = generateClientId("device") + def serviceId = generateClientId("service") + def serviceName = "ConnectSubscribePublishTest5" + def received = new CompletableFuture() + def subscriber = buildExternalMqtt311Client(serviceId) + def publisher = buildExternalMqtt311Client(deviceId) + when: + subscriber.connect().join() + publisher.connect().join() + def subscribeResult = subscribe(subscriber, "service/$serviceName/device/+", MqttQos.EXACTLY_ONCE, received) + def publishResult = publish(publisher, "service/$serviceName/device/$deviceId", MqttQos.EXACTLY_ONCE) + then: + noExceptionThrown() + subscribeResult != null + subscribeResult.returnCodes.contains(Mqtt3SubAckReturnCode.SUCCESS_MAXIMUM_QOS_2) + subscribeResult.type == Mqtt3MessageType.SUBACK + publishResult != null + publishResult.qos == MqttQos.EXACTLY_ONCE + publishResult.type == Mqtt3MessageType.PUBLISH + received.join() != null + received.join().qos == MqttQos.EXACTLY_ONCE + received.join().type == Mqtt3MessageType.PUBLISH + cleanup: + subscriber.disconnect().join() + publisher.disconnect().join() + } + + def "should deliver publish message QoS 2 using mqtt 5"() { + given: + def deviceId = generateClientId("device") + def serviceId = generateClientId("service") + def serviceName = "ConnectSubscribePublishTest6" + def received = new CompletableFuture() + def subscriber = buildExternalMqtt5Client(serviceId) + def publisher = buildExternalMqtt5Client(deviceId) + when: + subscriber.connect().join() + publisher.connect().join() + def subscribeResult = subscribe(subscriber, "service/$serviceName/device/+", MqttQos.EXACTLY_ONCE, received) + def publishResult = publish(publisher, "service/$serviceName/device/$deviceId", MqttQos.EXACTLY_ONCE) + Thread.sleep(100) + then: + noExceptionThrown() + subscribeResult != null + subscribeResult.reasonCodes.contains(Mqtt5SubAckReasonCode.GRANTED_QOS_2) + subscribeResult.type == Mqtt5MessageType.SUBACK + publishResult != null + publishResult.publish.qos == MqttQos.EXACTLY_ONCE + publishResult.publish.type == Mqtt5MessageType.PUBLISH + received.join() != null + received.join().qos == MqttQos.EXACTLY_ONCE + received.join().type == Mqtt5MessageType.PUBLISH + cleanup: + subscriber.disconnect().join() + publisher.disconnect().join() + } + + def "client should connect to broker without user and pass using MQTT 3.1.1"() { + given: + def client = buildExternalMqtt311Client() + when: + def result = client.connect().join() + then: + result.returnCode == Mqtt3ConnAckReturnCode.SUCCESS + !result.sessionPresent + cleanup: + client.disconnect().join() + } + + def "client should connect to broker without user and pass using MQTT 5"() { + given: + def client = buildExternalMqtt5Client() + when: + def result = client.connect().join() + then: + result.reasonCode == Mqtt5ConnAckReasonCode.SUCCESS + !result.sessionExpiryInterval.present + result.serverKeepAlive.present + result.serverKeepAlive.getAsInt() == MqttProperties.SERVER_KEEP_ALIVE_DISABLED + !result.serverReference.present + !result.responseInformation.present + !result.assignedClientIdentifier.present + !result.sessionPresent + cleanup: + client.disconnect().join() + } + + def "client should connect to broker with user and pass using MQTT 3.1.1"() { + given: + def client = buildExternalMqtt311Client() + when: + def result = connectWith(client, 'user1', 'password') + then: + result.returnCode == Mqtt3ConnAckReturnCode.SUCCESS + !result.sessionPresent + cleanup: + client.disconnect().join() + } + + def "client should connect to broker with user and pass using MQTT 5"() { + given: + def client = buildExternalMqtt5Client() + when: + def result = connectWith(client, 'user1', 'password') + then: + result.reasonCode == Mqtt5ConnAckReasonCode.SUCCESS + !result.sessionExpiryInterval.present + result.serverKeepAlive.present + result.serverKeepAlive.getAsInt() == MqttProperties.SERVER_KEEP_ALIVE_DISABLED + !result.serverReference.present + !result.responseInformation.present + !result.assignedClientIdentifier.present + !result.sessionPresent + cleanup: + client.disconnect().join() + } + + def "client should not connect to broker without providing a client id using MQTT 3.1.1"() { + given: + def client = buildExternalMqtt311Client("") + when: + client.connect().join() + then: + def ex = thrown CompletionException + def cause = ex.cause as Mqtt3ConnAckException + cause.mqttMessage.returnCode == Mqtt3ConnAckReturnCode.IDENTIFIER_REJECTED + } + + @Ignore("until finalizing clientId validation") + def "client should connect to broker without providing a client id using MQTT 5"() { + given: + def client = buildExternalMqtt5Client("") + when: + def result = client.connect().join() + then: + result.reasonCode == Mqtt5ConnAckReasonCode.SUCCESS + result.assignedClientIdentifier.present + result.assignedClientIdentifier.get().toString() != "" + cleanup: + client.disconnect().join() + } + + def "client should not connect to broker with invalid client id using MQTT 3.1.1"(String clientId) { + given: + def client = buildExternalMqtt311Client(clientId) + when: + client.connect().join() + then: + def ex = thrown CompletionException + def cause = ex.cause as Mqtt3ConnAckException + cause.mqttMessage.returnCode == Mqtt3ConnAckReturnCode.IDENTIFIER_REJECTED + where: + clientId << ["!@#!@*()^&"] + } + + def "client should not connect to broker with invalid client id using MQTT 5"(String clientId) { + given: + def client = buildExternalMqtt5Client(clientId) + when: + client.connect().join() + then: + def ex = thrown CompletionException + def cause = ex.cause as Mqtt5ConnAckException + cause.mqttMessage.reasonCode == Mqtt5ConnAckReasonCode.CLIENT_IDENTIFIER_NOT_VALID + where: + clientId << ["!@#!@*()^&"] + } + + def "client should not connect to broker with wrong pass using MQTT 3.1.1"() { + given: + def client = buildExternalMqtt311Client() + when: + connectWith(client, "user", "wrongPassword") + then: + def ex = thrown CompletionException + def cause = ex.cause as Mqtt3ConnAckException + cause.mqttMessage.returnCode == Mqtt3ConnAckReturnCode.BAD_USER_NAME_OR_PASSWORD + } + + def "client should not connect to broker with wrong pass using mqtt 5"() { + given: + def client = buildExternalMqtt5Client() + when: + connectWith(client, "user", "wrongPassword") + then: + def ex = thrown CompletionException + def cause = ex.cause as Mqtt5ConnAckException + cause.mqttMessage.reasonCode == Mqtt5ConnAckReasonCode.BAD_USER_NAME_OR_PASSWORD + } + + def "mqtt 3.1.1 client should be generate session with one pending QoS 1 packet"() { + given: + def deviceId = generateClientId("device") + def serviceId = generateClientId("service") + def serviceName = "PublishRetryTest1" + def publisher = buildExternalMqtt5Client(deviceId) + def subscriber = buildMqtt311MockClient() + when: + publisher.connect().join() + subscriber.connect() + subscriber.send(new ConnectMqtt311OutMessage(serviceId, keepAlive)) + then: + with(subscriber.readNext() as ConnectAckMqttInMessage) { + reasonCode() == ConnectAckReasonCode.SUCCESS + } + when: + subscriber.send(new SubscribeMqtt311OutMessage( + 1, + Array.of(Subscription.minimal(TopicFilter.valueOf("service/$serviceName/device/+"), QoS.AT_LEAST_ONCE)))) + then: + with(subscriber.readNext() as SubscribeAckMqttInMessage) { + reasonCodes() + .stream() + .allMatch({ it == SubscribeAckReasonCode.GRANTED_QOS_1 }) + } + when: + publisher + .publishWith() + .topic("service/$serviceName/device/$deviceId") + .qos(MqttQos.AT_MOST_ONCE) + .payload(publishPayload) + .send() + .join() + then: + def receivedPublish = subscriber.readNext() as PublishMqttInMessage + with(receivedPublish) { + payload() == publishPayload + } + when: + subscriber.disconnect() + Thread.sleep(1_000) + subscriber.connect() + subscriber.send(new ConnectMqtt311OutMessage(serviceId, keepAlive)) + then: + with(subscriber.readNext() as ConnectAckMqttInMessage) { + reasonCode() == ConnectAckReasonCode.SUCCESS + } + with(subscriber.readNext() as PublishMqttInMessage) { + duplicate() + messageId() == receivedPublish.messageId() + payload() == publishPayload + } + cleanup: + subscriber.close() + publisher.disconnect().join() + } + + def "mqtt 5 client should be generate session with one pending QoS 1 packet"() { + given: + def deviceId = generateClientId("device") + def serviceId = generateClientId("service") + def serviceName = "PublishRetryTest2" + def publisher = buildExternalMqtt5Client(deviceId) + def subscriber = buildMqtt5MockClient() + when: + publisher.connect().join() + subscriber.connect() + subscriber.send(new ConnectMqtt5OutMessage(serviceId, keepAlive, 120)) + then: + with(subscriber.readNext() as ConnectAckMqttInMessage) { + reasonCode() == ConnectAckReasonCode.SUCCESS + } + when: + subscriber.send(new SubscribeMqtt5OutMessage( + 1, + Array.of(Subscription.minimal(TopicFilter.valueOf("service/$serviceName/device/+"), QoS.AT_LEAST_ONCE)))) + then: + with(subscriber.readNext() as SubscribeAckMqttInMessage) { + reasonCodes() + .stream() + .allMatch({ it == SubscribeAckReasonCode.GRANTED_QOS_1 }) + } + when: + publisher + .publishWith() + .topic("service/$serviceName/device/$deviceId") + .qos(MqttQos.AT_MOST_ONCE) + .payload(publishPayload) + .send() + .join() + then: + def receivedPublish = subscriber.readNext() as PublishMqttInMessage + with(receivedPublish) { + payload() == publishPayload + } + when: + subscriber.disconnect() + subscriber.connect() + subscriber.send(new ConnectMqtt5OutMessage(serviceId, keepAlive, 120)) + then: + with(subscriber.readNext() as ConnectAckMqttInMessage) { + reasonCode() == ConnectAckReasonCode.SUCCESS + } + with(subscriber.readNext() as PublishMqttInMessage) { + duplicate() + messageId() == receivedPublish.messageId() + payload() == publishPayload + } + cleanup: + subscriber.close() + publisher.disconnect().join() + } + + def "mqtt 3.1.1 client should be generate session with one pending QoS 2 packet"() { + given: + def deviceId = generateClientId("device") + def serviceId = generateClientId("service") + def serviceName = "PublishRetryTest3" + def publisher = buildExternalMqtt5Client(deviceId) + def subscriber = buildMqtt311MockClient() + when: + publisher.connect().join() + subscriber.connect() + subscriber.send(new ConnectMqtt311OutMessage(serviceId, keepAlive)) + then: + with(subscriber.readNext() as ConnectAckMqttInMessage) { + reasonCode() == ConnectAckReasonCode.SUCCESS + } + when: + subscriber.send(new SubscribeMqtt311OutMessage( + 1, + Array.of(Subscription.minimal(TopicFilter.valueOf("service/$serviceName/device/+"), QoS.EXACTLY_ONCE)))) + then: + with(subscriber.readNext() as SubscribeAckMqttInMessage) { + reasonCodes() + .stream() + .allMatch({ it == SubscribeAckReasonCode.GRANTED_QOS_2 }) + } + when: + publisher + .publishWith() + .topic("service/$serviceName/device/$deviceId") + .qos(MqttQos.AT_MOST_ONCE) + .payload(publishPayload) + .send() + .join() + then: + def receivedPublish = subscriber.readNext() as PublishMqttInMessage + with(receivedPublish) { + payload() == publishPayload + } + when: + subscriber.disconnect() + subscriber.connect() + subscriber.send(new ConnectMqtt311OutMessage(serviceId, keepAlive)) + then: + with(subscriber.readNext() as ConnectAckMqttInMessage) { + reasonCode() == ConnectAckReasonCode.SUCCESS + } + with(subscriber.readNext() as PublishMqttInMessage) { + duplicate() + messageId() == receivedPublish.messageId() + payload() == publishPayload + } + when: + subscriber.disconnect() + subscriber.connect() + subscriber.send(new ConnectMqtt311OutMessage(serviceId, keepAlive)) + subscriber.send(new PublishReceivedMqtt311OutMessage(receivedPublish.messageId())) + subscriber.send(new PublishCompleteMqtt311OutMessage(receivedPublish.messageId())) + then: + with(subscriber.readNext() as ConnectAckMqttInMessage) { + reasonCode() == ConnectAckReasonCode.SUCCESS + } + with(subscriber.readNext() as PublishMqttInMessage) { + duplicate() + messageId() == receivedPublish.messageId() + payload() == publishPayload + } + with(subscriber.readNext() as PublishReleaseMqttInMessage) { + messageId() == receivedPublish.messageId() + } + cleanup: + subscriber.close() + publisher.disconnect().join() + } + + def "mqtt 5 client should be generate session with one pending QoS 2 packet"() { + given: + def deviceId = generateClientId("device") + def serviceId = generateClientId("service") + def serviceName = "PublishRetryTest4" + def publisher = buildExternalMqtt5Client(deviceId) + def subscriber = buildMqtt5MockClient() + when: + publisher.connect().join() + subscriber.connect() + subscriber.send(new ConnectMqtt5OutMessage(serviceId, keepAlive, 120)) + then: + with(subscriber.readNext() as ConnectAckMqttInMessage) { + reasonCode() == ConnectAckReasonCode.SUCCESS + } + when: + subscriber.send(new SubscribeMqtt5OutMessage( + 1, + Array.of(Subscription.minimal(TopicFilter.valueOf("service/$serviceName/device/+"), QoS.EXACTLY_ONCE)))) + then: + with(subscriber.readNext() as SubscribeAckMqttInMessage) { + reasonCodes() + .stream() + .allMatch({ it == SubscribeAckReasonCode.GRANTED_QOS_2 }) + } + when: + publisher + .publishWith() + .topic("service/$serviceName/device/$deviceId") + .qos(MqttQos.AT_MOST_ONCE) + .payload(publishPayload) + .send() + .join() + then: + def receivedPublish = subscriber.readNext() as PublishMqttInMessage + with(receivedPublish) { + payload() == publishPayload + } + when: + subscriber.disconnect() + subscriber.connect() + subscriber.send(new ConnectMqtt5OutMessage(serviceId, keepAlive, 120)) + then: + with(subscriber.readNext() as ConnectAckMqttInMessage) { + reasonCode() == ConnectAckReasonCode.SUCCESS + } + with(subscriber.readNext() as PublishMqttInMessage) { + duplicate() + messageId() == receivedPublish.messageId() + payload() == publishPayload + } + when: + subscriber.disconnect() + subscriber.connect() + subscriber.send(new ConnectMqtt5OutMessage(serviceId, keepAlive, 120)) + subscriber.send(new PublishReceivedMqtt5OutMessage( + receivedPublish.messageId(), + PublishReceivedReasonCode.SUCCESS + )) + subscriber.send(new PublishCompleteMqtt5OutMessage( + receivedPublish.messageId(), + PublishCompletedReasonCode.SUCCESS + )) + then: + with(subscriber.readNext() as ConnectAckMqttInMessage) { + reasonCode() == ConnectAckReasonCode.SUCCESS + } + with(subscriber.readNext() as PublishMqttInMessage) { + duplicate() + messageId() == receivedPublish.messageId() + payload() == publishPayload + } + with(subscriber.readNext() as PublishReleaseMqttInMessage) { + messageId() == receivedPublish.messageId() + } + cleanup: + subscriber.close() + publisher.disconnect().join() + } + + def publish(Mqtt5AsyncClient publisher, String topicName, MqttQos qos) { + return publisher.publishWith() + .topic(topicName) + .qos(qos) + .payload(publishPayload) + .payloadFormatIndicator(Mqtt5PayloadFormatIndicator.UTF_8) + .send() + .join() + } + + def subscribe( + Mqtt5AsyncClient subscriber, + String topicFilter, + MqttQos qos, + CompletableFuture received) { + return subscriber.subscribeWith() + .topicFilter(topicFilter) + .qos(qos) + .callback({ publish -> received.complete(publish) }) + .send() + .join() + } + + def publish(Mqtt3AsyncClient publisher, String topicName, MqttQos qos) { + return publisher.publishWith() + .topic(topicName) + .qos(qos) + .payload(publishPayload) + .send() + .join() + } + + def subscribe( + Mqtt3AsyncClient subscriber, + String topicFilter, + MqttQos qos, + CompletableFuture received) { + return subscriber.subscribeWith() + .topicFilter(topicFilter) + .qos(qos) + .callback({ publish -> received.complete(publish) }) + .send() + .join() + } +} diff --git a/build.gradle b/build.gradle index 7fbcfd80..406c2fb0 100644 --- a/build.gradle +++ b/build.gradle @@ -6,6 +6,7 @@ buildscript { } dependencies { classpath "org.springframework.boot:spring-boot-gradle-plugin:4.0.2" + classpath "org.graalvm.buildtools:native-gradle-plugin:1.1.1" } } From e5ff5dedb86891bc73a875a938d09ed277ad9be6 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sat, 6 Jun 2026 10:57:58 +0200 Subject: [PATCH 02/11] feature: [172] Implement ClassPathUriResolver --- .../GroovyDslBasedAuthorizationService.java | 3 +- .../mqtt/base/util/ClassPathUriResolver.java | 89 +++++++++++++++++++ .../base/util/ClassPathUriResolverTest.groovy | 84 +++++++++++++++++ .../source/FileCredentialsSource.java | 3 +- 4 files changed, 177 insertions(+), 2 deletions(-) create mode 100644 base/src/main/java/javasabr/mqtt/base/util/ClassPathUriResolver.java create mode 100644 base/src/test/groovy/javasabr/mqtt/base/util/ClassPathUriResolverTest.groovy diff --git a/acl-service/src/main/java/javasabr/mqtt/acl/service/impl/GroovyDslBasedAuthorizationService.java b/acl-service/src/main/java/javasabr/mqtt/acl/service/impl/GroovyDslBasedAuthorizationService.java index ff439c3d..baa12711 100644 --- a/acl-service/src/main/java/javasabr/mqtt/acl/service/impl/GroovyDslBasedAuthorizationService.java +++ b/acl-service/src/main/java/javasabr/mqtt/acl/service/impl/GroovyDslBasedAuthorizationService.java @@ -5,6 +5,7 @@ import java.nio.file.Path; import java.util.Map; import javasabr.mqtt.acl.engine.AclEngine; +import javasabr.mqtt.base.util.ClassPathUriResolver; import javasabr.mqtt.acl.engine.exception.AclConfigurationException; import javasabr.mqtt.acl.engine.model.rule.AclRule; import javasabr.mqtt.acl.groovy.dsl.loader.AclRulesLoader; @@ -17,7 +18,7 @@ public class GroovyDslBasedAuthorizationService extends AclEngineBasedAuthorizationService { public void loadFrom(URI resource) { - Path localFile = Path.of(resource); + Path localFile = ClassPathUriResolver.resolveToPath(resource); if (Files.notExists(localFile)) { throw new AclConfigurationException("ACL configuration:[%s] doesn't exist".formatted(resource)); } else if (Files.isDirectory(localFile)) { diff --git a/base/src/main/java/javasabr/mqtt/base/util/ClassPathUriResolver.java b/base/src/main/java/javasabr/mqtt/base/util/ClassPathUriResolver.java new file mode 100644 index 00000000..ab4bb40a --- /dev/null +++ b/base/src/main/java/javasabr/mqtt/base/util/ClassPathUriResolver.java @@ -0,0 +1,89 @@ +package javasabr.mqtt.base.util; + +import java.io.IOException; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; + +public final class ClassPathUriResolver { + + private ClassPathUriResolver() {} + + public static Path resolveToPath(URI uri) { + if (uri == null) { + throw new NullPointerException("uri must not be null"); + } + + if ("classpath".equalsIgnoreCase(uri.getScheme())) { + return resolveClasspathUri(uri); + } + + return Path.of(uri); + } + + private static Path resolveClasspathUri(URI uri) { + String resourcePath = uri.getSchemeSpecificPart(); + if (resourcePath == null || resourcePath.isEmpty()) { + throw new IllegalArgumentException( + "classpath URI must have a non-empty resource path: " + uri); + } + + if (resourcePath.startsWith("/")) { + resourcePath = resourcePath.substring(1); + } + + var classLoader = Thread.currentThread().getContextClassLoader(); + if (classLoader == null) { + classLoader = ClassPathUriResolver.class.getClassLoader(); + } + + var systemResource = classLoader.getResource(resourcePath); + if (systemResource != null) { + URI resourceUri = URI.create(systemResource.toString()); + if ("file".equalsIgnoreCase(resourceUri.getScheme())) { + Path directPath = Path.of(resourceUri); + if (Files.exists(directPath)) { + return directPath; + } + } + } + + Path localPath = Path.of(resourcePath); + if (Files.exists(localPath)) { + return localPath; + } + + return extractToTempFile(classLoader, resourcePath); + } + + private static Path extractToTempFile(ClassLoader classLoader, String resourcePath) { + try (var inputStream = classLoader.getResourceAsStream(resourcePath)) { + if (inputStream == null) { + throw new IllegalArgumentException( + "Classpath resource not found: " + resourcePath); + } + + String fileName = extractFileName(resourcePath); + String suffix = extractSuffix(fileName); + + Path tempFile = Files.createTempFile("classpath-" + fileName.replace('.', '_'), suffix); + tempFile.toFile().deleteOnExit(); + Files.copy(inputStream, tempFile, StandardCopyOption.REPLACE_EXISTING); + return tempFile; + } catch (IOException e) { + throw new RuntimeException( + "Failed to extract classpath resource to temp file: " + resourcePath, e); + } + } + + private static String extractFileName(String resourcePath) { + int lastSlash = resourcePath.lastIndexOf('/'); + return lastSlash >= 0 ? resourcePath.substring(lastSlash + 1) : resourcePath; + } + + private static String extractSuffix(String fileName) { + int dotIndex = fileName.lastIndexOf('.'); + return dotIndex >= 0 ? fileName.substring(dotIndex) : ".tmp"; + } +} diff --git a/base/src/test/groovy/javasabr/mqtt/base/util/ClassPathUriResolverTest.groovy b/base/src/test/groovy/javasabr/mqtt/base/util/ClassPathUriResolverTest.groovy new file mode 100644 index 00000000..0ccfe355 --- /dev/null +++ b/base/src/test/groovy/javasabr/mqtt/base/util/ClassPathUriResolverTest.groovy @@ -0,0 +1,84 @@ +package javasabr.mqtt.base.util + +import javasabr.mqtt.test.support.UnitSpecification + +import java.nio.file.Files +import java.nio.file.Path + +class ClassPathUriResolverTest extends UnitSpecification { + + def "should resolve file URI to Path"() { + given: + def tempFile = Files.createTempFile("test", ".txt") + Files.writeString(tempFile, "hello") + def uri = tempFile.toUri() + when: + def result = ClassPathUriResolver.resolveToPath(uri) + then: + Files.exists(result) + Files.readString(result) == "hello" + cleanup: + Files.deleteIfExists(tempFile) + } + + def "should resolve classpath URI to existing Path"() { + given: + def uri = URI.create("classpath:javasabr/mqtt/base/util/ClassPathUriResolver.class") + when: + def result = ClassPathUriResolver.resolveToPath(uri) + then: + Files.exists(result) + Files.size(result) > 0 + } + + def "should handle classpath URI with leading slash"() { + given: + def uri = URI.create("classpath:/javasabr/mqtt/base/util/ClassPathUriResolver.class") + when: + def result = ClassPathUriResolver.resolveToPath(uri) + then: + Files.exists(result) + Files.size(result) > 0 + } + + def "should throw NullPointerException for null URI"() { + when: + ClassPathUriResolver.resolveToPath(null) + then: + thrown(NullPointerException) + } + + def "should throw IllegalArgumentException for missing classpath resource"() { + given: + def uri = URI.create("classpath:nonexistent/resource.txt") + when: + ClassPathUriResolver.resolveToPath(uri) + then: + thrown(IllegalArgumentException) + } + + def "should throw IllegalArgumentException for classpath URI with blank resource path"() { + given: + def uri = new URI("classpath", " ", null) + when: + ClassPathUriResolver.resolveToPath(uri) + then: + thrown(IllegalArgumentException) + } + + def "should resolve classpath URI via filesystem fallback when not on classloader"() { + given: + def testDir = Path.of("build/tmp/classpath-test") + Files.createDirectories(testDir) + def resourceFile = Files.writeString(testDir.resolve("data.txt"), "fallback content") + def uri = URI.create("classpath:build/tmp/classpath-test/data.txt") + when: + def result = ClassPathUriResolver.resolveToPath(uri) + then: + Files.exists(result) + Files.readString(result) == "fallback content" + cleanup: + Files.deleteIfExists(resourceFile) + Files.deleteIfExists(testDir) + } +} diff --git a/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java b/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java index 385fd3b0..c8cdd410 100644 --- a/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java +++ b/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java @@ -8,6 +8,7 @@ import java.util.Map; import javasabr.mqtt.auth.api.CredentialsSourceType; import javasabr.mqtt.auth.api.exception.CredentialsSourceException; +import javasabr.mqtt.base.util.ClassPathUriResolver; import javasabr.mqtt.base.util.DebugUtils; import lombok.AccessLevel; import lombok.experimental.FieldDefaults; @@ -24,7 +25,7 @@ public FileCredentialsSource(URI fileName) { } private void init() { - Path path = Path.of(fileName); + Path path = ClassPathUriResolver.resolveToPath(fileName); if (!Files.exists(path)) { throw new CredentialsSourceException("Credentials file:[%s] could not be found".formatted(fileName)); } From 7a200e6dab46e16217f122f9dbb2f6ac653871a7 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sat, 6 Jun 2026 10:59:30 +0200 Subject: [PATCH 03/11] feature: [172] Enable existing tests for native build --- application/build.gradle | 37 +- .../NativeImageVerificationTest.groovy | 872 +----------------- gradle/libs.versions.toml | 3 + 3 files changed, 60 insertions(+), 852 deletions(-) diff --git a/application/build.gradle b/application/build.gradle index 9e9707ca..191ec4e8 100644 --- a/application/build.gradle +++ b/application/build.gradle @@ -17,13 +17,13 @@ dependencies { implementation libs.springboot.starter.core implementation libs.springboot.starter.log4j2 - testImplementation projects.testSupport - testImplementation testFixtures(projects.network) runtimeOnly projects.credentialsSourceFile runtimeOnly projects.authenticationProviderBasic - + testImplementation projects.testSupport + testImplementation testFixtures(projects.network) testImplementation projects.credentialsSourceFile testImplementation projects.authenticationProviderBasic + testImplementation libs.junit.platform.testkit } tasks.withType(GroovyCompile).configureEach { @@ -64,24 +64,29 @@ graalvmNative { } } -def nativeCompileDir = layout.buildDirectory.dir('native/nativeCompile').get().asFile.absolutePath - tasks.register('copyNativeTestProperties', Copy) { from('src/test/resources/application-test.properties') { - rename 'application-test.properties', 'application.properties' - filter { line -> line.replace('classpath:auth/credentials-test', "file://${nativeCompileDir}/auth/credentials-test") } + rename 'application-test.properties', "application.properties" } from('src/test/resources/log4j2-test.xml') { - rename 'log4j2-test.xml', 'log4j2.xml' + rename 'log4j2-test.xml', "log4j2.xml" } - from('src/test/resources') { - include 'auth/credentials-test' + from('src/test/resources/auth/credentials-test') { + into 'auth' } - into layout.buildDirectory.dir('native/nativeCompile') + from('src/test/resources/test-acl.gacl') + into layout.buildDirectory.dir("native/nativeCompile") } -tasks.named('nativeCompile') { - dependsOn(':authentication-provider-basic:jar') - dependsOn(':credentials-source-file:jar') - finalizedBy copyNativeTestProperties -} \ No newline at end of file +tasks.named("nativeCompile") { + dependsOn ':authentication-provider-basic:jar', ':credentials-source-file:jar', 'processAotResources' +} + +tasks.register('nativeImageTest', Test) { + dependsOn 'nativeCompile', 'copyNativeTestProperties' + finalizedBy 'test' +} + +tasks.named('copyNativeTestProperties') { + mustRunAfter 'nativeCompile' +} diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/NativeImageVerificationTest.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/NativeImageVerificationTest.groovy index e0ab5938..c6f39577 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/NativeImageVerificationTest.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/NativeImageVerificationTest.groovy @@ -1,77 +1,42 @@ package javasabr.mqtt.broker.application -import com.hivemq.client.mqtt.MqttClient -import com.hivemq.client.mqtt.datatypes.MqttQos -import com.hivemq.client.mqtt.mqtt3.Mqtt3AsyncClient -import com.hivemq.client.mqtt.mqtt3.exceptions.Mqtt3ConnAckException -import com.hivemq.client.mqtt.mqtt3.message.Mqtt3MessageType -import com.hivemq.client.mqtt.mqtt3.message.connect.connack.Mqtt3ConnAckReturnCode -import com.hivemq.client.mqtt.mqtt3.message.publish.Mqtt3Publish -import com.hivemq.client.mqtt.mqtt3.message.subscribe.suback.Mqtt3SubAckReturnCode -import com.hivemq.client.mqtt.mqtt5.Mqtt5AsyncClient -import com.hivemq.client.mqtt.mqtt5.exceptions.Mqtt5ConnAckException -import com.hivemq.client.mqtt.mqtt5.message.Mqtt5MessageType -import com.hivemq.client.mqtt.mqtt5.message.connect.connack.Mqtt5ConnAckReasonCode -import com.hivemq.client.mqtt.mqtt5.message.publish.Mqtt5PayloadFormatIndicator -import com.hivemq.client.mqtt.mqtt5.message.publish.Mqtt5Publish -import com.hivemq.client.mqtt.mqtt5.message.subscribe.suback.Mqtt5SubAckReasonCode import groovy.util.logging.Slf4j -import javasabr.mqtt.model.* -import javasabr.mqtt.model.reason.code.ConnectAckReasonCode -import javasabr.mqtt.model.reason.code.PublishCompletedReasonCode -import javasabr.mqtt.model.reason.code.PublishReceivedReasonCode -import javasabr.mqtt.model.reason.code.SubscribeAckReasonCode -import javasabr.mqtt.model.subscription.Subscription -import javasabr.mqtt.model.topic.TopicFilter -import javasabr.mqtt.network.MqttConnection -import javasabr.mqtt.network.MqttMockClient -import javasabr.mqtt.network.message.in.ConnectAckMqttInMessage -import javasabr.mqtt.network.message.in.PublishMqttInMessage -import javasabr.mqtt.network.message.in.PublishReleaseMqttInMessage -import javasabr.mqtt.network.message.in.SubscribeAckMqttInMessage -import javasabr.mqtt.network.message.out.* -import javasabr.mqtt.network.user.ConfigurableNetworkMqttUser -import javasabr.rlib.collections.array.Array -import spock.lang.Ignore +import spock.lang.IgnoreIf import spock.lang.Shared import spock.lang.Specification +import spock.util.EmbeddedSpecRunner -import java.nio.charset.StandardCharsets -import java.util.concurrent.CompletableFuture -import java.util.concurrent.CompletionException import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit -import java.util.concurrent.atomic.AtomicReference -import static javasabr.mqtt.broker.application.MqttClientFactory.generateClientId +import static spock.util.EmbeddedSpecRunner.XFailure @Slf4j +@IgnoreIf({ !new File(BINARY_DIR, BINARY_NAME).exists() }) class NativeImageVerificationTest extends Specification { - private static final String BINARY_PATH = "build/native/nativeCompile/application" + private static final String BINARY_DIR = "build/native/nativeCompile" + private static final String BINARY_NAME = "application" private static final String NETWORK_READY_MARKER = "Started external MQTT network by address" - public static final encoding = StandardCharsets.UTF_8 - public static final publishPayload = "publishPayload".getBytes(encoding) - public static final testClientId = "testClientId" - public static final keepAlive = 120 - @Shared Process brokerProcess + @Shared + EmbeddedSpecRunner testRunner + def setupSpec() { brokerProcess = startBroker() + testRunner = new EmbeddedSpecRunner(throwFailure: false) } private static Process startBroker() { - def binaryPath = new File(BINARY_PATH).absolutePath + def binaryDir = new File(BINARY_DIR) + def applicationPropertiesPath = new File("src/test/resources/application-test.properties").absolutePath + def binaryPath = new File(binaryDir, BINARY_NAME).absolutePath Process process = new ProcessBuilder() - .command([ - binaryPath, - //"-Dlog4j.configurationFile=classpath:log4j2.xml", - //"--debug" - ]) - .directory(new File("build/native/nativeCompile/")) + .command(binaryPath, "--spring.config.location=file://${applicationPropertiesPath}") + .directory(binaryDir) .redirectErrorStream(true) .start() @@ -106,798 +71,33 @@ class NativeImageVerificationTest extends Specification { } } - def buildExternalMqtt311Client() { - return buildExternalMqtt311Client(generateClientId("mqtt311")) - } - - def buildExternalMqtt5Client() { - return buildExternalMqtt5Client(generateClientId("mqtt5")) - } - - def buildExternalMqtt311Client(String clientId) { - return MqttClient.builder() - .identifier(clientId) - .serverHost("localhost") - .serverPort(1883) - .useMqttVersion3() - .addDisconnectedListener { - println "[$clientId|mqtt311] disconnected:[${it.cause?.message}]" - } - .buildAsync() - } - - def buildExternalMqtt5Client(String clientId) { - return MqttClient.builder() - .identifier(clientId) - .serverHost("localhost") - .serverPort(1883) - .useMqttVersion5() - .addDisconnectedListener { - println "[$clientId|mqtt5] disconnected:[${it.cause?.message}]" - } - .buildAsync() - } - - def connectWith(Mqtt3AsyncClient client, String user, String pass) { - return client.connectWith() - .simpleAuth() - .username(user) - .password(pass.getBytes(encoding)) - .applySimpleAuth() - .send() - .join() - } - - def connectWith(Mqtt5AsyncClient client, String user, String pass) { - return client.connectWith() - .simpleAuth() - .username(user) - .password(pass.getBytes(encoding)) - .applySimpleAuth() - .send() - .join() - } - - def buildMqtt5MockClient() { - return new MqttMockClient( - "localhost", - 1883, - mqtt5MockedConnection() - ) - } - - def buildMqtt311MockClient() { - return new MqttMockClient( - "localhost", - 1883, - mqtt311MockedConnection() - ) - } - - def mqtt5MockedConnection() { - def serverConnConfig = new MqttServerConnectionConfig( - QoS.EXACTLY_ONCE, - MqttProperties.MAX_MESSAGE_SIZE_DEFAULT, - MqttProperties.MAX_MESSAGE_SIZE_DEFAULT / 2 as int, - MqttProperties.MAX_MESSAGE_SIZE_DEFAULT, - 10, - MqttProperties.SERVER_KEEP_ALIVE_MIN, - 10, - MqttProperties.TOPIC_ALIAS_MAX, - true, - true, - true, - true, - true, - true - ) - MqttClientConnectionConfig clientConnConfig = new MqttClientConnectionConfig( - serverConnConfig, - serverConnConfig.maxQos(), - MqttVersion.MQTT_5, - null, - serverConnConfig.receiveMaxPublishes(), - serverConnConfig.maxMessageSize(), - serverConnConfig.topicAliasMaxValue(), - MqttProperties.SERVER_KEEP_ALIVE_DEFAULT, - false, - false) - def connectionRef = new AtomicReference() - def connection = Stub(MqttConnection) { - isSupported(MqttVersion.MQTT_5) >> true - isSupported(MqttVersion.MQTT_3_1_1) >> true - serverConnectionConfig() >> serverConnConfig - clientConnectionConfig() >> clientConnConfig - user() >> Stub(ConfigurableNetworkMqttUser) { - connectionConfig() >> clientConnConfig - connection() >> connectionRef.get() - clientId() >> testClientId - } - } - connectionRef.set(connection) - return connection - } - - def mqtt311MockedConnection() { - def serverConnConfig = new MqttServerConnectionConfig( - QoS.EXACTLY_ONCE, - MqttProperties.MAX_MESSAGE_SIZE_DEFAULT, - MqttProperties.MAX_MESSAGE_SIZE_DEFAULT / 2 as int, - MqttProperties.MAX_MESSAGE_SIZE_DEFAULT, - 10, - MqttProperties.SERVER_KEEP_ALIVE_MIN, - 10, - MqttProperties.TOPIC_ALIAS_MAX, - true, - true, - true, - true, - true, - true - ) - MqttClientConnectionConfig clientConnConfig = new MqttClientConnectionConfig( - serverConnConfig, - serverConnConfig.maxQos(), - MqttVersion.MQTT_3_1_1, - null, - serverConnConfig.receiveMaxPublishes(), - serverConnConfig.maxMessageSize(), - serverConnConfig.topicAliasMaxValue(), - MqttProperties.SERVER_KEEP_ALIVE_DEFAULT, - false, - false) - def connectionRef = new AtomicReference() - def connection = Stub(MqttConnection) { - isSupported(MqttVersion.MQTT_5) >> false - isSupported(MqttVersion.MQTT_3_1_1) >> true - serverConnectionConfig() >> serverConnConfig - clientConnectionConfig() >> clientConnConfig - user() >> Stub(ConfigurableNetworkMqttUser) { - connectionConfig() >> clientConnConfig - connection() >> connectionRef.get() - clientId() >> testClientId - } - } - connectionRef.set(connection) - return connection - } - - def "should connect to native image"() { - given: - def client = MqttClient.builder() - .identifier(generateClientId("TLS5")) - .serverHost("localhost") - .serverPort(1883) - .useMqttVersion5() - .addDisconnectedListener { - println "[mqtt5] disconnected:[${it.cause?.message}]" - } - .buildAsync() - when: - def result = client.connect().join() - then: - result.reasonCode == Mqtt5ConnAckReasonCode.SUCCESS - cleanup: - client.disconnect().join() - } - - def "should deliver publish message QoS 0 using mqtt 3.1.1"() { - given: - def deviceId = generateClientId("device") - def serviceId = generateClientId("service") - def serviceName = "ConnectSubscribePublishTest1" - def received = new CompletableFuture() - def subscriber = buildExternalMqtt311Client(serviceId) - def publisher = buildExternalMqtt311Client(deviceId) - when: - subscriber.connect().join() - publisher.connect().join() - def subscribeResult = subscribe(subscriber, "service/$serviceName/device/+", MqttQos.AT_MOST_ONCE, received) - def publishResult = publish(publisher, "service/$serviceName/device/$deviceId", MqttQos.AT_MOST_ONCE) - then: - noExceptionThrown() - subscribeResult != null - subscribeResult.returnCodes.contains(Mqtt3SubAckReturnCode.SUCCESS_MAXIMUM_QOS_0) - subscribeResult.type == Mqtt3MessageType.SUBACK - publishResult != null - publishResult.qos == MqttQos.AT_MOST_ONCE - publishResult.type == Mqtt3MessageType.PUBLISH - received.join() != null - received.join().qos == MqttQos.AT_MOST_ONCE - received.join().type == Mqtt3MessageType.PUBLISH - cleanup: - subscriber.disconnect().join() - publisher.disconnect().join() - } - - def "should deliver publish message QoS 0 using mqtt 5"() { - given: - def deviceId = generateClientId("device") - def serviceId = generateClientId("service") - def serviceName = "ConnectSubscribePublishTest2" - def received = new CompletableFuture() - def subscriber = buildExternalMqtt5Client(serviceId) - def publisher = buildExternalMqtt5Client(deviceId) - when: - subscriber.connect().join() - publisher.connect().join() - def subscribeResult = subscribe(subscriber, "service/$serviceName/device/+", MqttQos.AT_MOST_ONCE, received) - def publishResult = publish(publisher, "service/$serviceName/device/$deviceId", MqttQos.AT_MOST_ONCE) - then: - noExceptionThrown() - subscribeResult != null - subscribeResult.reasonCodes.contains(Mqtt5SubAckReasonCode.GRANTED_QOS_0) - subscribeResult.type == Mqtt5MessageType.SUBACK - publishResult != null - publishResult.publish.qos == MqttQos.AT_MOST_ONCE - publishResult.publish.type == Mqtt5MessageType.PUBLISH - received.join() != null - received.join().qos == MqttQos.AT_MOST_ONCE - received.join().type == Mqtt5MessageType.PUBLISH - cleanup: - subscriber.disconnect().join() - publisher.disconnect().join() - } - - def "should deliver publish message QoS 1 using mqtt 3.1.1"() { - given: - def deviceId = generateClientId("device") - def serviceId = generateClientId("service") - def serviceName = "ConnectSubscribePublishTest3" - def received = new CompletableFuture() - def subscriber = buildExternalMqtt311Client(serviceId) - def publisher = buildExternalMqtt311Client(deviceId) - when: - subscriber.connect().join() - publisher.connect().join() - def subscribeResult = subscribe(subscriber, "service/$serviceName/device/+", MqttQos.AT_LEAST_ONCE, received) - def publishResult = publish(publisher, "service/$serviceName/device/$deviceId", MqttQos.AT_LEAST_ONCE) - then: - noExceptionThrown() - subscribeResult != null - subscribeResult.returnCodes.contains(Mqtt3SubAckReturnCode.SUCCESS_MAXIMUM_QOS_1) - subscribeResult.type == Mqtt3MessageType.SUBACK - publishResult != null - publishResult.qos == MqttQos.AT_LEAST_ONCE - publishResult.type == Mqtt3MessageType.PUBLISH - received.join() != null - received.join().qos == MqttQos.AT_LEAST_ONCE - received.join().type == Mqtt3MessageType.PUBLISH - cleanup: - subscriber.disconnect().join() - publisher.disconnect().join() - } - - def "should deliver publish message QoS 1 using mqtt 5"() { - given: - def deviceId = generateClientId("device") - def serviceId = generateClientId("service") - def serviceName = "ConnectSubscribePublishTest4" - def received = new CompletableFuture() - def subscriber = buildExternalMqtt5Client(serviceId) - def publisher = buildExternalMqtt5Client(deviceId) - when: - subscriber.connect().join() - publisher.connect().join() - def subscribeResult = subscribe(subscriber, "service/$serviceName/device/+", MqttQos.AT_LEAST_ONCE, received) - def publishResult = publish(publisher, "service/$serviceName/device/$deviceId", MqttQos.AT_LEAST_ONCE) - then: - noExceptionThrown() - subscribeResult != null - subscribeResult.reasonCodes.contains(Mqtt5SubAckReasonCode.GRANTED_QOS_1) - subscribeResult.type == Mqtt5MessageType.SUBACK - publishResult != null - publishResult.publish.qos == MqttQos.AT_LEAST_ONCE - publishResult.publish.type == Mqtt5MessageType.PUBLISH - received.join() != null - received.join().qos == MqttQos.AT_LEAST_ONCE - received.join().type == Mqtt5MessageType.PUBLISH - cleanup: - subscriber.disconnect().join() - publisher.disconnect().join() - } - - def "should deliver publish message QoS 2 using mqtt 3.1.1"() { - given: - def deviceId = generateClientId("device") - def serviceId = generateClientId("service") - def serviceName = "ConnectSubscribePublishTest5" - def received = new CompletableFuture() - def subscriber = buildExternalMqtt311Client(serviceId) - def publisher = buildExternalMqtt311Client(deviceId) - when: - subscriber.connect().join() - publisher.connect().join() - def subscribeResult = subscribe(subscriber, "service/$serviceName/device/+", MqttQos.EXACTLY_ONCE, received) - def publishResult = publish(publisher, "service/$serviceName/device/$deviceId", MqttQos.EXACTLY_ONCE) - then: - noExceptionThrown() - subscribeResult != null - subscribeResult.returnCodes.contains(Mqtt3SubAckReturnCode.SUCCESS_MAXIMUM_QOS_2) - subscribeResult.type == Mqtt3MessageType.SUBACK - publishResult != null - publishResult.qos == MqttQos.EXACTLY_ONCE - publishResult.type == Mqtt3MessageType.PUBLISH - received.join() != null - received.join().qos == MqttQos.EXACTLY_ONCE - received.join().type == Mqtt3MessageType.PUBLISH - cleanup: - subscriber.disconnect().join() - publisher.disconnect().join() - } - - def "should deliver publish message QoS 2 using mqtt 5"() { - given: - def deviceId = generateClientId("device") - def serviceId = generateClientId("service") - def serviceName = "ConnectSubscribePublishTest6" - def received = new CompletableFuture() - def subscriber = buildExternalMqtt5Client(serviceId) - def publisher = buildExternalMqtt5Client(deviceId) - when: - subscriber.connect().join() - publisher.connect().join() - def subscribeResult = subscribe(subscriber, "service/$serviceName/device/+", MqttQos.EXACTLY_ONCE, received) - def publishResult = publish(publisher, "service/$serviceName/device/$deviceId", MqttQos.EXACTLY_ONCE) - Thread.sleep(100) - then: - noExceptionThrown() - subscribeResult != null - subscribeResult.reasonCodes.contains(Mqtt5SubAckReasonCode.GRANTED_QOS_2) - subscribeResult.type == Mqtt5MessageType.SUBACK - publishResult != null - publishResult.publish.qos == MqttQos.EXACTLY_ONCE - publishResult.publish.type == Mqtt5MessageType.PUBLISH - received.join() != null - received.join().qos == MqttQos.EXACTLY_ONCE - received.join().type == Mqtt5MessageType.PUBLISH - cleanup: - subscriber.disconnect().join() - publisher.disconnect().join() - } - - def "client should connect to broker without user and pass using MQTT 3.1.1"() { - given: - def client = buildExternalMqtt311Client() - when: - def result = client.connect().join() - then: - result.returnCode == Mqtt3ConnAckReturnCode.SUCCESS - !result.sessionPresent - cleanup: - client.disconnect().join() - } - - def "client should connect to broker without user and pass using MQTT 5"() { - given: - def client = buildExternalMqtt5Client() - when: - def result = client.connect().join() - then: - result.reasonCode == Mqtt5ConnAckReasonCode.SUCCESS - !result.sessionExpiryInterval.present - result.serverKeepAlive.present - result.serverKeepAlive.getAsInt() == MqttProperties.SERVER_KEEP_ALIVE_DISABLED - !result.serverReference.present - !result.responseInformation.present - !result.assignedClientIdentifier.present - !result.sessionPresent - cleanup: - client.disconnect().join() - } - - def "client should connect to broker with user and pass using MQTT 3.1.1"() { - given: - def client = buildExternalMqtt311Client() - when: - def result = connectWith(client, 'user1', 'password') - then: - result.returnCode == Mqtt3ConnAckReturnCode.SUCCESS - !result.sessionPresent - cleanup: - client.disconnect().join() - } - - def "client should connect to broker with user and pass using MQTT 5"() { - given: - def client = buildExternalMqtt5Client() - when: - def result = connectWith(client, 'user1', 'password') - then: - result.reasonCode == Mqtt5ConnAckReasonCode.SUCCESS - !result.sessionExpiryInterval.present - result.serverKeepAlive.present - result.serverKeepAlive.getAsInt() == MqttProperties.SERVER_KEEP_ALIVE_DISABLED - !result.serverReference.present - !result.responseInformation.present - !result.assignedClientIdentifier.present - !result.sessionPresent - cleanup: - client.disconnect().join() - } - - def "client should not connect to broker without providing a client id using MQTT 3.1.1"() { + def "#test should pass without failures"(String test) { given: - def client = buildExternalMqtt311Client("") + def testSource = new File("src/test/groovy/javasabr/mqtt/broker/application/${test}.groovy") + .text + .replace( + "extends IntegrationSpecification", + "extends javasabr.mqtt.broker.application.NativeImageVerificationTest.WrapperSpec" + ) when: - client.connect().join() + def testResult = testRunner.run(testSource) then: - def ex = thrown CompletionException - def cause = ex.cause as Mqtt3ConnAckException - cause.mqttMessage.returnCode == Mqtt3ConnAckReturnCode.IDENTIFIER_REJECTED - } - - @Ignore("until finalizing clientId validation") - def "client should connect to broker without providing a client id using MQTT 5"() { - given: - def client = buildExternalMqtt5Client("") - when: - def result = client.connect().join() - then: - result.reasonCode == Mqtt5ConnAckReasonCode.SUCCESS - result.assignedClientIdentifier.present - result.assignedClientIdentifier.get().toString() != "" - cleanup: - client.disconnect().join() - } - - def "client should not connect to broker with invalid client id using MQTT 3.1.1"(String clientId) { - given: - def client = buildExternalMqtt311Client(clientId) - when: - client.connect().join() - then: - def ex = thrown CompletionException - def cause = ex.cause as Mqtt3ConnAckException - cause.mqttMessage.returnCode == Mqtt3ConnAckReturnCode.IDENTIFIER_REJECTED - where: - clientId << ["!@#!@*()^&"] - } - - def "client should not connect to broker with invalid client id using MQTT 5"(String clientId) { - given: - def client = buildExternalMqtt5Client(clientId) - when: - client.connect().join() - then: - def ex = thrown CompletionException - def cause = ex.cause as Mqtt5ConnAckException - cause.mqttMessage.reasonCode == Mqtt5ConnAckReasonCode.CLIENT_IDENTIFIER_NOT_VALID - where: - clientId << ["!@#!@*()^&"] - } - - def "client should not connect to broker with wrong pass using MQTT 3.1.1"() { - given: - def client = buildExternalMqtt311Client() - when: - connectWith(client, "user", "wrongPassword") - then: - def ex = thrown CompletionException - def cause = ex.cause as Mqtt3ConnAckException - cause.mqttMessage.returnCode == Mqtt3ConnAckReturnCode.BAD_USER_NAME_OR_PASSWORD - } - - def "client should not connect to broker with wrong pass using mqtt 5"() { - given: - def client = buildExternalMqtt5Client() - when: - connectWith(client, "user", "wrongPassword") - then: - def ex = thrown CompletionException - def cause = ex.cause as Mqtt5ConnAckException - cause.mqttMessage.reasonCode == Mqtt5ConnAckReasonCode.BAD_USER_NAME_OR_PASSWORD - } - - def "mqtt 3.1.1 client should be generate session with one pending QoS 1 packet"() { - given: - def deviceId = generateClientId("device") - def serviceId = generateClientId("service") - def serviceName = "PublishRetryTest1" - def publisher = buildExternalMqtt5Client(deviceId) - def subscriber = buildMqtt311MockClient() - when: - publisher.connect().join() - subscriber.connect() - subscriber.send(new ConnectMqtt311OutMessage(serviceId, keepAlive)) - then: - with(subscriber.readNext() as ConnectAckMqttInMessage) { - reasonCode() == ConnectAckReasonCode.SUCCESS - } - when: - subscriber.send(new SubscribeMqtt311OutMessage( - 1, - Array.of(Subscription.minimal(TopicFilter.valueOf("service/$serviceName/device/+"), QoS.AT_LEAST_ONCE)))) - then: - with(subscriber.readNext() as SubscribeAckMqttInMessage) { - reasonCodes() - .stream() - .allMatch({ it == SubscribeAckReasonCode.GRANTED_QOS_1 }) - } - when: - publisher - .publishWith() - .topic("service/$serviceName/device/$deviceId") - .qos(MqttQos.AT_MOST_ONCE) - .payload(publishPayload) - .send() - .join() - then: - def receivedPublish = subscriber.readNext() as PublishMqttInMessage - with(receivedPublish) { - payload() == publishPayload - } - when: - subscriber.disconnect() - Thread.sleep(1_000) - subscriber.connect() - subscriber.send(new ConnectMqtt311OutMessage(serviceId, keepAlive)) - then: - with(subscriber.readNext() as ConnectAckMqttInMessage) { - reasonCode() == ConnectAckReasonCode.SUCCESS - } - with(subscriber.readNext() as PublishMqttInMessage) { - duplicate() - messageId() == receivedPublish.messageId() - payload() == publishPayload - } - cleanup: - subscriber.close() - publisher.disconnect().join() - } - - def "mqtt 5 client should be generate session with one pending QoS 1 packet"() { - given: - def deviceId = generateClientId("device") - def serviceId = generateClientId("service") - def serviceName = "PublishRetryTest2" - def publisher = buildExternalMqtt5Client(deviceId) - def subscriber = buildMqtt5MockClient() - when: - publisher.connect().join() - subscriber.connect() - subscriber.send(new ConnectMqtt5OutMessage(serviceId, keepAlive, 120)) - then: - with(subscriber.readNext() as ConnectAckMqttInMessage) { - reasonCode() == ConnectAckReasonCode.SUCCESS + with(testResult.properties) { props -> + props.each { k, v -> println "$k: $v" } + if (props['failureCount'] > 0) { + props['failures'].each { XFailure f -> f.exception.printStackTrace() } + } + props['testsStartedCount'] > 0 + props['testsStartedCount'] == props['testsSucceededCount'] } - when: - subscriber.send(new SubscribeMqtt5OutMessage( - 1, - Array.of(Subscription.minimal(TopicFilter.valueOf("service/$serviceName/device/+"), QoS.AT_LEAST_ONCE)))) - then: - with(subscriber.readNext() as SubscribeAckMqttInMessage) { - reasonCodes() - .stream() - .allMatch({ it == SubscribeAckReasonCode.GRANTED_QOS_1 }) - } - when: - publisher - .publishWith() - .topic("service/$serviceName/device/$deviceId") - .qos(MqttQos.AT_MOST_ONCE) - .payload(publishPayload) - .send() - .join() - then: - def receivedPublish = subscriber.readNext() as PublishMqttInMessage - with(receivedPublish) { - payload() == publishPayload - } - when: - subscriber.disconnect() - subscriber.connect() - subscriber.send(new ConnectMqtt5OutMessage(serviceId, keepAlive, 120)) - then: - with(subscriber.readNext() as ConnectAckMqttInMessage) { - reasonCode() == ConnectAckReasonCode.SUCCESS - } - with(subscriber.readNext() as PublishMqttInMessage) { - duplicate() - messageId() == receivedPublish.messageId() - payload() == publishPayload - } - cleanup: - subscriber.close() - publisher.disconnect().join() - } - - def "mqtt 3.1.1 client should be generate session with one pending QoS 2 packet"() { - given: - def deviceId = generateClientId("device") - def serviceId = generateClientId("service") - def serviceName = "PublishRetryTest3" - def publisher = buildExternalMqtt5Client(deviceId) - def subscriber = buildMqtt311MockClient() - when: - publisher.connect().join() - subscriber.connect() - subscriber.send(new ConnectMqtt311OutMessage(serviceId, keepAlive)) - then: - with(subscriber.readNext() as ConnectAckMqttInMessage) { - reasonCode() == ConnectAckReasonCode.SUCCESS - } - when: - subscriber.send(new SubscribeMqtt311OutMessage( - 1, - Array.of(Subscription.minimal(TopicFilter.valueOf("service/$serviceName/device/+"), QoS.EXACTLY_ONCE)))) - then: - with(subscriber.readNext() as SubscribeAckMqttInMessage) { - reasonCodes() - .stream() - .allMatch({ it == SubscribeAckReasonCode.GRANTED_QOS_2 }) - } - when: - publisher - .publishWith() - .topic("service/$serviceName/device/$deviceId") - .qos(MqttQos.AT_MOST_ONCE) - .payload(publishPayload) - .send() - .join() - then: - def receivedPublish = subscriber.readNext() as PublishMqttInMessage - with(receivedPublish) { - payload() == publishPayload - } - when: - subscriber.disconnect() - subscriber.connect() - subscriber.send(new ConnectMqtt311OutMessage(serviceId, keepAlive)) - then: - with(subscriber.readNext() as ConnectAckMqttInMessage) { - reasonCode() == ConnectAckReasonCode.SUCCESS - } - with(subscriber.readNext() as PublishMqttInMessage) { - duplicate() - messageId() == receivedPublish.messageId() - payload() == publishPayload - } - when: - subscriber.disconnect() - subscriber.connect() - subscriber.send(new ConnectMqtt311OutMessage(serviceId, keepAlive)) - subscriber.send(new PublishReceivedMqtt311OutMessage(receivedPublish.messageId())) - subscriber.send(new PublishCompleteMqtt311OutMessage(receivedPublish.messageId())) - then: - with(subscriber.readNext() as ConnectAckMqttInMessage) { - reasonCode() == ConnectAckReasonCode.SUCCESS - } - with(subscriber.readNext() as PublishMqttInMessage) { - duplicate() - messageId() == receivedPublish.messageId() - payload() == publishPayload - } - with(subscriber.readNext() as PublishReleaseMqttInMessage) { - messageId() == receivedPublish.messageId() - } - cleanup: - subscriber.close() - publisher.disconnect().join() - } - - def "mqtt 5 client should be generate session with one pending QoS 2 packet"() { - given: - def deviceId = generateClientId("device") - def serviceId = generateClientId("service") - def serviceName = "PublishRetryTest4" - def publisher = buildExternalMqtt5Client(deviceId) - def subscriber = buildMqtt5MockClient() - when: - publisher.connect().join() - subscriber.connect() - subscriber.send(new ConnectMqtt5OutMessage(serviceId, keepAlive, 120)) - then: - with(subscriber.readNext() as ConnectAckMqttInMessage) { - reasonCode() == ConnectAckReasonCode.SUCCESS - } - when: - subscriber.send(new SubscribeMqtt5OutMessage( - 1, - Array.of(Subscription.minimal(TopicFilter.valueOf("service/$serviceName/device/+"), QoS.EXACTLY_ONCE)))) - then: - with(subscriber.readNext() as SubscribeAckMqttInMessage) { - reasonCodes() - .stream() - .allMatch({ it == SubscribeAckReasonCode.GRANTED_QOS_2 }) - } - when: - publisher - .publishWith() - .topic("service/$serviceName/device/$deviceId") - .qos(MqttQos.AT_MOST_ONCE) - .payload(publishPayload) - .send() - .join() - then: - def receivedPublish = subscriber.readNext() as PublishMqttInMessage - with(receivedPublish) { - payload() == publishPayload - } - when: - subscriber.disconnect() - subscriber.connect() - subscriber.send(new ConnectMqtt5OutMessage(serviceId, keepAlive, 120)) - then: - with(subscriber.readNext() as ConnectAckMqttInMessage) { - reasonCode() == ConnectAckReasonCode.SUCCESS - } - with(subscriber.readNext() as PublishMqttInMessage) { - duplicate() - messageId() == receivedPublish.messageId() - payload() == publishPayload - } - when: - subscriber.disconnect() - subscriber.connect() - subscriber.send(new ConnectMqtt5OutMessage(serviceId, keepAlive, 120)) - subscriber.send(new PublishReceivedMqtt5OutMessage( - receivedPublish.messageId(), - PublishReceivedReasonCode.SUCCESS - )) - subscriber.send(new PublishCompleteMqtt5OutMessage( - receivedPublish.messageId(), - PublishCompletedReasonCode.SUCCESS - )) - then: - with(subscriber.readNext() as ConnectAckMqttInMessage) { - reasonCode() == ConnectAckReasonCode.SUCCESS - } - with(subscriber.readNext() as PublishMqttInMessage) { - duplicate() - messageId() == receivedPublish.messageId() - payload() == publishPayload - } - with(subscriber.readNext() as PublishReleaseMqttInMessage) { - messageId() == receivedPublish.messageId() - } - cleanup: - subscriber.close() - publisher.disconnect().join() - } - - def publish(Mqtt5AsyncClient publisher, String topicName, MqttQos qos) { - return publisher.publishWith() - .topic(topicName) - .qos(qos) - .payload(publishPayload) - .payloadFormatIndicator(Mqtt5PayloadFormatIndicator.UTF_8) - .send() - .join() + where: + test << ["ConnectSubscribePublishTest", "ExternalConnectionTest", "PublishRetryTest"] } - def subscribe( - Mqtt5AsyncClient subscriber, - String topicFilter, - MqttQos qos, - CompletableFuture received) { - return subscriber.subscribeWith() - .topicFilter(topicFilter) - .qos(qos) - .callback({ publish -> received.complete(publish) }) - .send() - .join() - } + static class WrapperSpec extends IntegrationSpecification { - def publish(Mqtt3AsyncClient publisher, String topicName, MqttQos qos) { - return publisher.publishWith() - .topic(topicName) - .qos(qos) - .payload(publishPayload) - .send() - .join() - } - - def subscribe( - Mqtt3AsyncClient subscriber, - String topicFilter, - MqttQos qos, - CompletableFuture received) { - return subscriber.subscribeWith() - .topicFilter(topicFilter) - .qos(qos) - .callback({ publish -> received.complete(publish) }) - .send() - .join() + def setup() { + externalPlainNetworkAddress = InetSocketAddress.createUnresolved("localhost", 1883) + } } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 51806111..2dc9d7ae 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -23,6 +23,8 @@ flyway="11.19.0" spock = "2.4-M6-groovy-4.0" # https://mvnrepository.com/artifact/org.apache.groovy/groovy-all groovy = "4.0.28" +# https://mvnrepository.com/artifact/org.junit/junit-bom +junit = "6.0.2" # https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web springboot = '4.0.6' # https://mvnrepository.com/artifact/org.springframework/spring-core @@ -87,6 +89,7 @@ spock-core = { module = "org.spockframework:spock-core", version.ref = "spock" } spock-spring = { module = "org.spockframework:spock-spring", version.ref = "spock" } groovy-core = { module = "org.apache.groovy:groovy", version.ref = "groovy" } groovy-all = { module = "org.apache.groovy:groovy-all", version.ref = "groovy" } +junit-platform-testkit = { module = "org.junit.platform:junit-platform-testkit", version.ref = "junit" } byte-buddy-dep = { module = "net.bytebuddy:byte-buddy-dep", version.ref = "byte-buddy" } objenesis = { module = "org.objenesis:objenesis", version.ref = "objenesis" } hivemq-mqtt-client = { module = "com.hivemq:hivemq-mqtt-client", version.ref = "hivemq-mqtt-client" } From a36d5b5b25bfe366cb700e8fe318bb93ec505e5a Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sun, 7 Jun 2026 00:03:42 +0200 Subject: [PATCH 04/11] feature: [172] Refactoring of native image test and build.gradle --- application/build.gradle | 39 ++--- .../NativeImageVerificationTest.groovy | 158 +++++++++++++----- .../TestSslPropertiesInitializer.groovy | 17 +- 3 files changed, 138 insertions(+), 76 deletions(-) diff --git a/application/build.gradle b/application/build.gradle index 191ec4e8..27912622 100644 --- a/application/build.gradle +++ b/application/build.gradle @@ -19,10 +19,9 @@ dependencies { runtimeOnly projects.credentialsSourceFile runtimeOnly projects.authenticationProviderBasic + testImplementation projects.testSupport testImplementation testFixtures(projects.network) - testImplementation projects.credentialsSourceFile - testImplementation projects.authenticationProviderBasic testImplementation libs.junit.platform.testkit } @@ -32,7 +31,6 @@ tasks.withType(GroovyCompile).configureEach { } bootRun { - mainClass = "javasabr.mqtt.broker.application.MqttBrokerApplication" jvmArgs += "--enable-preview" } @@ -40,21 +38,11 @@ bootJar { mainClass = "javasabr.mqtt.broker.application.MqttBrokerApplication" } -jar { - from(sourceSets.aot.output) -} - -tasks.named('processAot') { - jvmArgs += '-Dauthentication.credentials-source.file.enabled=true' - jvmArgs += '-Dauthentication.provider.basic.enabled=true' -} - graalvmNative { toolchainDetection = true binaries { main { sharedLibrary = false - mainClass = "javasabr.mqtt.broker.application.MqttBrokerApplication" buildArgs.add("--enable-preview") javaLauncher = javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(25) @@ -64,7 +52,20 @@ graalvmNative { } } -tasks.register('copyNativeTestProperties', Copy) { +jar { + from(sourceSets.aot.output) +} + +processAot { + jvmArgs([ + '-Dauthentication.credentials-source.file.enabled=true', + '-Dauthentication.provider.basic.enabled=true', + '-Dmqtt.external.tls.network.enabled=true', + ]) +} + +tasks.register('copyNativeImageTestResources', Copy) { + mustRunAfter 'nativeCompile' from('src/test/resources/application-test.properties') { rename 'application-test.properties', "application.properties" } @@ -78,15 +79,7 @@ tasks.register('copyNativeTestProperties', Copy) { into layout.buildDirectory.dir("native/nativeCompile") } -tasks.named("nativeCompile") { - dependsOn ':authentication-provider-basic:jar', ':credentials-source-file:jar', 'processAotResources' -} - tasks.register('nativeImageTest', Test) { - dependsOn 'nativeCompile', 'copyNativeTestProperties' + dependsOn 'nativeCompile', 'copyNativeImageTestResources' finalizedBy 'test' } - -tasks.named('copyNativeTestProperties') { - mustRunAfter 'nativeCompile' -} diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/NativeImageVerificationTest.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/NativeImageVerificationTest.groovy index c6f39577..258d878e 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/NativeImageVerificationTest.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/NativeImageVerificationTest.groovy @@ -1,7 +1,10 @@ package javasabr.mqtt.broker.application import groovy.util.logging.Slf4j -import spock.lang.IgnoreIf +import org.junit.platform.engine.TestExecutionResult +import org.junit.platform.testkit.engine.EngineExecutionResults +import org.junit.platform.testkit.engine.EventType +import spock.lang.Requires import spock.lang.Shared import spock.lang.Specification import spock.util.EmbeddedSpecRunner @@ -9,10 +12,12 @@ import spock.util.EmbeddedSpecRunner import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit +import static javasabr.mqtt.broker.application.NativeImageVerificationTest.ConsoleStyle.color +import static javasabr.mqtt.broker.application.NativeImageVerificationTest.ConsoleStyle.italic import static spock.util.EmbeddedSpecRunner.XFailure @Slf4j -@IgnoreIf({ !new File(BINARY_DIR, BINARY_NAME).exists() }) +@Requires({ new File(BINARY_DIR, BINARY_NAME).exists() }) class NativeImageVerificationTest extends Specification { private static final String BINARY_DIR = "build/native/nativeCompile" @@ -26,78 +31,141 @@ class NativeImageVerificationTest extends Specification { EmbeddedSpecRunner testRunner def setupSpec() { - brokerProcess = startBroker() + brokerProcess = new ProcessBuilder() + .command(buildBrokerStartupCommand()) + .directory(new File(BINARY_DIR)) + .redirectErrorStream(true) + .start() + awaitBrokerStartup(brokerProcess, 1, TimeUnit.SECONDS) testRunner = new EmbeddedSpecRunner(throwFailure: false) } - private static Process startBroker() { + def cleanupSpec() { + try { + if (brokerProcess != null && brokerProcess.isAlive()) { + brokerProcess.destroy() + brokerProcess.waitFor(5, TimeUnit.SECONDS) + } + } finally { + if (brokerProcess.isAlive()) { + brokerProcess.destroyForcibly() + } + } + } + + def "#test should pass without failures"(String test) { + given: + def testSource = new File("src/test/groovy/javasabr/mqtt/broker/application/${test}.groovy") + .text + .replace( + "extends TlsIntegrationSpecification", + "extends javasabr.mqtt.broker.application.NativeImageVerificationTest.TlsStandaloneBrokerRouterSpec" + ) + .replace( + "extends IntegrationSpecification", + "extends javasabr.mqtt.broker.application.NativeImageVerificationTest.StandaloneBrokerRouterSpec" + ) + when: + def testResult = testRunner.run(testSource) + then: + with(testResult.properties) { results -> + printTestResults(results) + results['testsStartedCount'] > 0 + results['testsStartedCount'] == results['testsSucceededCount'] + } + where: + test << ["ConnectSubscribePublishTest", "ExternalConnectionTest", "PublishRetryTest", "TlsCommunicationTest"] + } + + + private static String[] buildBrokerStartupCommand() { def binaryDir = new File(BINARY_DIR) def applicationPropertiesPath = new File("src/test/resources/application-test.properties").absolutePath def binaryPath = new File(binaryDir, BINARY_NAME).absolutePath - Process process = new ProcessBuilder() - .command(binaryPath, "--spring.config.location=file://${applicationPropertiesPath}") - .directory(binaryDir) - .redirectErrorStream(true) - .start() - def networkReady = new CountDownLatch(1) + return TestSslPropertiesInitializer.getProps() + .collect { key, value -> "--${key}=${value}" as String } + .plus(0, [ + binaryPath, + "--spring.config.location=file://${applicationPropertiesPath}" as String, + "--mqtt.external.tls.network.enabled=true", + "--mqtt.external.tls.require-client-cert=false" + ]) + .toArray(String[]::new) + } + private static void awaitBrokerStartup(Process process, long amount, TimeUnit unit) { + def networkReady = new CountDownLatch(1) Thread.startDaemon("broker-output-drainer") { def reader = new BufferedReader(new InputStreamReader(process.getInputStream())) String line while ((line = reader.readLine()) != null) { - println(line) + println "[Standalone Broker] ${line}" if (line.contains(NETWORK_READY_MARKER)) { networkReady.countDown() } } } - - if (!networkReady.await(10, TimeUnit.SECONDS)) { + if (!networkReady.await(amount, unit)) { process.destroyForcibly() throw new RuntimeException("Broker failed to start within 10 seconds: network not ready") } + } + + static void printTestResults(Map props) { + (props['results'] as EngineExecutionResults).testEvents() + .stream() + .filter { it.type == EventType.FINISHED } + .each { event -> + def testMethod = event.testDescriptor.displayName + def status = event.payload + .map { it as TestExecutionResult } + .map { it.status } + .orElse(null) + log.info "'${italic(testMethod)}' ${color(status)}" + } - return process + if (props['failureCount'] > 0) { + props['failures'].each { XFailure f -> f.exception.printStackTrace() } + } } - def cleanupSpec() { - if (brokerProcess != null && brokerProcess.isAlive()) { - brokerProcess.destroy() - brokerProcess.waitFor(5, TimeUnit.SECONDS) - if (brokerProcess.isAlive()) { - brokerProcess.destroyForcibly() - } + static class StandaloneBrokerRouterSpec extends IntegrationSpecification { + def setup() { + externalPlainNetworkAddress = InetSocketAddress.createUnresolved("localhost", 1883) } } - def "#test should pass without failures"(String test) { - given: - def testSource = new File("src/test/groovy/javasabr/mqtt/broker/application/${test}.groovy") - .text - .replace( - "extends IntegrationSpecification", - "extends javasabr.mqtt.broker.application.NativeImageVerificationTest.WrapperSpec" - ) - when: - def testResult = testRunner.run(testSource) - then: - with(testResult.properties) { props -> - props.each { k, v -> println "$k: $v" } - if (props['failureCount'] > 0) { - props['failures'].each { XFailure f -> f.exception.printStackTrace() } - } - props['testsStartedCount'] > 0 - props['testsStartedCount'] == props['testsSucceededCount'] - } - where: - test << ["ConnectSubscribePublishTest", "ExternalConnectionTest", "PublishRetryTest"] + static class TlsStandaloneBrokerRouterSpec extends TlsIntegrationSpecification { + def setup() { + externalTlsNetworkAddress = InetSocketAddress.createUnresolved("localhost", 8883) + } } - static class WrapperSpec extends IntegrationSpecification { + enum ConsoleStyle { + RED("\u001B[31m", TestExecutionResult.Status.FAILED), + GREEN("\u001B[32m", TestExecutionResult.Status.SUCCESSFUL), + YELLOW("\u001B[33m", TestExecutionResult.Status.ABORTED); - def setup() { - externalPlainNetworkAddress = InetSocketAddress.createUnresolved("localhost", 1883) + static final Map CACHE = values().collectEntries { [it.status, it.anchor] } + + static final String RESET = "\u001B[0m" + static final String ITALIC = "\u001B[3m" + + String anchor; + TestExecutionResult.Status status; + + ConsoleStyle(String anchor, TestExecutionResult.Status status) { + this.anchor = anchor + this.status = status + } + + static String color(TestExecutionResult.Status status) { + return "${CACHE.getOrDefault(status, RESET)}${status}${RESET}" + } + + static String italic(String text) { + return "${ITALIC}${text}${RESET}" } } } diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/TestSslPropertiesInitializer.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/TestSslPropertiesInitializer.groovy index 8bf994f7..e32e4f59 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/TestSslPropertiesInitializer.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/TestSslPropertiesInitializer.groovy @@ -7,16 +7,17 @@ import org.springframework.core.env.MapPropertySource class TestSslPropertiesInitializer implements ApplicationContextInitializer { - TestSslContexts sslContexts = TestSslContexts.getInstance() - @Override void initialize(ConfigurableApplicationContext applicationContext) { - def props = [ - "mqtt.external.tls.keystore-path": sslContexts.serverKeystorePath.toString(), - "mqtt.external.tls.keystore-password": sslContexts.password, - "mqtt.external.tls.truststore-path": sslContexts.truststore.toString(), - "mqtt.external.tls.truststore-password": sslContexts.password + applicationContext.environment.propertySources.addFirst(new MapPropertySource("tlsProps", getProps())) + } + + static Map getProps() { + return [ + "mqtt.external.tls.keystore-path": TestSslContexts.getInstance().serverKeystorePath.toString(), + "mqtt.external.tls.keystore-password": TestSslContexts.getInstance().password, + "mqtt.external.tls.truststore-path": TestSslContexts.getInstance().truststore.toString(), + "mqtt.external.tls.truststore-password": TestSslContexts.getInstance().password ] - applicationContext.environment.propertySources.addFirst(new MapPropertySource("tlsProps", props)) } } From 31ad63c6b79bca24585d7e402c394bd01164bce9 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Sun, 7 Jun 2026 09:46:06 +0200 Subject: [PATCH 05/11] feature: [172] Simplify resource loading process --- .../exception/AclConfigurationException.java | 4 + .../groovy/dsl/loader/AclRulesLoader.groovy | 16 +--- .../dsl/loader/AclRulesLoaderTest.groovy | 23 ++--- .../GroovyDslBasedAuthorizationService.java | 21 +++-- .../base/util/ClassPathResourceResolver.java | 35 ++++++++ .../mqtt/base/util/ClassPathUriResolver.java | 89 ------------------- .../util/ClassPathResourceResolverTest.groovy | 81 +++++++++++++++++ .../base/util/ClassPathUriResolverTest.groovy | 84 ----------------- .../source/FileCredentialsSource.java | 11 +-- 9 files changed, 147 insertions(+), 217 deletions(-) create mode 100644 base/src/main/java/javasabr/mqtt/base/util/ClassPathResourceResolver.java delete mode 100644 base/src/main/java/javasabr/mqtt/base/util/ClassPathUriResolver.java create mode 100644 base/src/test/groovy/javasabr/mqtt/base/util/ClassPathResourceResolverTest.groovy delete mode 100644 base/src/test/groovy/javasabr/mqtt/base/util/ClassPathUriResolverTest.groovy diff --git a/acl-engine/src/main/java/javasabr/mqtt/acl/engine/exception/AclConfigurationException.java b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/exception/AclConfigurationException.java index 0c3d6f36..67cfd46e 100644 --- a/acl-engine/src/main/java/javasabr/mqtt/acl/engine/exception/AclConfigurationException.java +++ b/acl-engine/src/main/java/javasabr/mqtt/acl/engine/exception/AclConfigurationException.java @@ -5,4 +5,8 @@ public class AclConfigurationException extends RuntimeException { public AclConfigurationException(String message) { super(message); } + + public AclConfigurationException(String message, Throwable cause) { + super(message, cause); + } } diff --git a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/loader/AclRulesLoader.groovy b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/loader/AclRulesLoader.groovy index e8d7e59d..54584999 100644 --- a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/loader/AclRulesLoader.groovy +++ b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/acl/groovy/dsl/loader/AclRulesLoader.groovy @@ -1,26 +1,14 @@ package javasabr.mqtt.acl.groovy.dsl.loader import javasabr.mqtt.acl.engine.builder.RuleContainerBuilder -import javasabr.mqtt.acl.engine.exception.AclConfigurationException import javasabr.mqtt.acl.engine.model.rule.AclRule import javasabr.mqtt.acl.groovy.dsl.builder.AclRulesBuilder import javasabr.mqtt.model.acl.Operation import javasabr.rlib.collections.array.Array -import java.nio.file.Files -import java.nio.file.Path - class AclRulesLoader { - static Map> load(String aclConfigPath) { - return load(Path.of(aclConfigPath)) - } - - static Map> load(Path aclConfigPath) { - if (Files.notExists(aclConfigPath)) { - throw new AclConfigurationException("Config file:[%s] doesn't exist".formatted(aclConfigPath)) - } - + static Map> load(InputStream aclConfigPath) { AclRulesBuilder aclRulesBuilder = new AclRulesBuilder() def binding = new Binding() @@ -32,7 +20,7 @@ class AclRulesLoader { } def groovyShell = new GroovyShell(binding) - groovyShell.evaluate(aclConfigPath.toFile()) + groovyShell.evaluate(new InputStreamReader(aclConfigPath)) return RuleContainerBuilder.groupRulesByOperation(aclRulesBuilder.build()) } diff --git a/acl-groovy-dsl/src/test/groovy/javasabr/mqtt/acl/groovy/dsl/loader/AclRulesLoaderTest.groovy b/acl-groovy-dsl/src/test/groovy/javasabr/mqtt/acl/groovy/dsl/loader/AclRulesLoaderTest.groovy index bdfcacca..d843c22b 100644 --- a/acl-groovy-dsl/src/test/groovy/javasabr/mqtt/acl/groovy/dsl/loader/AclRulesLoaderTest.groovy +++ b/acl-groovy-dsl/src/test/groovy/javasabr/mqtt/acl/groovy/dsl/loader/AclRulesLoaderTest.groovy @@ -25,6 +25,9 @@ import javasabr.mqtt.service.acl.TestRulesGenerator import javasabr.mqtt.test.support.UnitSpecification import javasabr.rlib.collections.array.Array +import java.nio.file.Files +import java.nio.file.NoSuchFileException +import java.nio.file.Path import java.util.concurrent.CompletionException import static javasabr.mqtt.acl.engine.model.Action.ALLOW @@ -38,29 +41,29 @@ class AclRulesLoaderTest extends UnitSpecification { given: def ruleFile = TestRulesGenerator.generate(100) when: - def load = AclRulesLoader.load(ruleFile.toString()) + def load = AclRulesLoader.load(new FileInputStream(ruleFile)) then: load.get(SUBSCRIBE).size() == 50 load.get(PUBLISH).size() == 50 ruleFile.delete() } - def "should throw exception if config not exists"(String configPath, String errorMessage) { + def "should throw exception if config not exists"(String configPath) { when: - AclRulesLoader.load(configPath) + AclRulesLoader.load(Files.newInputStream(Path.of(configPath))) then: - def exception = thrown(AclConfigurationException) - exception.message == errorMessage + def exception = thrown(NoSuchFileException) + exception.message == configPath where: - configPath | errorMessage - "not/existed/path" | 'Config file:[not/existed/path] doesn\'t exist' + configPath | _ + "not/existed/path" | _ } def "should work fine with only publish rules"() { given: def onlyPublishRulesAclPath = getAbsolutePath("acl/config/acl-publish-only.gacl") when: - def ruleMap = AclRulesLoader.load(onlyPublishRulesAclPath) + def ruleMap = AclRulesLoader.load(Files.newInputStream(Path.of(onlyPublishRulesAclPath))) then: noExceptionThrown() !ruleMap.get(PUBLISH).isEmpty() @@ -71,7 +74,7 @@ class AclRulesLoaderTest extends UnitSpecification { given: def invalidAclPath = getAbsolutePath("acl/config/invalid/${invalidAclFileName}") when: - AclRulesLoader.load(invalidAclPath) + AclRulesLoader.load(Files.newInputStream(Path.of(invalidAclPath))) then: def exception = thrown CompletionException exceptionClass.isInstance exception.cause @@ -98,7 +101,7 @@ class AclRulesLoaderTest extends UnitSpecification { def "should parse Groovy DSL config"() { when: def absolutePath = getAbsolutePath("acl/config/acl.gacl") - def rules = AclRulesLoader.load(absolutePath) + def rules = AclRulesLoader.load(Files.newInputStream(Path.of(absolutePath))) then: verifyAll(rules.get(PUBLISH)) { size() == 3 diff --git a/acl-service/src/main/java/javasabr/mqtt/acl/service/impl/GroovyDslBasedAuthorizationService.java b/acl-service/src/main/java/javasabr/mqtt/acl/service/impl/GroovyDslBasedAuthorizationService.java index baa12711..1886488f 100644 --- a/acl-service/src/main/java/javasabr/mqtt/acl/service/impl/GroovyDslBasedAuthorizationService.java +++ b/acl-service/src/main/java/javasabr/mqtt/acl/service/impl/GroovyDslBasedAuthorizationService.java @@ -1,15 +1,15 @@ package javasabr.mqtt.acl.service.impl; +import java.io.IOException; +import java.io.InputStream; import java.net.URI; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.Map; import javasabr.mqtt.acl.engine.AclEngine; -import javasabr.mqtt.base.util.ClassPathUriResolver; import javasabr.mqtt.acl.engine.exception.AclConfigurationException; import javasabr.mqtt.acl.engine.model.rule.AclRule; import javasabr.mqtt.acl.groovy.dsl.loader.AclRulesLoader; import javasabr.mqtt.acl.service.AclEngineBasedAuthorizationService; +import javasabr.mqtt.base.util.ClassPathResourceResolver; import javasabr.mqtt.model.acl.Operation; import javasabr.rlib.collections.array.Array; import lombok.CustomLog; @@ -18,15 +18,14 @@ public class GroovyDslBasedAuthorizationService extends AclEngineBasedAuthorizationService { public void loadFrom(URI resource) { - Path localFile = ClassPathUriResolver.resolveToPath(resource); - if (Files.notExists(localFile)) { - throw new AclConfigurationException("ACL configuration:[%s] doesn't exist".formatted(resource)); - } else if (Files.isDirectory(localFile)) { - throw new AclConfigurationException("ACL configuration:[%s] is directory".formatted(resource)); + try { + InputStream localFile = ClassPathResourceResolver.newInputStream(resource); + Map> loadedAclRulesMap = AclRulesLoader.load(localFile); + switchTo(new AclEngine(loadedAclRulesMap)); + log.info(resource, loadedAclRulesMap, GroovyDslBasedAuthorizationService::buildServiceDescription); + } catch (IOException e) { + throw new AclConfigurationException("ACL configuration issue:[%s]".formatted(resource), e); } - Map> loadedAclRulesMap = AclRulesLoader.load(localFile); - switchTo(new AclEngine(loadedAclRulesMap)); - log.info(resource, loadedAclRulesMap, GroovyDslBasedAuthorizationService::buildServiceDescription); } private static String buildServiceDescription(URI resource, Map> aclRulesMap) { diff --git a/base/src/main/java/javasabr/mqtt/base/util/ClassPathResourceResolver.java b/base/src/main/java/javasabr/mqtt/base/util/ClassPathResourceResolver.java new file mode 100644 index 00000000..4347ef12 --- /dev/null +++ b/base/src/main/java/javasabr/mqtt/base/util/ClassPathResourceResolver.java @@ -0,0 +1,35 @@ +package javasabr.mqtt.base.util; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; + +public final class ClassPathResourceResolver { + + private ClassPathResourceResolver() {} + + public static InputStream newInputStream(URI uri) throws IOException { + if (uri == null) { + throw new IllegalArgumentException("URI must not be null"); + } + if ("classpath".equalsIgnoreCase(uri.getScheme())) { + String resourcePath = uri.getSchemeSpecificPart(); + if (resourcePath == null || resourcePath.isEmpty()) { + throw new IllegalArgumentException("Classpath URI must have a non-empty resource path: %s".formatted(uri)); + } + Path localPath = Path.of(resourcePath); + if (Files.exists(localPath)) { + return Files.newInputStream(localPath); + } + throw new FileNotFoundException(uri.toString()); + } + Path localPath = Path.of(uri); + if (Files.exists(localPath)) { + return Files.newInputStream(localPath); + } + throw new FileNotFoundException(uri.toString()); + } +} diff --git a/base/src/main/java/javasabr/mqtt/base/util/ClassPathUriResolver.java b/base/src/main/java/javasabr/mqtt/base/util/ClassPathUriResolver.java deleted file mode 100644 index ab4bb40a..00000000 --- a/base/src/main/java/javasabr/mqtt/base/util/ClassPathUriResolver.java +++ /dev/null @@ -1,89 +0,0 @@ -package javasabr.mqtt.base.util; - -import java.io.IOException; -import java.net.URI; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; - -public final class ClassPathUriResolver { - - private ClassPathUriResolver() {} - - public static Path resolveToPath(URI uri) { - if (uri == null) { - throw new NullPointerException("uri must not be null"); - } - - if ("classpath".equalsIgnoreCase(uri.getScheme())) { - return resolveClasspathUri(uri); - } - - return Path.of(uri); - } - - private static Path resolveClasspathUri(URI uri) { - String resourcePath = uri.getSchemeSpecificPart(); - if (resourcePath == null || resourcePath.isEmpty()) { - throw new IllegalArgumentException( - "classpath URI must have a non-empty resource path: " + uri); - } - - if (resourcePath.startsWith("/")) { - resourcePath = resourcePath.substring(1); - } - - var classLoader = Thread.currentThread().getContextClassLoader(); - if (classLoader == null) { - classLoader = ClassPathUriResolver.class.getClassLoader(); - } - - var systemResource = classLoader.getResource(resourcePath); - if (systemResource != null) { - URI resourceUri = URI.create(systemResource.toString()); - if ("file".equalsIgnoreCase(resourceUri.getScheme())) { - Path directPath = Path.of(resourceUri); - if (Files.exists(directPath)) { - return directPath; - } - } - } - - Path localPath = Path.of(resourcePath); - if (Files.exists(localPath)) { - return localPath; - } - - return extractToTempFile(classLoader, resourcePath); - } - - private static Path extractToTempFile(ClassLoader classLoader, String resourcePath) { - try (var inputStream = classLoader.getResourceAsStream(resourcePath)) { - if (inputStream == null) { - throw new IllegalArgumentException( - "Classpath resource not found: " + resourcePath); - } - - String fileName = extractFileName(resourcePath); - String suffix = extractSuffix(fileName); - - Path tempFile = Files.createTempFile("classpath-" + fileName.replace('.', '_'), suffix); - tempFile.toFile().deleteOnExit(); - Files.copy(inputStream, tempFile, StandardCopyOption.REPLACE_EXISTING); - return tempFile; - } catch (IOException e) { - throw new RuntimeException( - "Failed to extract classpath resource to temp file: " + resourcePath, e); - } - } - - private static String extractFileName(String resourcePath) { - int lastSlash = resourcePath.lastIndexOf('/'); - return lastSlash >= 0 ? resourcePath.substring(lastSlash + 1) : resourcePath; - } - - private static String extractSuffix(String fileName) { - int dotIndex = fileName.lastIndexOf('.'); - return dotIndex >= 0 ? fileName.substring(dotIndex) : ".tmp"; - } -} diff --git a/base/src/test/groovy/javasabr/mqtt/base/util/ClassPathResourceResolverTest.groovy b/base/src/test/groovy/javasabr/mqtt/base/util/ClassPathResourceResolverTest.groovy new file mode 100644 index 00000000..45e0ab57 --- /dev/null +++ b/base/src/test/groovy/javasabr/mqtt/base/util/ClassPathResourceResolverTest.groovy @@ -0,0 +1,81 @@ +package javasabr.mqtt.base.util + +import javasabr.mqtt.test.support.UnitSpecification + +import java.nio.charset.StandardCharsets +import java.nio.file.Files +import java.nio.file.Path + +class ClassPathResourceResolverTest extends UnitSpecification { + + def "should resolve file URI to Path"() { + given: + def tempFile = Files.createTempFile("test", ".txt") + Files.writeString(tempFile, "hello") + def uri = tempFile.toUri() + when: + def result = ClassPathResourceResolver.newInputStream(uri) + then: + new String(result.readAllBytes(), StandardCharsets.UTF_8) == "hello" + cleanup: + Files.deleteIfExists(tempFile) + } + + def "should resolve classpath URI to existing Path"() { + given: + def uri = URI.create("classpath:javasabr/mqtt/base/util/ClassPathResourceStreamer.class") + when: + def result = ClassPathResourceResolver.newInputStream(uri) + then: + result.available() > 0 + } + + def "should handle classpath URI with leading slash"() { + given: + def uri = URI.create("classpath:/javasabr/mqtt/base/util/ClassPathResourceStreamer.class") + when: + def result = ClassPathResourceResolver.newInputStream(uri) + then: + result.available() > 0 + } + + def "should throw NullPointerException for null URI"() { + when: + ClassPathResourceResolver.newInputStream(null) + then: + thrown(IllegalArgumentException) + } + + def "should throw IllegalArgumentException for missing classpath resource"() { + given: + def uri = URI.create("classpath:nonexistent/resource.txt") + when: + ClassPathResourceResolver.newInputStream(uri) + then: + thrown(FileNotFoundException) + } + + def "should throw IllegalArgumentException for classpath URI with blank resource path"() { + given: + def uri = new URI("classpath", " ", null) + when: + ClassPathResourceResolver.newInputStream(uri) + then: + thrown(FileNotFoundException) + } + + def "should resolve classpath URI via filesystem fallback when not on classloader"() { + given: + def testDir = Path.of("build/tmp/classpath-test") + Files.createDirectories(testDir) + def resourceFile = Files.writeString(testDir.resolve("data.txt"), "fallback content") + def uri = URI.create("classpath:build/tmp/classpath-test/data.txt") + when: + def result = ClassPathResourceResolver.newInputStream(uri) + then: + new String(result.readAllBytes(), StandardCharsets.UTF_8) == "fallback content" + cleanup: + Files.deleteIfExists(resourceFile) + Files.deleteIfExists(testDir) + } +} diff --git a/base/src/test/groovy/javasabr/mqtt/base/util/ClassPathUriResolverTest.groovy b/base/src/test/groovy/javasabr/mqtt/base/util/ClassPathUriResolverTest.groovy deleted file mode 100644 index 0ccfe355..00000000 --- a/base/src/test/groovy/javasabr/mqtt/base/util/ClassPathUriResolverTest.groovy +++ /dev/null @@ -1,84 +0,0 @@ -package javasabr.mqtt.base.util - -import javasabr.mqtt.test.support.UnitSpecification - -import java.nio.file.Files -import java.nio.file.Path - -class ClassPathUriResolverTest extends UnitSpecification { - - def "should resolve file URI to Path"() { - given: - def tempFile = Files.createTempFile("test", ".txt") - Files.writeString(tempFile, "hello") - def uri = tempFile.toUri() - when: - def result = ClassPathUriResolver.resolveToPath(uri) - then: - Files.exists(result) - Files.readString(result) == "hello" - cleanup: - Files.deleteIfExists(tempFile) - } - - def "should resolve classpath URI to existing Path"() { - given: - def uri = URI.create("classpath:javasabr/mqtt/base/util/ClassPathUriResolver.class") - when: - def result = ClassPathUriResolver.resolveToPath(uri) - then: - Files.exists(result) - Files.size(result) > 0 - } - - def "should handle classpath URI with leading slash"() { - given: - def uri = URI.create("classpath:/javasabr/mqtt/base/util/ClassPathUriResolver.class") - when: - def result = ClassPathUriResolver.resolveToPath(uri) - then: - Files.exists(result) - Files.size(result) > 0 - } - - def "should throw NullPointerException for null URI"() { - when: - ClassPathUriResolver.resolveToPath(null) - then: - thrown(NullPointerException) - } - - def "should throw IllegalArgumentException for missing classpath resource"() { - given: - def uri = URI.create("classpath:nonexistent/resource.txt") - when: - ClassPathUriResolver.resolveToPath(uri) - then: - thrown(IllegalArgumentException) - } - - def "should throw IllegalArgumentException for classpath URI with blank resource path"() { - given: - def uri = new URI("classpath", " ", null) - when: - ClassPathUriResolver.resolveToPath(uri) - then: - thrown(IllegalArgumentException) - } - - def "should resolve classpath URI via filesystem fallback when not on classloader"() { - given: - def testDir = Path.of("build/tmp/classpath-test") - Files.createDirectories(testDir) - def resourceFile = Files.writeString(testDir.resolve("data.txt"), "fallback content") - def uri = URI.create("classpath:build/tmp/classpath-test/data.txt") - when: - def result = ClassPathUriResolver.resolveToPath(uri) - then: - Files.exists(result) - Files.readString(result) == "fallback content" - cleanup: - Files.deleteIfExists(resourceFile) - Files.deleteIfExists(testDir) - } -} diff --git a/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java b/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java index c8cdd410..b0bf1b4a 100644 --- a/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java +++ b/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java @@ -3,17 +3,14 @@ import com.fasterxml.jackson.annotation.JsonValue; import java.io.IOException; import java.net.URI; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.Map; import javasabr.mqtt.auth.api.CredentialsSourceType; import javasabr.mqtt.auth.api.exception.CredentialsSourceException; -import javasabr.mqtt.base.util.ClassPathUriResolver; +import javasabr.mqtt.base.util.ClassPathResourceResolver; import javasabr.mqtt.base.util.DebugUtils; import lombok.AccessLevel; import lombok.experimental.FieldDefaults; - @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public class FileCredentialsSource extends InMemoryCredentialsSource { @@ -25,12 +22,8 @@ public FileCredentialsSource(URI fileName) { } private void init() { - Path path = ClassPathUriResolver.resolveToPath(fileName); - if (!Files.exists(path)) { - throw new CredentialsSourceException("Credentials file:[%s] could not be found".formatted(fileName)); - } try { - reset(Files.newInputStream(path)); + reset(ClassPathResourceResolver.newInputStream(fileName)); } catch (IOException e) { throw new CredentialsSourceException("Error during credentials file read", e); } From 68e4df7b0274307baa1685ef8a80c41e8f98b5da Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Mon, 8 Jun 2026 18:49:47 +0200 Subject: [PATCH 06/11] feature: [172] Add reachability-metadata.json --- .../native-image/reachability-metadata.json | 4632 +++++++++++++++++ 1 file changed, 4632 insertions(+) create mode 100644 application/src/main/resources/META-INF/native-image/reachability-metadata.json diff --git a/application/src/main/resources/META-INF/native-image/reachability-metadata.json b/application/src/main/resources/META-INF/native-image/reachability-metadata.json new file mode 100644 index 00000000..ee8643c1 --- /dev/null +++ b/application/src/main/resources/META-INF/native-image/reachability-metadata.json @@ -0,0 +1,4632 @@ +{ + "reflection": [ + { + "type": "boolean[]" + }, + { + "type": "byte[]" + }, + { + "type": "ch.qos.logback.classic.LoggerContext" + }, + { + "type": "com.fasterxml.jackson.annotation.JacksonAnnotation" + }, + { + "type": "com.fasterxml.jackson.annotation.JsonValue" + }, + { + "type": "com.fasterxml.jackson.core.JsonParser" + }, + { + "type": "com.fasterxml.jackson.databind.JsonNode" + }, + { + "type": "com.fasterxml.jackson.databind.ObjectMapper" + }, + { + "type": "com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.fasterxml.jackson.dataformat.yaml.YAMLFactory" + }, + { + "type": "com.sun.crypto.provider.AESCipher$General", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.AESKeyGenerator", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.ARCFOURCipher", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.ChaCha20Cipher$ChaCha20Poly1305", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.DESCipher", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.DESedeCipher", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.DHParameters", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.GaloisCounterMode$AESGCM", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.HKDFKeyDerivation$HKDFSHA384", + "methods": [ + { + "name": "", + "parameterTypes": [ + "javax.crypto.KDFParameters" + ] + } + ] + }, + { + "type": "com.sun.crypto.provider.HmacCore$HmacSHA256", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.HmacCore$HmacSHA384", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.HmacPKCS12PBECore$HmacPKCS12PBE_SHA256", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.PBEKeyFactory$PBEWithMD5AndDES", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.PBES2Core$HmacSHA256AndAES_256", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.PBES2Parameters$General", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.PBKDF2Core$HmacSHA1", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.PBKDF2Core$HmacSHA224", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.PBKDF2Core$HmacSHA256", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.PBKDF2Core$HmacSHA384", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.PBKDF2Core$HmacSHA512", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.TlsMasterSecretGenerator", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "groovy.lang.MetaClass" + }, + { + "type": "io.micrometer.context.ContextRegistry" + }, + { + "type": "io.netty.buffer.AbstractByteBufAllocator" + }, + { + "type": "io.netty.util.internal.CleanerJava25$CleanableDirectBufferImpl", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.AutoCloseable", + "java.nio.ByteBuffer", + "long" + ] + } + ] + }, + { + "type": "io.r2dbc.pool.PoolingConnectionFactoryProvider" + }, + { + "type": "io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider" + }, + { + "type": "io.r2dbc.postgresql.codec.BuiltinDynamicCodecs" + }, + { + "type": "jakarta.annotation.PostConstruct" + }, + { + "type": "jakarta.annotation.PreDestroy" + }, + { + "type": "jakarta.annotation.Resource" + }, + { + "type": "jakarta.ejb.EJB" + }, + { + "type": "jakarta.inject.Inject" + }, + { + "type": "jakarta.inject.Named" + }, + { + "type": "jakarta.inject.Provider" + }, + { + "type": "jakarta.inject.Qualifier" + }, + { + "type": "jakarta.persistence.EntityManagerFactory" + }, + { + "type": "jakarta.servlet.Servlet" + }, + { + "type": "java.io.Closeable" + }, + { + "type": "java.io.Console", + "methods": [ + { + "name": "isTerminal", + "parameterTypes": [] + } + ] + }, + { + "type": "java.io.Serializable" + }, + { + "type": "java.lang.AutoCloseable" + }, + { + "type": "java.lang.Boolean", + "jniAccessible": true, + "methods": [ + { + "name": "getBoolean", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "type": "java.lang.Class", + "methods": [ + { + "name": "getModule", + "parameterTypes": [] + } + ] + }, + { + "type": "java.lang.ClassLoader", + "fields": [ + { + "name": "classLoaderValueMap" + } + ] + }, + { + "type": "java.lang.Class[]" + }, + { + "type": "java.lang.Comparable" + }, + { + "type": "java.lang.Iterable" + }, + { + "type": "java.lang.Module", + "methods": [ + { + "name": "isNativeAccessEnabled", + "parameterTypes": [] + } + ] + }, + { + "type": "java.lang.Object" + }, + { + "type": "java.lang.Object[]" + }, + { + "type": "java.lang.Record" + }, + { + "type": "java.lang.SecurityManager", + "methods": [ + { + "name": "checkPermission", + "parameterTypes": [ + "java.security.Permission" + ] + } + ] + }, + { + "type": "java.lang.String" + }, + { + "type": "java.lang.String[]" + }, + { + "type": "java.lang.System", + "methods": [ + { + "name": "getSecurityManager", + "parameterTypes": [] + } + ] + }, + { + "type": "java.lang.Thread", + "methods": [ + { + "name": "isVirtual", + "parameterTypes": [] + } + ] + }, + { + "type": "java.lang.annotation.Documented" + }, + { + "type": "java.lang.annotation.Repeatable" + }, + { + "type": "java.lang.annotation.Retention" + }, + { + "type": "java.lang.annotation.Target" + }, + { + "type": "java.lang.foreign.Arena", + "methods": [ + { + "name": "allocate", + "parameterTypes": [ + "long" + ] + }, + { + "name": "ofShared", + "parameterTypes": [] + } + ] + }, + { + "type": "java.lang.foreign.MemorySegment", + "methods": [ + { + "name": "address", + "parameterTypes": [] + }, + { + "name": "asByteBuffer", + "parameterTypes": [] + }, + { + "name": "ofBuffer", + "parameterTypes": [ + "java.nio.Buffer" + ] + } + ] + }, + { + "type": "java.lang.invoke.MethodHandles", + "methods": [ + { + "name": "byteArrayViewVarHandle", + "parameterTypes": [ + "java.lang.Class", + "java.nio.ByteOrder" + ] + }, + { + "name": "byteBufferViewVarHandle", + "parameterTypes": [ + "java.lang.Class", + "java.nio.ByteOrder" + ] + }, + { + "name": "privateLookupIn", + "parameterTypes": [ + "java.lang.Class", + "java.lang.invoke.MethodHandles$Lookup" + ] + } + ] + }, + { + "type": "java.lang.invoke.MethodHandles$Lookup", + "methods": [ + { + "name": "findVarHandle", + "parameterTypes": [ + "java.lang.Class", + "java.lang.String", + "java.lang.Class" + ] + } + ] + }, + { + "type": "java.lang.management.ManagementFactory", + "methods": [ + { + "name": "getRuntimeMXBean", + "parameterTypes": [] + } + ] + }, + { + "type": "java.lang.management.RuntimeMXBean", + "methods": [ + { + "name": "getInputArguments", + "parameterTypes": [] + } + ] + }, + { + "type": "java.lang.reflect.ParameterizedType", + "methods": [ + { + "name": "getActualTypeArguments", + "parameterTypes": [] + }, + { + "name": "getRawType", + "parameterTypes": [] + } + ] + }, + { + "type": "java.lang.reflect.WildcardType", + "methods": [ + { + "name": "getUpperBounds", + "parameterTypes": [] + } + ] + }, + { + "type": "java.net.InetSocketAddress" + }, + { + "type": "java.net.URI", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "type": "java.nio.ByteBuffer", + "methods": [ + { + "name": "alignedSlice", + "parameterTypes": [ + "int" + ] + }, + { + "name": "put", + "parameterTypes": [ + "int", + "java.nio.ByteBuffer", + "int", + "int" + ] + }, + { + "name": "put", + "parameterTypes": [ + "int", + "byte[]", + "int", + "int" + ] + }, + { + "name": "slice", + "parameterTypes": [ + "int", + "int" + ] + } + ] + }, + { + "type": "java.security.AccessController", + "methods": [ + { + "name": "doPrivileged", + "parameterTypes": [ + "java.security.PrivilegedExceptionAction" + ] + } + ] + }, + { + "type": "java.security.AlgorithmParametersSpi" + }, + { + "type": "java.security.KeyStoreSpi" + }, + { + "type": "java.security.interfaces.RSAPrivateKey" + }, + { + "type": "java.security.interfaces.RSAPublicKey" + }, + { + "type": "java.sql.Date" + }, + { + "type": "java.sql.Time" + }, + { + "type": "java.util.AbstractCollection" + }, + { + "type": "java.util.AbstractMap" + }, + { + "type": "java.util.Collection" + }, + { + "type": "java.util.EnumMap$Values" + }, + { + "type": "java.util.EventListener" + }, + { + "type": "java.util.ImmutableCollections$AbstractImmutableMap" + }, + { + "type": "java.util.ImmutableCollections$MapN" + }, + { + "type": "java.util.Map" + }, + { + "type": "java.util.UUID[]" + }, + { + "type": "java.util.function.BiConsumer[]" + }, + { + "type": "java.util.function.Consumer[]" + }, + { + "type": "java.util.logging.LogManager" + }, + { + "type": "javasabr.mqtt.acl.engine.model.condition.MqttUserCondition[]" + }, + { + "type": "javasabr.mqtt.acl.engine.model.matcher.AnyTopicMatcher[]" + }, + { + "type": "javasabr.mqtt.acl.engine.model.matcher.TopicMatcher[]" + }, + { + "type": "javasabr.mqtt.acl.engine.model.rule.AclRule[]" + }, + { + "type": "javasabr.mqtt.acl.java.dsl.AclRulesLoader" + }, + { + "type": "javasabr.mqtt.acl.service.AclEngineBasedAuthorizationService" + }, + { + "type": "javasabr.mqtt.acl.service.conifg.AntlrDslBasedAclServiceSpringConfig", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "authorizationService", + "parameterTypes": [ + "java.net.URI" + ] + } + ] + }, + { + "type": "javasabr.mqtt.acl.service.impl.UriLoaderAuthorizationService" + }, + { + "type": "javasabr.mqtt.auth.api.AuthenticationMethod" + }, + { + "type": "javasabr.mqtt.auth.api.AuthenticationProvider" + }, + { + "type": "javasabr.mqtt.auth.api.AuthenticationProvider[]" + }, + { + "type": "javasabr.mqtt.auth.api.AuthenticationService" + }, + { + "type": "javasabr.mqtt.auth.api.CredentialsSource" + }, + { + "type": "javasabr.mqtt.auth.api.CredentialsSourceType" + }, + { + "type": "javasabr.mqtt.auth.api.CredentialsSource[]" + }, + { + "type": "javasabr.mqtt.auth.api.database.DatabaseConnectionProperties" + }, + { + "type": "javasabr.mqtt.auth.api.database.DatabaseCredentials" + }, + { + "type": "javasabr.mqtt.auth.api.database.DatabasePoolProperties" + }, + { + "type": "javasabr.mqtt.auth.api.database.DatabaseTimeoutProperties" + }, + { + "type": "javasabr.mqtt.auth.api.file.FileProperties" + }, + { + "type": "javasabr.mqtt.auth.credentials.source.DatabaseCredentialsSource", + "methods": [ + { + "name": "jsonDebugValue", + "parameterTypes": [] + } + ] + }, + { + "type": "javasabr.mqtt.auth.credentials.source.FileCredentialsSource", + "methods": [ + { + "name": "jsonDebugValue", + "parameterTypes": [] + } + ] + }, + { + "type": "javasabr.mqtt.auth.credentials.source.InMemoryCredentialsSource" + }, + { + "type": "javasabr.mqtt.auth.provider.BasicAuthenticationProvider", + "methods": [ + { + "name": "jsonDebugValue", + "parameterTypes": [] + } + ] + }, + { + "type": "javasabr.mqtt.auth.service.DefaultAuthenticationService" + }, + { + "type": "javasabr.mqtt.auth.service.config.AuthenticationServiceSpringConfig", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "authenticationService", + "parameterTypes": [ + "java.util.List", + "boolean" + ] + }, + { + "name": "basicAuthenticationProvider", + "parameterTypes": [ + "java.util.List" + ] + } + ] + }, + { + "type": "javasabr.mqtt.auth.service.config.DatabaseCredentialsSourceSpringConfig", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "adminDatabaseCredentials", + "parameterTypes": [] + }, + { + "name": "credentialsSourceDatabaseConnectionProperties", + "parameterTypes": [ + "java.lang.String", + "java.lang.String", + "int", + "java.lang.String" + ] + }, + { + "name": "credentialsSourceDatabasePoolProperties", + "parameterTypes": [ + "int", + "int", + "int" + ] + }, + { + "name": "credentialsSourceDatabaseTimeoutsProperties", + "parameterTypes": [ + "int", + "int" + ] + }, + { + "name": "credentialsSourceFlyway", + "parameterTypes": [ + "javasabr.mqtt.auth.api.database.DatabaseConnectionProperties", + "javasabr.mqtt.auth.api.database.DatabaseCredentials", + "java.lang.String" + ] + }, + { + "name": "databaseCredentialsSource", + "parameterTypes": [ + "javasabr.mqtt.auth.api.database.DatabasePoolProperties", + "javasabr.mqtt.auth.api.database.DatabaseTimeoutProperties", + "javasabr.mqtt.auth.api.database.DatabaseConnectionProperties", + "javasabr.mqtt.auth.api.database.DatabaseCredentials" + ] + }, + { + "name": "readerDatabaseCredentials", + "parameterTypes": [] + } + ] + }, + { + "type": "javasabr.mqtt.auth.service.config.FileCredentialsSourceSpringConfig", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "fileCredentialsSource", + "parameterTypes": [ + "javasabr.mqtt.auth.api.file.FileProperties" + ] + }, + { + "name": "fileCredentialsSourceProperties", + "parameterTypes": [ + "java.net.URI" + ] + } + ] + }, + { + "type": "javasabr.mqtt.base.util.DebugUtils$DebugFieldsFilterMixIn" + }, + { + "type": "javasabr.mqtt.broker.application.MqttBrokerApplication", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "main", + "parameterTypes": [ + "java.lang.String[]" + ] + } + ] + }, + { + "type": "javasabr.mqtt.broker.application.config.MqttBrokerSpringConfig", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "clientIdRegistry", + "parameterTypes": [ + "org.springframework.core.env.Environment" + ] + }, + { + "name": "connectInMqttInMessageHandler", + "parameterTypes": [ + "javasabr.mqtt.service.ClientIdRegistry", + "javasabr.mqtt.auth.api.AuthenticationService", + "javasabr.mqtt.service.session.MqttSessionService", + "javasabr.mqtt.service.SubscriptionService", + "javasabr.mqtt.service.MessageOutFactoryService" + ] + }, + { + "name": "disconnectMqttInMessageHandler", + "parameterTypes": [ + "javasabr.mqtt.service.MessageOutFactoryService" + ] + }, + { + "name": "externalClientFactory", + "parameterTypes": [ + "javasabr.mqtt.network.handler.NetworkMqttUserReleaseHandler" + ] + }, + { + "name": "externalConnectionConfig", + "parameterTypes": [ + "org.springframework.core.env.Environment" + ] + }, + { + "name": "externalMqttClientReleaseHandler", + "parameterTypes": [ + "javasabr.mqtt.service.ClientIdRegistry", + "javasabr.mqtt.service.session.MqttSessionService", + "javasabr.mqtt.service.SubscriptionService" + ] + }, + { + "name": "externalMqttConnectionService", + "parameterTypes": [ + "java.util.Collection" + ] + }, + { + "name": "incomingPublishRouter", + "parameterTypes": [ + "java.util.Collection" + ] + }, + { + "name": "incomingPublishStorage", + "parameterTypes": [ + "javasabr.mqtt.service.publish.PublishDataStorage", + "int" + ] + }, + { + "name": "mqtt311MessageOutFactory", + "parameterTypes": [] + }, + { + "name": "mqtt5MessageOutFactory", + "parameterTypes": [] + }, + { + "name": "mqttMessageOutFactoryService", + "parameterTypes": [ + "java.util.Collection" + ] + }, + { + "name": "mqttPacketCodec", + "parameterTypes": [] + }, + { + "name": "mqttSessionService", + "parameterTypes": [ + "int", + "int", + "int", + "int", + "int", + "int" + ] + }, + { + "name": "publishAckMqttInMessageHandler", + "parameterTypes": [ + "javasabr.mqtt.service.MessageOutFactoryService" + ] + }, + { + "name": "publishCompleteMqttInMessageHandler", + "parameterTypes": [ + "javasabr.mqtt.service.MessageOutFactoryService" + ] + }, + { + "name": "publishDataStorage", + "parameterTypes": [] + }, + { + "name": "publishDispatcher", + "parameterTypes": [ + "java.util.Collection" + ] + }, + { + "name": "publishMqttInMessageHandler", + "parameterTypes": [ + "javasabr.mqtt.service.publish.IncomingPublishRouter", + "javasabr.mqtt.service.MessageOutFactoryService", + "javasabr.mqtt.service.TopicService", + "javasabr.mqtt.service.AuthorizationService", + "javasabr.mqtt.service.publish.PublishDataStorage", + "javasabr.mqtt.service.publish.IncomingPublishStorage" + ] + }, + { + "name": "publishReceiveMqttInMessageHandler", + "parameterTypes": [ + "javasabr.mqtt.service.MessageOutFactoryService" + ] + }, + { + "name": "publishReleaseMqttInMessageHandler", + "parameterTypes": [ + "javasabr.mqtt.service.MessageOutFactoryService" + ] + }, + { + "name": "qos0IncomingPublishProcessor", + "parameterTypes": [ + "javasabr.mqtt.service.SubscriptionService", + "javasabr.mqtt.service.publish.PublishDispatcher", + "javasabr.mqtt.service.MessageOutFactoryService", + "javasabr.mqtt.service.publish.RetainPublishService", + "javasabr.mqtt.service.publish.IncomingPublishStorage" + ] + }, + { + "name": "qos0SubscriberPublishSender", + "parameterTypes": [ + "javasabr.mqtt.service.MessageOutFactoryService", + "javasabr.mqtt.service.publish.IncomingPublishStorage" + ] + }, + { + "name": "qos1IncomingPublishProcessor", + "parameterTypes": [ + "javasabr.mqtt.service.SubscriptionService", + "javasabr.mqtt.service.publish.PublishDispatcher", + "javasabr.mqtt.service.MessageOutFactoryService", + "javasabr.mqtt.service.publish.RetainPublishService", + "javasabr.mqtt.service.publish.IncomingPublishStorage" + ] + }, + { + "name": "qos1SubscriberPublishSender", + "parameterTypes": [ + "javasabr.mqtt.service.MessageOutFactoryService", + "javasabr.mqtt.service.publish.IncomingPublishStorage" + ] + }, + { + "name": "qos2IncomingPublishProcessor", + "parameterTypes": [ + "javasabr.mqtt.service.SubscriptionService", + "javasabr.mqtt.service.publish.PublishDispatcher", + "javasabr.mqtt.service.MessageOutFactoryService", + "javasabr.mqtt.service.publish.RetainPublishService", + "javasabr.mqtt.service.publish.IncomingPublishStorage" + ] + }, + { + "name": "qos2SubscriberPublishSender", + "parameterTypes": [ + "javasabr.mqtt.service.MessageOutFactoryService", + "javasabr.mqtt.service.publish.IncomingPublishStorage" + ] + }, + { + "name": "retainMessageService", + "parameterTypes": [] + }, + { + "name": "subscribeMqttInMessageHandler", + "parameterTypes": [ + "javasabr.mqtt.service.SubscriptionService", + "javasabr.mqtt.service.MessageOutFactoryService", + "javasabr.mqtt.service.TopicService", + "javasabr.mqtt.service.publish.RetainPublishService", + "javasabr.mqtt.service.publish.PublishDispatcher" + ] + }, + { + "name": "subscriptionService", + "parameterTypes": [ + "javasabr.mqtt.service.AuthorizationService" + ] + }, + { + "name": "topicService", + "parameterTypes": [] + }, + { + "name": "unsubscribeMqttInMessageHandler", + "parameterTypes": [ + "javasabr.mqtt.service.SubscriptionService", + "javasabr.mqtt.service.MessageOutFactoryService", + "javasabr.mqtt.service.TopicService" + ] + } + ] + }, + { + "type": "javasabr.mqtt.broker.application.config.MqttExternalPlainNetworkConfig", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "externalPlainConnectionFactory", + "parameterTypes": [ + "javasabr.mqtt.model.MqttServerConnectionConfig", + "javasabr.mqtt.network.user.NetworkMqttUserFactory", + "int", + "javasabr.mqtt.network.message.MqttPacketCodec" + ] + }, + { + "name": "externalPlainNetwork", + "parameterTypes": [ + "javasabr.rlib.network.ServerNetworkConfig", + "javasabr.mqtt.network.MqttConnectionFactory" + ] + }, + { + "name": "externalPlainNetworkAddress", + "parameterTypes": [ + "java.lang.String", + "int" + ] + }, + { + "name": "externalPlainNetworkConfig", + "parameterTypes": [ + "int", + "int", + "int", + "java.lang.String", + "int" + ] + }, + { + "name": "externalPlainNetworkStarter", + "parameterTypes": [ + "javasabr.rlib.network.server.ServerNetwork", + "javasabr.mqtt.service.ConnectionService", + "java.net.InetSocketAddress" + ] + } + ] + }, + { + "type": "javasabr.mqtt.broker.application.config.MqttExternalTlsNetworkConfig", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "externalNetworkSslContext", + "parameterTypes": [ + "javasabr.mqtt.network.TlsProperties" + ] + }, + { + "name": "externalTlsConnectionFactory", + "parameterTypes": [ + "javasabr.mqtt.model.MqttServerConnectionConfig", + "javasabr.mqtt.network.user.NetworkMqttUserFactory", + "int", + "javax.net.ssl.SSLContext", + "javasabr.mqtt.network.TlsProperties", + "javasabr.rlib.network.ServerNetworkConfig", + "javasabr.mqtt.network.message.MqttPacketCodec" + ] + }, + { + "name": "externalTlsNetwork", + "parameterTypes": [ + "javasabr.rlib.network.ServerNetworkConfig", + "javasabr.mqtt.network.MqttConnectionFactory" + ] + }, + { + "name": "externalTlsNetworkAddress", + "parameterTypes": [ + "java.lang.String", + "int" + ] + }, + { + "name": "externalTlsNetworkConfig", + "parameterTypes": [ + "int", + "int", + "int", + "java.lang.String", + "int" + ] + }, + { + "name": "externalTlsNetworkStarter", + "parameterTypes": [ + "javasabr.rlib.network.server.ServerNetwork", + "javasabr.mqtt.service.ConnectionService", + "java.net.InetSocketAddress" + ] + }, + { + "name": "externalTlsProperties", + "parameterTypes": [ + "java.lang.String", + "java.lang.String", + "java.lang.String", + "java.lang.String", + "java.lang.String", + "java.lang.String", + "boolean", + "java.util.List", + "java.util.List" + ] + } + ] + }, + { + "type": "javasabr.mqtt.model.MqttMessageProperty[]" + }, + { + "type": "javasabr.mqtt.model.MqttServerConnectionConfig" + }, + { + "type": "javasabr.mqtt.model.MqttVersion" + }, + { + "type": "javasabr.mqtt.model.PayloadFormat" + }, + { + "type": "javasabr.mqtt.model.QoS" + }, + { + "type": "javasabr.mqtt.model.QoS[]" + }, + { + "type": "javasabr.mqtt.model.SubscribeRetainHandling" + }, + { + "type": "javasabr.mqtt.model.data.type.StringPair[]" + }, + { + "type": "javasabr.mqtt.model.message.MqttMessage" + }, + { + "type": "javasabr.mqtt.model.message.MqttMessageType[]" + }, + { + "type": "javasabr.mqtt.model.message.ReceivableMqttMessage" + }, + { + "type": "javasabr.mqtt.model.message.SendableMqttMessage" + }, + { + "type": "javasabr.mqtt.model.message.TrackableMqttMessage" + }, + { + "type": "javasabr.mqtt.model.publish.IncomingPublish" + }, + { + "type": "javasabr.mqtt.model.publish.IncomingPublish[]" + }, + { + "type": "javasabr.mqtt.model.publish.Publish" + }, + { + "type": "javasabr.mqtt.model.publish.PublishData" + }, + { + "type": "javasabr.mqtt.model.publish.SimpleIncomingPublish", + "methods": [ + { + "name": "jsonDebugValue", + "parameterTypes": [] + } + ] + }, + { + "type": "javasabr.mqtt.model.publish.impl.InMemoryPublishData", + "methods": [ + { + "name": "jsonDebugValue", + "parameterTypes": [] + } + ] + }, + { + "type": "javasabr.mqtt.model.reason.code.ConnectAckReasonCode" + }, + { + "type": "javasabr.mqtt.model.reason.code.ConnectAckReasonCode[]" + }, + { + "type": "javasabr.mqtt.model.reason.code.DisconnectReasonCode" + }, + { + "type": "javasabr.mqtt.model.reason.code.DisconnectReasonCode[]" + }, + { + "type": "javasabr.mqtt.model.reason.code.PublishAckReasonCode" + }, + { + "type": "javasabr.mqtt.model.reason.code.PublishAckReasonCode[]" + }, + { + "type": "javasabr.mqtt.model.reason.code.PublishCompletedReasonCode[]" + }, + { + "type": "javasabr.mqtt.model.reason.code.PublishReceivedReasonCode[]" + }, + { + "type": "javasabr.mqtt.model.reason.code.PublishReleaseReasonCode" + }, + { + "type": "javasabr.mqtt.model.reason.code.PublishReleaseReasonCode[]" + }, + { + "type": "javasabr.mqtt.model.reason.code.ReasonCode" + }, + { + "type": "javasabr.mqtt.model.reason.code.SubscribeAckReasonCode" + }, + { + "type": "javasabr.mqtt.model.reason.code.SubscribeAckReasonCode[]" + }, + { + "type": "javasabr.mqtt.model.subscriber.SingleSubscriber[]" + }, + { + "type": "javasabr.mqtt.model.subscriber.Subscriber[]" + }, + { + "type": "javasabr.mqtt.model.subscription.RequestedSubscription", + "fields": [ + { + "name": "noLocal" + }, + { + "name": "qos" + }, + { + "name": "rawTopicFilter" + }, + { + "name": "retainAsPublished" + }, + { + "name": "retainHandling" + } + ] + }, + { + "type": "javasabr.mqtt.model.subscription.RequestedSubscription[]" + }, + { + "type": "javasabr.mqtt.model.subscription.SubscriptionResult[]" + }, + { + "type": "javasabr.mqtt.model.subscription.Subscription[]" + }, + { + "type": "javasabr.mqtt.model.topic.AbstractTopic", + "methods": [ + { + "name": "jsonDebugValue", + "parameterTypes": [] + } + ] + }, + { + "type": "javasabr.mqtt.model.topic.TopicName" + }, + { + "type": "javasabr.mqtt.network.MqttConnectionFactory" + }, + { + "type": "javasabr.mqtt.network.TlsProperties" + }, + { + "type": "javasabr.mqtt.network.handler.NetworkMqttUserReleaseHandler" + }, + { + "type": "javasabr.mqtt.network.message.MqttPacketCodec" + }, + { + "type": "javasabr.mqtt.network.message.in.ConnectMqttInMessage", + "fields": [ + { + "name": "cleanStart" + }, + { + "name": "clientId" + }, + { + "name": "keepAlive" + }, + { + "name": "mqttVersion" + }, + { + "name": "sessionExpiryInterval" + } + ] + }, + { + "type": "javasabr.mqtt.network.message.in.DisconnectMqttInMessage", + "fields": [ + { + "name": "reasonCode" + } + ] + }, + { + "type": "javasabr.mqtt.network.message.in.MqttInMessage", + "fields": [ + { + "name": "userProperties" + } + ] + }, + { + "type": "javasabr.mqtt.network.message.in.PublishAckMqttInMessage" + }, + { + "type": "javasabr.mqtt.network.message.in.PublishControlMqttInMessage", + "fields": [ + { + "name": "reasonCode" + } + ] + }, + { + "type": "javasabr.mqtt.network.message.in.PublishMqttInMessage", + "fields": [ + { + "name": "duplicate" + }, + { + "name": "messageExpiryInterval" + }, + { + "name": "payloadFormat" + }, + { + "name": "qos" + }, + { + "name": "rawTopicName" + }, + { + "name": "topicAlias" + } + ] + }, + { + "type": "javasabr.mqtt.network.message.in.PublishReleaseMqttInMessage" + }, + { + "type": "javasabr.mqtt.network.message.in.SubscribeMqttInMessage", + "fields": [ + { + "name": "subscriptions" + } + ] + }, + { + "type": "javasabr.mqtt.network.message.in.TrackableMqttInMessage", + "fields": [ + { + "name": "messageId" + } + ] + }, + { + "type": "javasabr.mqtt.network.message.out.ConnectAckMqtt311OutMessage", + "fields": [ + { + "name": "reasonCode" + }, + { + "name": "sessionPresent" + } + ] + }, + { + "type": "javasabr.mqtt.network.message.out.MqttOutMessage" + }, + { + "type": "javasabr.mqtt.network.message.out.PublishAckMqtt311OutMessage" + }, + { + "type": "javasabr.mqtt.network.message.out.PublishCompleteMqtt311OutMessage" + }, + { + "type": "javasabr.mqtt.network.message.out.PublishMqtt311OutMessage", + "fields": [ + { + "name": "duplicate" + }, + { + "name": "qos" + }, + { + "name": "topicName" + } + ] + }, + { + "type": "javasabr.mqtt.network.message.out.PublishReceivedMqtt311OutMessage" + }, + { + "type": "javasabr.mqtt.network.message.out.SubscribeAckMqtt311OutMessage", + "fields": [ + { + "name": "reasonCodes" + } + ] + }, + { + "type": "javasabr.mqtt.network.message.out.TrackableMqttOutMessage", + "fields": [ + { + "name": "messageId" + } + ] + }, + { + "type": "javasabr.mqtt.network.user.NetworkMqttUserFactory" + }, + { + "type": "javasabr.mqtt.service.AuthorizationService" + }, + { + "type": "javasabr.mqtt.service.ClientIdRegistry" + }, + { + "type": "javasabr.mqtt.service.ConnectionService" + }, + { + "type": "javasabr.mqtt.service.MessageOutFactoryService" + }, + { + "type": "javasabr.mqtt.service.SubscriptionService" + }, + { + "type": "javasabr.mqtt.service.TopicService" + }, + { + "type": "javasabr.mqtt.service.handler.client.AbstractNetworkMqttUserReleaseHandler" + }, + { + "type": "javasabr.mqtt.service.handler.client.ExternalNetworkMqttUserReleaseHandler" + }, + { + "type": "javasabr.mqtt.service.impl.DefaultConnectionService" + }, + { + "type": "javasabr.mqtt.service.impl.DefaultMessageOutFactoryService" + }, + { + "type": "javasabr.mqtt.service.impl.DefaultTopicService" + }, + { + "type": "javasabr.mqtt.service.impl.ExternalNetworkMqttUserFactory" + }, + { + "type": "javasabr.mqtt.service.impl.InMemoryClientIdRegistry" + }, + { + "type": "javasabr.mqtt.service.impl.InMemorySubscriptionService" + }, + { + "type": "javasabr.mqtt.service.impl.PlainMqttConnectionFactory" + }, + { + "type": "javasabr.mqtt.service.impl.TlsMqttConnectionFactory" + }, + { + "type": "javasabr.mqtt.service.message.handler.MqttInMessageHandler" + }, + { + "type": "javasabr.mqtt.service.message.handler.impl.AbstractMqttInMessageHandler" + }, + { + "type": "javasabr.mqtt.service.message.handler.impl.ConnectInMqttInMessageHandler" + }, + { + "type": "javasabr.mqtt.service.message.handler.impl.DisconnectMqttInMessageHandler" + }, + { + "type": "javasabr.mqtt.service.message.handler.impl.FieldsValidatedMqttInMessageHandler" + }, + { + "type": "javasabr.mqtt.service.message.handler.impl.ProcessingOutPublishesMqttInMessageHandler" + }, + { + "type": "javasabr.mqtt.service.message.handler.impl.PublishAckMqttInMessageHandler" + }, + { + "type": "javasabr.mqtt.service.message.handler.impl.PublishCompleteMqttInMessageHandler" + }, + { + "type": "javasabr.mqtt.service.message.handler.impl.PublishMqttInMessageHandler" + }, + { + "type": "javasabr.mqtt.service.message.handler.impl.PublishReceiveMqttInMessageHandler" + }, + { + "type": "javasabr.mqtt.service.message.handler.impl.PublishReleaseMqttInMessageHandler" + }, + { + "type": "javasabr.mqtt.service.message.handler.impl.SubscribeMqttInMessageHandler" + }, + { + "type": "javasabr.mqtt.service.message.handler.impl.UnsubscribeMqttInMessageHandler" + }, + { + "type": "javasabr.mqtt.service.message.out.factory.Mqtt311MessageOutFactory" + }, + { + "type": "javasabr.mqtt.service.message.out.factory.Mqtt5MessageOutFactory" + }, + { + "type": "javasabr.mqtt.service.message.out.factory.MqttMessageOutFactory" + }, + { + "type": "javasabr.mqtt.service.publish.IncomingPublishRouter" + }, + { + "type": "javasabr.mqtt.service.publish.IncomingPublishStorage" + }, + { + "type": "javasabr.mqtt.service.publish.PublishDataStorage" + }, + { + "type": "javasabr.mqtt.service.publish.PublishDispatcher" + }, + { + "type": "javasabr.mqtt.service.publish.RetainPublishService" + }, + { + "type": "javasabr.mqtt.service.publish.impl.DefaultIncomingPublishRouter" + }, + { + "type": "javasabr.mqtt.service.publish.impl.DefaultPublishDispatcher" + }, + { + "type": "javasabr.mqtt.service.publish.impl.InMemoryIncomingPublishStorage" + }, + { + "type": "javasabr.mqtt.service.publish.impl.InMemoryPublishDataStorage" + }, + { + "type": "javasabr.mqtt.service.publish.impl.InMemoryRetainPublishService" + }, + { + "type": "javasabr.mqtt.service.publish.processor.AbstractIncomingPublishProcessor" + }, + { + "type": "javasabr.mqtt.service.publish.processor.IncomingPublishProcessor" + }, + { + "type": "javasabr.mqtt.service.publish.processor.Qos0IncomingPublishProcessor" + }, + { + "type": "javasabr.mqtt.service.publish.processor.Qos1IncomingPublishProcessor" + }, + { + "type": "javasabr.mqtt.service.publish.processor.Qos2IncomingPublishProcessor" + }, + { + "type": "javasabr.mqtt.service.publish.processor.TrackableIncomingPublishProcessor" + }, + { + "type": "javasabr.mqtt.service.publish.sender.AbstractSubscriberPublishSender" + }, + { + "type": "javasabr.mqtt.service.publish.sender.Qos0SubscriberPublishSender" + }, + { + "type": "javasabr.mqtt.service.publish.sender.Qos1SubscriberPublishSender" + }, + { + "type": "javasabr.mqtt.service.publish.sender.Qos2SubscriberPublishSender" + }, + { + "type": "javasabr.mqtt.service.publish.sender.SubscriberPublishSender" + }, + { + "type": "javasabr.mqtt.service.publish.sender.TrackableSubscriberPublishSender" + }, + { + "type": "javasabr.mqtt.service.session.MqttSessionService" + }, + { + "type": "javasabr.mqtt.service.session.impl.ExpirableSession[]" + }, + { + "type": "javasabr.mqtt.service.session.impl.InMemoryMqttSessionService" + }, + { + "type": "javasabr.mqtt.service.session.impl.InMemoryNetworkMqttSession[]" + }, + { + "type": "javasabr.mqtt.service.session.impl.NotExpirableSession[]" + }, + { + "type": "javasabr.rlib.common.AliasedEnum" + }, + { + "type": "javasabr.rlib.common.util.NumberedEnum" + }, + { + "type": "javasabr.rlib.logger.slf4j.Slf4jLoggerFactory", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "javasabr.rlib.network.Network" + }, + { + "type": "javasabr.rlib.network.NetworkConfig" + }, + { + "type": "javasabr.rlib.network.ServerNetworkConfig" + }, + { + "type": "javasabr.rlib.network.ServerNetworkConfig$SimpleServerNetworkConfig" + }, + { + "type": "javasabr.rlib.network.impl.AbstractNetwork" + }, + { + "type": "javasabr.rlib.network.packet.NetworkPacket" + }, + { + "type": "javasabr.rlib.network.packet.ReadableNetworkPacket" + }, + { + "type": "javasabr.rlib.network.packet.WritableNetworkPacket" + }, + { + "type": "javasabr.rlib.network.packet.WritableNetworkPacket[]" + }, + { + "type": "javasabr.rlib.network.packet.impl.AbstractNetworkPacket" + }, + { + "type": "javasabr.rlib.network.packet.impl.AbstractReadableNetworkPacket" + }, + { + "type": "javasabr.rlib.network.packet.impl.AbstractWritableNetworkPacket" + }, + { + "type": "javasabr.rlib.network.server.ServerNetwork" + }, + { + "type": "javasabr.rlib.network.server.impl.DefaultServerNetwork", + "methods": [ + { + "name": "shutdown", + "parameterTypes": [] + } + ] + }, + { + "type": "javax.money.MonetaryAmount" + }, + { + "type": "javax.naming.InitialContext" + }, + { + "type": "javax.net.ssl.SSLContext" + }, + { + "type": "javax.servlet.Servlet" + }, + { + "type": "jdk.crac.management.CRaCMXBean" + }, + { + "type": "jdk.internal.loader.ClassLoaders$AppClassLoader" + }, + { + "type": "jdk.internal.loader.ClassLoaders$PlatformClassLoader" + }, + { + "type": "jdk.internal.misc.Unsafe" + }, + { + "type": "kotlin.Metadata" + }, + { + "type": "kotlin.reflect.full.KClasses" + }, + { + "type": "kotlinx.coroutines.reactor.MonoKt" + }, + { + "type": "org.apache.commons.logging.LogFactory" + }, + { + "type": "org.apache.commons.logging.impl.Log4jApiLogFactory", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.commons.logging.impl.WeakHashtable", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.Appender[]" + }, + { + "type": "org.apache.logging.log4j.core.appender.AbstractAppender$Builder", + "fields": [ + { + "name": "configuration" + }, + { + "name": "ignoreExceptions" + }, + { + "name": "layout" + }, + { + "name": "name" + } + ] + }, + { + "type": "org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender$Builder", + "fields": [ + { + "name": "bufferSize" + }, + { + "name": "bufferedIo" + }, + { + "name": "immediateFlush" + } + ] + }, + { + "type": "org.apache.logging.log4j.core.appender.AppenderSet" + }, + { + "type": "org.apache.logging.log4j.core.appender.AsyncAppender" + }, + { + "type": "org.apache.logging.log4j.core.appender.ConsoleAppender", + "methods": [ + { + "name": "newBuilder", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.appender.ConsoleAppender$Builder", + "fields": [ + { + "name": "direct" + }, + { + "name": "follow" + }, + { + "name": "target" + } + ] + }, + { + "type": "org.apache.logging.log4j.core.appender.CountingNoOpAppender" + }, + { + "type": "org.apache.logging.log4j.core.appender.FailoverAppender" + }, + { + "type": "org.apache.logging.log4j.core.appender.FailoversPlugin" + }, + { + "type": "org.apache.logging.log4j.core.appender.FileAppender" + }, + { + "type": "org.apache.logging.log4j.core.appender.HttpAppender" + }, + { + "type": "org.apache.logging.log4j.core.appender.MemoryMappedFileAppender" + }, + { + "type": "org.apache.logging.log4j.core.appender.NullAppender" + }, + { + "type": "org.apache.logging.log4j.core.appender.OutputStreamAppender" + }, + { + "type": "org.apache.logging.log4j.core.appender.RandomAccessFileAppender" + }, + { + "type": "org.apache.logging.log4j.core.appender.RollingFileAppender" + }, + { + "type": "org.apache.logging.log4j.core.appender.RollingRandomAccessFileAppender" + }, + { + "type": "org.apache.logging.log4j.core.appender.ScriptAppenderSelector" + }, + { + "type": "org.apache.logging.log4j.core.appender.SmtpAppender" + }, + { + "type": "org.apache.logging.log4j.core.appender.SocketAppender" + }, + { + "type": "org.apache.logging.log4j.core.appender.SyslogAppender" + }, + { + "type": "org.apache.logging.log4j.core.appender.WriterAppender" + }, + { + "type": "org.apache.logging.log4j.core.appender.db.ColumnMapping" + }, + { + "type": "org.apache.logging.log4j.core.appender.db.jdbc.ColumnConfig" + }, + { + "type": "org.apache.logging.log4j.core.appender.db.jdbc.DataSourceConnectionSource" + }, + { + "type": "org.apache.logging.log4j.core.appender.db.jdbc.DriverManagerConnectionSource" + }, + { + "type": "org.apache.logging.log4j.core.appender.db.jdbc.FactoryMethodConnectionSource" + }, + { + "type": "org.apache.logging.log4j.core.appender.db.jdbc.JdbcAppender" + }, + { + "type": "org.apache.logging.log4j.core.appender.mom.JmsAppender" + }, + { + "type": "org.apache.logging.log4j.core.appender.mom.jeromq.JeroMqAppender" + }, + { + "type": "org.apache.logging.log4j.core.appender.mom.kafka.KafkaAppender" + }, + { + "type": "org.apache.logging.log4j.core.appender.nosql.NoSqlAppender" + }, + { + "type": "org.apache.logging.log4j.core.appender.rewrite.LoggerNameLevelRewritePolicy" + }, + { + "type": "org.apache.logging.log4j.core.appender.rewrite.MapRewritePolicy" + }, + { + "type": "org.apache.logging.log4j.core.appender.rewrite.PropertiesRewritePolicy" + }, + { + "type": "org.apache.logging.log4j.core.appender.rewrite.RewriteAppender" + }, + { + "type": "org.apache.logging.log4j.core.appender.rolling.CompositeTriggeringPolicy" + }, + { + "type": "org.apache.logging.log4j.core.appender.rolling.CronTriggeringPolicy" + }, + { + "type": "org.apache.logging.log4j.core.appender.rolling.DefaultRolloverStrategy" + }, + { + "type": "org.apache.logging.log4j.core.appender.rolling.DirectWriteRolloverStrategy" + }, + { + "type": "org.apache.logging.log4j.core.appender.rolling.NoOpTriggeringPolicy" + }, + { + "type": "org.apache.logging.log4j.core.appender.rolling.OnStartupTriggeringPolicy" + }, + { + "type": "org.apache.logging.log4j.core.appender.rolling.SizeBasedTriggeringPolicy" + }, + { + "type": "org.apache.logging.log4j.core.appender.rolling.TimeBasedTriggeringPolicy" + }, + { + "type": "org.apache.logging.log4j.core.appender.rolling.action.DeleteAction" + }, + { + "type": "org.apache.logging.log4j.core.appender.rolling.action.IfAccumulatedFileCount" + }, + { + "type": "org.apache.logging.log4j.core.appender.rolling.action.IfAccumulatedFileSize" + }, + { + "type": "org.apache.logging.log4j.core.appender.rolling.action.IfAll" + }, + { + "type": "org.apache.logging.log4j.core.appender.rolling.action.IfAny" + }, + { + "type": "org.apache.logging.log4j.core.appender.rolling.action.IfFileName" + }, + { + "type": "org.apache.logging.log4j.core.appender.rolling.action.IfLastModified" + }, + { + "type": "org.apache.logging.log4j.core.appender.rolling.action.IfNot" + }, + { + "type": "org.apache.logging.log4j.core.appender.rolling.action.PathSortByModificationTime" + }, + { + "type": "org.apache.logging.log4j.core.appender.rolling.action.PosixViewAttributeAction" + }, + { + "type": "org.apache.logging.log4j.core.appender.rolling.action.ScriptCondition" + }, + { + "type": "org.apache.logging.log4j.core.appender.routing.IdlePurgePolicy" + }, + { + "type": "org.apache.logging.log4j.core.appender.routing.Route" + }, + { + "type": "org.apache.logging.log4j.core.appender.routing.Routes" + }, + { + "type": "org.apache.logging.log4j.core.appender.routing.RoutingAppender" + }, + { + "type": "org.apache.logging.log4j.core.async.ArrayBlockingQueueFactory" + }, + { + "type": "org.apache.logging.log4j.core.async.AsyncLoggerConfig" + }, + { + "type": "org.apache.logging.log4j.core.async.AsyncLoggerConfig$RootLogger" + }, + { + "type": "org.apache.logging.log4j.core.async.AsyncWaitStrategyFactoryConfig" + }, + { + "type": "org.apache.logging.log4j.core.async.DisruptorBlockingQueueFactory" + }, + { + "type": "org.apache.logging.log4j.core.async.JCToolsBlockingQueueFactory" + }, + { + "type": "org.apache.logging.log4j.core.async.LinkedTransferQueueFactory" + }, + { + "type": "org.apache.logging.log4j.core.config.AppenderControlArraySet" + }, + { + "type": "org.apache.logging.log4j.core.config.AppenderRef", + "methods": [ + { + "name": "createAppenderRef", + "parameterTypes": [ + "java.lang.String", + "org.apache.logging.log4j.Level", + "org.apache.logging.log4j.core.Filter" + ] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.AppenderRef[]" + }, + { + "type": "org.apache.logging.log4j.core.config.AppendersPlugin", + "methods": [ + { + "name": "createAppenders", + "parameterTypes": [ + "org.apache.logging.log4j.core.Appender[]" + ] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.CustomLevelConfig" + }, + { + "type": "org.apache.logging.log4j.core.config.CustomLevels" + }, + { + "type": "org.apache.logging.log4j.core.config.DefaultAdvertiser" + }, + { + "type": "org.apache.logging.log4j.core.config.HttpWatcher" + }, + { + "type": "org.apache.logging.log4j.core.config.LoggerConfig", + "methods": [ + { + "name": "newBuilder", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.LoggerConfig$Builder", + "fields": [ + { + "name": "additivity" + }, + { + "name": "config" + }, + { + "name": "filter" + }, + { + "name": "includeLocation" + }, + { + "name": "level" + }, + { + "name": "levelAndRefs" + }, + { + "name": "loggerName" + }, + { + "name": "properties" + }, + { + "name": "refs" + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.LoggerConfig$RootLogger", + "methods": [ + { + "name": "newRootBuilder", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.LoggerConfig$RootLogger$Builder", + "fields": [ + { + "name": "additivity" + }, + { + "name": "config" + }, + { + "name": "filter" + }, + { + "name": "includeLocation" + }, + { + "name": "level" + }, + { + "name": "levelAndRefs" + }, + { + "name": "properties" + }, + { + "name": "refs" + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.LoggerConfig[]" + }, + { + "type": "org.apache.logging.log4j.core.config.LoggersPlugin", + "methods": [ + { + "name": "createLoggers", + "parameterTypes": [ + "org.apache.logging.log4j.core.config.LoggerConfig[]" + ] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.MonitorResource" + }, + { + "type": "org.apache.logging.log4j.core.config.MonitorResources" + }, + { + "type": "org.apache.logging.log4j.core.config.PropertiesPlugin" + }, + { + "type": "org.apache.logging.log4j.core.config.Property" + }, + { + "type": "org.apache.logging.log4j.core.config.Property[]" + }, + { + "type": "org.apache.logging.log4j.core.config.ScriptsPlugin" + }, + { + "type": "org.apache.logging.log4j.core.config.arbiters.ClassArbiter" + }, + { + "type": "org.apache.logging.log4j.core.config.arbiters.DefaultArbiter" + }, + { + "type": "org.apache.logging.log4j.core.config.arbiters.EnvironmentArbiter" + }, + { + "type": "org.apache.logging.log4j.core.config.arbiters.ScriptArbiter" + }, + { + "type": "org.apache.logging.log4j.core.config.arbiters.SelectArbiter" + }, + { + "type": "org.apache.logging.log4j.core.config.arbiters.SystemPropertyArbiter" + }, + { + "type": "org.apache.logging.log4j.core.config.json.JsonConfigurationFactory", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$BigDecimalConverter", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$BigIntegerConverter", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$BooleanConverter", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$ByteArrayConverter", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$ByteConverter", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$CharArrayConverter", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$CharacterConverter", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$CharsetConverter", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$ClassConverter", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$CronExpressionConverter", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$DoubleConverter", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$DurationConverter", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$FileConverter", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$FloatConverter", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$InetAddressConverter", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$IntegerConverter", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$LevelConverter", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$LongConverter", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$PathConverter", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$PatternConverter", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$SecurityProviderConverter", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$ShortConverter", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$StringConverter", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$UriConverter", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$UrlConverter", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.plugins.convert.TypeConverters$UuidConverter", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.plugins.validation.validators.RequiredValidator", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.plugins.visitors.PluginAttributeVisitor", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.plugins.visitors.PluginBuilderAttributeVisitor", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.plugins.visitors.PluginConfigurationVisitor", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.plugins.visitors.PluginElementVisitor", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.properties.PropertiesConfigurationFactory", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.xml.XmlConfigurationFactory", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.config.yaml.YamlConfigurationFactory", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.filter.AbstractFilterable$Builder", + "fields": [ + { + "name": "filter" + }, + { + "name": "propertyArray" + } + ] + }, + { + "type": "org.apache.logging.log4j.core.filter.BurstFilter" + }, + { + "type": "org.apache.logging.log4j.core.filter.CompositeFilter" + }, + { + "type": "org.apache.logging.log4j.core.filter.DenyAllFilter" + }, + { + "type": "org.apache.logging.log4j.core.filter.DynamicThresholdFilter" + }, + { + "type": "org.apache.logging.log4j.core.filter.LevelMatchFilter" + }, + { + "type": "org.apache.logging.log4j.core.filter.LevelRangeFilter" + }, + { + "type": "org.apache.logging.log4j.core.filter.MapFilter" + }, + { + "type": "org.apache.logging.log4j.core.filter.MarkerFilter" + }, + { + "type": "org.apache.logging.log4j.core.filter.MutableThreadContextMapFilter" + }, + { + "type": "org.apache.logging.log4j.core.filter.NoMarkerFilter" + }, + { + "type": "org.apache.logging.log4j.core.filter.RegexFilter" + }, + { + "type": "org.apache.logging.log4j.core.filter.ScriptFilter" + }, + { + "type": "org.apache.logging.log4j.core.filter.StringMatchFilter" + }, + { + "type": "org.apache.logging.log4j.core.filter.StructuredDataFilter" + }, + { + "type": "org.apache.logging.log4j.core.filter.ThreadContextMapFilter" + }, + { + "type": "org.apache.logging.log4j.core.filter.ThresholdFilter" + }, + { + "type": "org.apache.logging.log4j.core.filter.TimeFilter" + }, + { + "type": "org.apache.logging.log4j.core.impl.Log4jContextFactory" + }, + { + "type": "org.apache.logging.log4j.core.impl.Log4jProvider" + }, + { + "type": "org.apache.logging.log4j.core.impl.ThreadContextDataProvider" + }, + { + "type": "org.apache.logging.log4j.core.layout.CsvLogEventLayout" + }, + { + "type": "org.apache.logging.log4j.core.layout.CsvParameterLayout" + }, + { + "type": "org.apache.logging.log4j.core.layout.GelfLayout" + }, + { + "type": "org.apache.logging.log4j.core.layout.HtmlLayout" + }, + { + "type": "org.apache.logging.log4j.core.layout.JsonLayout" + }, + { + "type": "org.apache.logging.log4j.core.layout.LevelPatternSelector" + }, + { + "type": "org.apache.logging.log4j.core.layout.LoggerFields" + }, + { + "type": "org.apache.logging.log4j.core.layout.MarkerPatternSelector" + }, + { + "type": "org.apache.logging.log4j.core.layout.MessageLayout" + }, + { + "type": "org.apache.logging.log4j.core.layout.PatternLayout", + "methods": [ + { + "name": "newBuilder", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.layout.PatternLayout$Builder", + "fields": [ + { + "name": "alwaysWriteExceptions" + }, + { + "name": "charset" + }, + { + "name": "configuration" + }, + { + "name": "disableAnsi" + }, + { + "name": "footer" + }, + { + "name": "header" + }, + { + "name": "noConsoleNoAnsi" + }, + { + "name": "pattern" + }, + { + "name": "patternSelector" + }, + { + "name": "regexReplacement" + } + ] + }, + { + "type": "org.apache.logging.log4j.core.layout.PatternMatch" + }, + { + "type": "org.apache.logging.log4j.core.layout.Rfc5424Layout" + }, + { + "type": "org.apache.logging.log4j.core.layout.ScriptPatternSelector" + }, + { + "type": "org.apache.logging.log4j.core.layout.SerializedLayout" + }, + { + "type": "org.apache.logging.log4j.core.layout.SyslogLayout" + }, + { + "type": "org.apache.logging.log4j.core.layout.XmlLayout" + }, + { + "type": "org.apache.logging.log4j.core.layout.YamlLayout" + }, + { + "type": "org.apache.logging.log4j.core.lookup.ContextMapLookup", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.lookup.DateLookup", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.lookup.EnvironmentLookup", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.lookup.EventLookup", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.lookup.JavaLookup", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.lookup.JmxRuntimeInputArgumentsLookup", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.lookup.JndiLookup" + }, + { + "type": "org.apache.logging.log4j.core.lookup.Log4jLookup", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.lookup.LowerLookup", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.lookup.MainMapLookup", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.lookup.MapLookup", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.lookup.MarkerLookup", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.lookup.ResourceBundleLookup", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.lookup.StructuredDataLookup", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.lookup.SystemPropertiesLookup", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.lookup.UpperLookup", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.net.MulticastDnsAdvertiser" + }, + { + "type": "org.apache.logging.log4j.core.net.SocketAddress" + }, + { + "type": "org.apache.logging.log4j.core.net.SocketOptions" + }, + { + "type": "org.apache.logging.log4j.core.net.SocketPerformancePreferences" + }, + { + "type": "org.apache.logging.log4j.core.net.ssl.KeyStoreConfiguration" + }, + { + "type": "org.apache.logging.log4j.core.net.ssl.SslConfiguration" + }, + { + "type": "org.apache.logging.log4j.core.net.ssl.TrustStoreConfiguration" + }, + { + "type": "org.apache.logging.log4j.core.pattern.AbstractStyleNameConverter$Black" + }, + { + "type": "org.apache.logging.log4j.core.pattern.AbstractStyleNameConverter$Blue" + }, + { + "type": "org.apache.logging.log4j.core.pattern.AbstractStyleNameConverter$Cyan" + }, + { + "type": "org.apache.logging.log4j.core.pattern.AbstractStyleNameConverter$Green" + }, + { + "type": "org.apache.logging.log4j.core.pattern.AbstractStyleNameConverter$Magenta" + }, + { + "type": "org.apache.logging.log4j.core.pattern.AbstractStyleNameConverter$Red" + }, + { + "type": "org.apache.logging.log4j.core.pattern.AbstractStyleNameConverter$White" + }, + { + "type": "org.apache.logging.log4j.core.pattern.AbstractStyleNameConverter$Yellow" + }, + { + "type": "org.apache.logging.log4j.core.pattern.ClassNamePatternConverter" + }, + { + "type": "org.apache.logging.log4j.core.pattern.DatePatternConverter", + "methods": [ + { + "name": "newInstance", + "parameterTypes": [ + "java.lang.String[]" + ] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.pattern.EncodingPatternConverter" + }, + { + "type": "org.apache.logging.log4j.core.pattern.EndOfBatchPatternConverter" + }, + { + "type": "org.apache.logging.log4j.core.pattern.EqualsIgnoreCaseReplacementConverter" + }, + { + "type": "org.apache.logging.log4j.core.pattern.EqualsReplacementConverter" + }, + { + "type": "org.apache.logging.log4j.core.pattern.ExtendedThrowablePatternConverter" + }, + { + "type": "org.apache.logging.log4j.core.pattern.FileDatePatternConverter" + }, + { + "type": "org.apache.logging.log4j.core.pattern.FileLocationPatternConverter" + }, + { + "type": "org.apache.logging.log4j.core.pattern.FullLocationPatternConverter" + }, + { + "type": "org.apache.logging.log4j.core.pattern.HighlightConverter", + "methods": [ + { + "name": "newInstance", + "parameterTypes": [ + "org.apache.logging.log4j.core.config.Configuration", + "java.lang.String[]" + ] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.pattern.IntegerPatternConverter" + }, + { + "type": "org.apache.logging.log4j.core.pattern.LevelPatternConverter", + "methods": [ + { + "name": "newInstance", + "parameterTypes": [ + "java.lang.String[]" + ] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.pattern.LineLocationPatternConverter" + }, + { + "type": "org.apache.logging.log4j.core.pattern.LineSeparatorPatternConverter", + "methods": [ + { + "name": "newInstance", + "parameterTypes": [ + "java.lang.String[]" + ] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.pattern.LoggerFqcnPatternConverter" + }, + { + "type": "org.apache.logging.log4j.core.pattern.LoggerPatternConverter", + "methods": [ + { + "name": "newInstance", + "parameterTypes": [ + "java.lang.String[]" + ] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.pattern.MapPatternConverter" + }, + { + "type": "org.apache.logging.log4j.core.pattern.MarkerPatternConverter" + }, + { + "type": "org.apache.logging.log4j.core.pattern.MarkerSimpleNamePatternConverter" + }, + { + "type": "org.apache.logging.log4j.core.pattern.MaxLengthConverter" + }, + { + "type": "org.apache.logging.log4j.core.pattern.MdcPatternConverter" + }, + { + "type": "org.apache.logging.log4j.core.pattern.MessagePatternConverter", + "methods": [ + { + "name": "newInstance", + "parameterTypes": [ + "org.apache.logging.log4j.core.config.Configuration", + "java.lang.String[]" + ] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.pattern.MethodLocationPatternConverter" + }, + { + "type": "org.apache.logging.log4j.core.pattern.NanoTimePatternConverter" + }, + { + "type": "org.apache.logging.log4j.core.pattern.NdcPatternConverter" + }, + { + "type": "org.apache.logging.log4j.core.pattern.ProcessIdPatternConverter" + }, + { + "type": "org.apache.logging.log4j.core.pattern.RegexReplacement" + }, + { + "type": "org.apache.logging.log4j.core.pattern.RegexReplacementConverter" + }, + { + "type": "org.apache.logging.log4j.core.pattern.RelativeTimePatternConverter" + }, + { + "type": "org.apache.logging.log4j.core.pattern.RepeatPatternConverter" + }, + { + "type": "org.apache.logging.log4j.core.pattern.RootThrowablePatternConverter" + }, + { + "type": "org.apache.logging.log4j.core.pattern.SequenceNumberPatternConverter" + }, + { + "type": "org.apache.logging.log4j.core.pattern.StyleConverter" + }, + { + "type": "org.apache.logging.log4j.core.pattern.ThreadIdPatternConverter" + }, + { + "type": "org.apache.logging.log4j.core.pattern.ThreadNamePatternConverter" + }, + { + "type": "org.apache.logging.log4j.core.pattern.ThreadPriorityPatternConverter" + }, + { + "type": "org.apache.logging.log4j.core.pattern.ThrowablePatternConverter", + "methods": [ + { + "name": "newInstance", + "parameterTypes": [ + "org.apache.logging.log4j.core.config.Configuration", + "java.lang.String[]" + ] + } + ] + }, + { + "type": "org.apache.logging.log4j.core.pattern.UuidPatternConverter" + }, + { + "type": "org.apache.logging.log4j.core.pattern.VariablesNotEmptyReplacementConverter" + }, + { + "type": "org.apache.logging.log4j.core.script.Script" + }, + { + "type": "org.apache.logging.log4j.core.script.ScriptFile" + }, + { + "type": "org.apache.logging.log4j.core.script.ScriptRef" + }, + { + "type": "org.apache.logging.log4j.core.util.KeyValuePair" + }, + { + "type": "org.apache.logging.log4j.jul.Log4jBridgeHandler" + }, + { + "type": "org.apache.logging.log4j.util.EnvironmentPropertySource" + }, + { + "type": "org.apache.logging.log4j.util.SystemPropertiesPropertySource" + }, + { + "type": "org.apache.logging.slf4j.SLF4JProvider" + }, + { + "type": "org.apache.logging.slf4j.SLF4JServiceProvider" + }, + { + "type": "org.crac.Core" + }, + { + "type": "org.eclipse.core.runtime.FileLocator" + }, + { + "type": "org.flywaydb.core.Flyway", + "methods": [ + { + "name": "migrate", + "parameterTypes": [] + } + ] + }, + { + "type": "org.flywaydb.core.api.migration.baseline.BaselineAppliedMigration" + }, + { + "type": "org.flywaydb.core.api.migration.baseline.BaselineMigrationConfigurationExtension", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "getBaselineMigrationPrefix", + "parameterTypes": [] + }, + { + "name": "setBaselineMigrationPrefix", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "type": "org.flywaydb.core.api.migration.baseline.BaselineMigrationResolver" + }, + { + "type": "org.flywaydb.core.api.migration.baseline.BaselineResourceTypeProvider" + }, + { + "type": "org.flywaydb.core.extensibility.ConfigurationExtension" + }, + { + "type": "org.flywaydb.core.extensibility.Plugin" + }, + { + "type": "org.flywaydb.core.internal.NullFlywayTelemetryManager" + }, + { + "type": "org.flywaydb.core.internal.command.clean.CleanModeConfigurationExtension", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "getClean", + "parameterTypes": [] + }, + { + "name": "setClean", + "parameterTypes": [ + "org.flywaydb.core.internal.command.clean.CleanModel" + ] + } + ] + }, + { + "type": "org.flywaydb.core.internal.command.clean.CleanModel" + }, + { + "type": "org.flywaydb.core.internal.command.clean.SchemaModel" + }, + { + "type": "org.flywaydb.core.internal.configuration.resolvers.EnvironmentProvisionerNone" + }, + { + "type": "org.flywaydb.core.internal.configuration.resolvers.EnvironmentVariableResolver" + }, + { + "type": "org.flywaydb.core.internal.configuration.resolvers.PlaceholderPropertyResolver" + }, + { + "type": "org.flywaydb.core.internal.database.base.TestContainersDatabaseType" + }, + { + "type": "org.flywaydb.core.internal.database.h2.H2DatabaseType" + }, + { + "type": "org.flywaydb.core.internal.database.sqlite.SQLiteDatabaseType" + }, + { + "type": "org.flywaydb.core.internal.jdbc.ErrorOverrideInitializerStub" + }, + { + "type": "org.flywaydb.core.internal.logging.slf4j.Slf4jLogCreator", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.flywaydb.core.internal.proprietaryStubs.AuthCommandExtensionStub" + }, + { + "type": "org.flywaydb.core.internal.proprietaryStubs.CheckCommandExtensionStub" + }, + { + "type": "org.flywaydb.core.internal.proprietaryStubs.DeployCommandExtensionStub" + }, + { + "type": "org.flywaydb.core.internal.proprietaryStubs.DiffCommandExtensionStub" + }, + { + "type": "org.flywaydb.core.internal.proprietaryStubs.DiffTextCommandExtensionStub" + }, + { + "type": "org.flywaydb.core.internal.proprietaryStubs.GenerateCommandExtensionStub" + }, + { + "type": "org.flywaydb.core.internal.proprietaryStubs.LicensingConfigurationExtensionStub" + }, + { + "type": "org.flywaydb.core.internal.proprietaryStubs.ModelCommandExtensionStub" + }, + { + "type": "org.flywaydb.core.internal.proprietaryStubs.OfflinePermitConfigurationExtensionStub" + }, + { + "type": "org.flywaydb.core.internal.proprietaryStubs.PATTokenConfigurationExtensionStub" + }, + { + "type": "org.flywaydb.core.internal.proprietaryStubs.PrepareCommandExtensionStub" + }, + { + "type": "org.flywaydb.core.internal.proprietaryStubs.UndoCommandExtensionStub" + }, + { + "type": "org.flywaydb.core.internal.publishing.PublishingConfigurationExtension", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "isCheckDriftOnMigrate", + "parameterTypes": [] + }, + { + "name": "isPublishResult", + "parameterTypes": [] + }, + { + "name": "setCheckDriftOnMigrate", + "parameterTypes": [ + "boolean" + ] + }, + { + "name": "setPublishResult", + "parameterTypes": [ + "boolean" + ] + } + ] + }, + { + "type": "org.flywaydb.core.internal.resource.CoreResourceTypeProvider" + }, + { + "type": "org.flywaydb.core.internal.scanner.classpath.ClasspathLocationHandlerImpl" + }, + { + "type": "org.flywaydb.core.internal.scanner.filesystem.FilesystemLocationHandler" + }, + { + "type": "org.flywaydb.core.internal.schemahistory.BaseAppliedMigration" + }, + { + "type": "org.flywaydb.database.cockroachdb.CockroachDBDatabaseType" + }, + { + "type": "org.flywaydb.database.postgresql.PostgreSQLConfigurationExtension", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "getTransactional", + "parameterTypes": [] + }, + { + "name": "isTransactionalLock", + "parameterTypes": [] + }, + { + "name": "setTransactional", + "parameterTypes": [ + "org.flywaydb.database.postgresql.TransactionalModel" + ] + }, + { + "name": "setTransactionalLock", + "parameterTypes": [ + "boolean" + ] + } + ] + }, + { + "type": "org.flywaydb.database.postgresql.PostgreSQLDatabaseType" + }, + { + "type": "org.flywaydb.database.postgresql.TransactionalModel", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "getLock", + "parameterTypes": [] + }, + { + "name": "setLock", + "parameterTypes": [ + "java.lang.Boolean" + ] + } + ] + }, + { + "type": "org.osgi.framework.FrameworkUtil" + }, + { + "type": "org.postgresql.Driver", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.postgresql.core.QueryExecutorCloseAction" + }, + { + "type": "org.postgresql.jdbc.PgStatement" + }, + { + "type": "org.reactivestreams.Publisher" + }, + { + "type": "org.slf4j.Logger" + }, + { + "type": "org.slf4j.impl.StaticLoggerBinder" + }, + { + "type": "org.slf4j.spi.SLF4JServiceProvider" + }, + { + "type": "org.springframework.beans.factory.Aware" + }, + { + "type": "org.springframework.beans.factory.BeanClassLoaderAware" + }, + { + "type": "org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.beans.factory.annotation.Value" + }, + { + "type": "org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor" + }, + { + "type": "org.springframework.beans.factory.aot.BeanRegistrationAotProcessor" + }, + { + "type": "org.springframework.beans.factory.config.BeanFactoryPostProcessor" + }, + { + "type": "org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor" + }, + { + "type": "org.springframework.boot.ApplicationProperties" + }, + { + "type": "org.springframework.boot.ClearCachesApplicationListener", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.ansi.AnsiOutput$Enabled" + }, + { + "type": "org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnBean" + }, + { + "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty" + }, + { + "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnClass" + }, + { + "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean" + }, + { + "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnProperty" + }, + { + "type": "org.springframework.boot.autoconfigure.condition.OnBeanCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.condition.OnClassCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.condition.OnPropertyCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.preinitialize.BackgroundPreinitializingApplicationListener", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.preinitialize.CharsetsBackgroundPreinitializer", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.preinitialize.ConversionServiceBackgroundPreinitializer", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.builder.ParentContextCloserApplicationListener", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.boot.logging.DeferredLogFactory" + ] + } + ] + }, + { + "type": "org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.context.ContextIdApplicationContextInitializer", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.context.FileEncodingApplicationListener", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.boot.logging.DeferredLogFactory", + "org.springframework.boot.bootstrap.ConfigurableBootstrapContext" + ] + } + ] + }, + { + "type": "org.springframework.boot.context.config.ConfigDataLocation[]" + }, + { + "type": "org.springframework.boot.context.config.ConfigDataNotFoundAction" + }, + { + "type": "org.springframework.boot.context.config.ConfigDataProperties" + }, + { + "type": "org.springframework.boot.context.config.ConfigTreeConfigDataLoader", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.context.config.ConfigTreeConfigDataLocationResolver", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.core.io.ResourceLoader" + ] + } + ] + }, + { + "type": "org.springframework.boot.context.config.StandardConfigDataLoader", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.context.config.StandardConfigDataLocationResolver", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.boot.logging.DeferredLogFactory", + "org.springframework.boot.context.properties.bind.Binder", + "org.springframework.core.io.ResourceLoader" + ] + } + ] + }, + { + "type": "org.springframework.boot.context.config.SystemEnvironmentConfigDataLoader", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.context.config.SystemEnvironmentConfigDataLocationResolver", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.context.event.EventPublishingRunListener", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.boot.SpringApplication", + "java.lang.String[]" + ] + } + ] + }, + { + "type": "org.springframework.boot.context.logging.LoggingApplicationListener", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.context.properties.bind.Name" + }, + { + "type": "org.springframework.boot.env.PropertiesPropertySourceLoader", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.env.YamlPropertySourceLoader", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.io.Base64ProtocolResolver", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.io.ProtocolResolverApplicationContextInitializer", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.loader.launch.LaunchedClassLoader" + }, + { + "type": "org.springframework.boot.logging.java.JavaLoggingSystem$Factory", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.logging.log4j2.ColorConverter" + }, + { + "type": "org.springframework.boot.logging.log4j2.CorrelationIdConverter" + }, + { + "type": "org.springframework.boot.logging.log4j2.EnclosedInSquareBracketsConverter" + }, + { + "type": "org.springframework.boot.logging.log4j2.ExtendedWhitespaceThrowablePatternConverter" + }, + { + "type": "org.springframework.boot.logging.log4j2.Log4J2LoggingSystem$Factory", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.logging.log4j2.SpringBootConfigurationFactory", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.logging.log4j2.SpringBootPropertySource" + }, + { + "type": "org.springframework.boot.logging.log4j2.SpringEnvironmentLookup", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.logging.log4j2.SpringProfileArbiter" + }, + { + "type": "org.springframework.boot.logging.log4j2.StructuredLogLayout" + }, + { + "type": "org.springframework.boot.logging.log4j2.WhitespaceThrowablePatternConverter" + }, + { + "type": "org.springframework.boot.logging.logback.LogbackLoggingSystem$Factory", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.support.AnsiOutputApplicationListener", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.support.EnvironmentPostProcessorApplicationListener", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.support.RandomValuePropertySourceEnvironmentPostProcessor", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.boot.logging.DeferredLogFactory" + ] + } + ] + }, + { + "type": "org.springframework.boot.support.SpringApplicationJsonEnvironmentPostProcessor", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.support.SystemEnvironmentPropertySourceEnvironmentPostProcessor", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.context.ApplicationListener" + }, + { + "type": "org.springframework.context.ApplicationStartupAware" + }, + { + "type": "org.springframework.context.EnvironmentAware" + }, + { + "type": "org.springframework.context.ResourceLoaderAware" + }, + { + "type": "org.springframework.context.annotation.Bean" + }, + { + "type": "org.springframework.context.annotation.CommonAnnotationBeanPostProcessor", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.context.annotation.Conditional" + }, + { + "type": "org.springframework.context.annotation.Configuration" + }, + { + "type": "org.springframework.context.annotation.ConfigurationClassPostProcessor", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "setMetadataReaderFactory", + "parameterTypes": [ + "org.springframework.core.type.classreading.MetadataReaderFactory" + ] + } + ] + }, + { + "type": "org.springframework.context.annotation.DependsOn" + }, + { + "type": "org.springframework.context.annotation.Import" + }, + { + "type": "org.springframework.context.annotation.ImportRuntimeHints" + }, + { + "type": "org.springframework.context.annotation.PropertySource" + }, + { + "type": "org.springframework.context.annotation.PropertySources" + }, + { + "type": "org.springframework.context.event.DefaultEventListenerFactory", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.context.event.EventListenerMethodProcessor", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.core.Ordered" + }, + { + "type": "org.springframework.core.PriorityOrdered" + }, + { + "type": "org.springframework.core.annotation.AliasFor" + }, + { + "type": "org.springframework.core.annotation.AnnotationAttributes[]" + }, + { + "type": "org.springframework.core.annotation.MergedAnnotation[]" + }, + { + "type": "org.springframework.core.annotation.Order" + }, + { + "type": "org.springframework.core.type.classreading.CachingMetadataReaderFactory" + }, + { + "type": "org.springframework.core.type.classreading.MetadataReaderFactory" + }, + { + "type": "org.springframework.stereotype.Component" + }, + { + "type": "org.springframework.stereotype.Indexed" + }, + { + "type": "org.springframework.util.ConcurrentReferenceHashMap$Segment[]" + }, + { + "type": "reactor.core.publisher.FluxConcatMapNoPrefetch$FluxConcatMapNoPrefetchSubscriber" + }, + { + "type": "reactor.core.publisher.FluxIterable$IterableSubscription" + }, + { + "type": "reactor.core.publisher.FluxIterable$IterableSubscriptionConditional" + }, + { + "type": "reactor.core.publisher.FluxSink[]" + }, + { + "type": "reactor.core.publisher.LambdaMonoSubscriber" + }, + { + "type": "reactor.core.publisher.MonoCompletionStage$MonoCompletionStageSubscription" + }, + { + "type": "reactor.core.publisher.MonoFlatMap$FlatMapMain" + }, + { + "type": "reactor.core.publisher.Operators$BaseFluxToMonoOperator" + }, + { + "type": "reactor.core.publisher.Operators$MultiSubscriptionSubscriber" + }, + { + "type": "reactor.core.publisher.Operators$ScalarSubscription" + }, + { + "type": "reactor.core.scheduler.BoundedElasticScheduler" + }, + { + "type": "reactor.core.scheduler.BoundedElasticScheduler$BoundedServices" + }, + { + "type": "reactor.core.scheduler.BoundedElasticScheduler$BoundedState" + }, + { + "type": "reactor.core.scheduler.ParallelScheduler" + }, + { + "type": "reactor.core.scheduler.SchedulerTask" + }, + { + "type": "reactor.core.scheduler.SingleScheduler" + }, + { + "type": "reactor.pool.AllocationStrategies$SizeBasedAllocationStrategy" + }, + { + "type": "reactor.pool.SimpleDequePool" + }, + { + "type": "sun.management.VMManagementImpl", + "jniAccessible": true, + "fields": [ + { + "name": "compTimeMonitoringSupport" + }, + { + "name": "currentThreadCpuTimeSupport" + }, + { + "name": "objectMonitorUsageSupport" + }, + { + "name": "otherThreadCpuTimeSupport" + }, + { + "name": "remoteDiagnosticCommandsSupport" + }, + { + "name": "synchronizerUsageSupport" + }, + { + "name": "threadAllocatedMemorySupport" + }, + { + "name": "threadContentionMonitoringSupport" + } + ] + }, + { + "type": "sun.security.pkcs12.PKCS12KeyStore", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.pkcs12.PKCS12KeyStore$DualFormatPKCS12", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.provider.DSA$SHA224withDSA", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.provider.DSA$SHA256withDSA", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.provider.NativePRNG", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.security.SecureRandomParameters" + ] + } + ] + }, + { + "type": "sun.security.provider.SHA", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.provider.SHA2$SHA224", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.provider.SHA2$SHA256", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.provider.SHA5$SHA384", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.provider.SHA5$SHA512", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.provider.X509Factory", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.rsa.PSSParameters", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.rsa.RSAKeyFactory$Legacy", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.rsa.RSAPSSSignature", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.rsa.RSASignature$SHA224withRSA", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.rsa.RSASignature$SHA384withRSA", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.ssl.KeyManagerFactoryImpl$SunX509", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.ssl.SSLContextImpl$TLS12Context", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.ssl.TrustManagerFactoryImpl$PKIXFactory", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.x509.AuthorityInfoAccessExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.security.x509.AuthorityKeyIdentifierExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.security.x509.BasicConstraintsExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.security.x509.CRLDistributionPointsExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.security.x509.CertificatePoliciesExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.security.x509.ExtendedKeyUsageExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.security.x509.KeyUsageExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.security.x509.NetscapeCertTypeExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.security.x509.PrivateKeyUsageExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.security.x509.SubjectAlternativeNameExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.security.x509.SubjectKeyIdentifierExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.text.resources.cldr.FormatData" + }, + { + "type": "sun.text.resources.cldr.FormatData_en" + }, + { + "type": "sun.text.resources.cldr.FormatData_en_US" + }, + { + "type": "sun.util.resources.cldr.CalendarData" + }, + { + "type": "tools.jackson.databind.deser.Deserializers[]" + }, + { + "type": "tools.jackson.databind.deser.KeyDeserializers[]" + }, + { + "type": "tools.jackson.databind.deser.ValueInstantiators[]" + }, + { + "type": "tools.jackson.databind.ser.Serializers[]" + }, + { + "type": { + "proxy": [ + "java.lang.reflect.ParameterizedType", + "org.springframework.core.SerializableTypeWrapper$SerializableTypeProxy", + "java.io.Serializable" + ] + } + }, + { + "type": { + "proxy": [ + "java.lang.reflect.TypeVariable", + "org.springframework.core.SerializableTypeWrapper$SerializableTypeProxy", + "java.io.Serializable" + ] + } + }, + { + "type": { + "proxy": [ + "java.lang.reflect.WildcardType", + "org.springframework.core.SerializableTypeWrapper$SerializableTypeProxy", + "java.io.Serializable" + ] + } + }, + { + "type": { + "lambda": { + "declaringClass": "javasabr.mqtt.broker.application.config.MqttExternalPlainNetworkConfig", + "interfaces": [ + "org.springframework.context.ApplicationListener" + ] + } + } + }, + { + "type": { + "lambda": { + "declaringClass": "javasabr.mqtt.broker.application.config.MqttExternalTlsNetworkConfig", + "interfaces": [ + "org.springframework.context.ApplicationListener" + ] + } + } + } + ], + "resources": [ + { + "glob": "META-INF/log4j-provider.properties" + }, + { + "glob": "META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat" + }, + { + "glob": "META-INF/services/io.r2dbc.postgresql.extension.Extension" + }, + { + "glob": "META-INF/services/io.r2dbc.spi.ConnectionFactoryProvider" + }, + { + "glob": "META-INF/services/java.net.spi.InetAddressResolverProvider" + }, + { + "glob": "META-INF/services/java.net.spi.URLStreamHandlerProvider" + }, + { + "glob": "META-INF/services/java.nio.channels.spi.AsynchronousChannelProvider" + }, + { + "glob": "META-INF/services/java.time.zone.ZoneRulesProvider" + }, + { + "glob": "META-INF/services/javasabr.rlib.logger.api.LoggerFactory" + }, + { + "glob": "META-INF/services/javax.xml.parsers.DocumentBuilderFactory" + }, + { + "glob": "META-INF/services/org.apache.commons.logging.LogFactory" + }, + { + "glob": "META-INF/services/org.apache.logging.log4j.core.util.ContextDataProvider" + }, + { + "glob": "META-INF/services/org.apache.logging.log4j.core.util.WatchEventService" + }, + { + "glob": "META-INF/services/org.apache.logging.log4j.spi.Provider" + }, + { + "glob": "META-INF/services/org.apache.logging.log4j.util.PropertySource" + }, + { + "glob": "META-INF/services/org.flywaydb.core.extensibility.Plugin" + }, + { + "glob": "META-INF/services/org.slf4j.spi.SLF4JServiceProvider" + }, + { + "glob": "META-INF/spring.components" + }, + { + "glob": "META-INF/spring.factories" + }, + { + "glob": "application-default.properties" + }, + { + "glob": "application-default.xml" + }, + { + "glob": "application-default.yaml" + }, + { + "glob": "application-default.yml" + }, + { + "glob": "application.properties" + }, + { + "glob": "application.xml" + }, + { + "glob": "application.yaml" + }, + { + "glob": "application.yml" + }, + { + "glob": "banner.txt" + }, + { + "glob": "commons-logging.properties" + }, + { + "glob": "config/application-default.properties" + }, + { + "glob": "config/application-default.xml" + }, + { + "glob": "config/application-default.yaml" + }, + { + "glob": "config/application-default.yml" + }, + { + "glob": "config/application.properties" + }, + { + "glob": "config/application.xml" + }, + { + "glob": "config/application.yaml" + }, + { + "glob": "config/application.yml" + }, + { + "glob": "db/callback" + }, + { + "glob": "db/migration" + }, + { + "glob": "db/migration/V1__Create_User_Credentials_Table.sql" + }, + { + "glob": "javasabr/mqtt/auth/service/config/AuthenticationServiceSpringConfig.class" + }, + { + "glob": "javasabr/mqtt/auth/service/config/DatabaseCredentialsSourceSpringConfig.class" + }, + { + "glob": "javasabr/mqtt/auth/service/config/FileCredentialsSourceSpringConfig.class" + }, + { + "glob": "javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.class" + }, + { + "glob": "javasabr/mqtt/broker/application/config/MqttExternalPlainNetworkConfig.class" + }, + { + "glob": "javasabr/mqtt/broker/application/config/MqttExternalTlsNetworkConfig.class" + }, + { + "glob": "log4j2-test.jsn" + }, + { + "glob": "log4j2-test.json" + }, + { + "glob": "log4j2-test.properties" + }, + { + "glob": "log4j2-test.springboot" + }, + { + "glob": "log4j2-test.xml" + }, + { + "glob": "log4j2-test.yaml" + }, + { + "glob": "log4j2-test.yml" + }, + { + "glob": "log4j2-test27bc2616.jsn" + }, + { + "glob": "log4j2-test27bc2616.json" + }, + { + "glob": "log4j2-test27bc2616.properties" + }, + { + "glob": "log4j2-test27bc2616.springboot" + }, + { + "glob": "log4j2-test27bc2616.xml" + }, + { + "glob": "log4j2-test27bc2616.yaml" + }, + { + "glob": "log4j2-test27bc2616.yml" + }, + { + "glob": "log4j2.StatusLogger.properties" + }, + { + "glob": "log4j2.component.properties" + }, + { + "glob": "log4j2.jsn" + }, + { + "glob": "log4j2.json" + }, + { + "glob": "log4j2.properties" + }, + { + "glob": "log4j2.system.properties" + }, + { + "glob": "log4j2.xml" + }, + { + "glob": "log4j2.yaml" + }, + { + "glob": "log4j2.yml" + }, + { + "glob": "log4j227bc2616.jsn" + }, + { + "glob": "log4j227bc2616.json" + }, + { + "glob": "log4j227bc2616.properties" + }, + { + "glob": "log4j227bc2616.springboot" + }, + { + "glob": "log4j227bc2616.xml" + }, + { + "glob": "log4j227bc2616.yaml" + }, + { + "glob": "log4j227bc2616.yml" + }, + { + "glob": "org/flywaydb/core/internal/version.txt" + }, + { + "glob": "org/postgresql/driverconfig.properties" + }, + { + "glob": "spring.properties" + }, + { + "module": "java.base", + "glob": "jdk/internal/icu/impl/data/icudt76b/nfkc.nrm" + } + ] +} \ No newline at end of file From 608ab8198f74e53317db133f512111b40680f349 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Mon, 8 Jun 2026 18:50:40 +0200 Subject: [PATCH 07/11] feature: [172] Improve nativeImageTest task --- application/build.gradle | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/application/build.gradle b/application/build.gradle index 27912622..5b4b23b1 100644 --- a/application/build.gradle +++ b/application/build.gradle @@ -58,6 +58,7 @@ jar { processAot { jvmArgs([ + '-Dauthentication.credentials-source.database.enabled=true', '-Dauthentication.credentials-source.file.enabled=true', '-Dauthentication.provider.basic.enabled=true', '-Dmqtt.external.tls.network.enabled=true', @@ -81,5 +82,13 @@ tasks.register('copyNativeImageTestResources', Copy) { tasks.register('nativeImageTest', Test) { dependsOn 'nativeCompile', 'copyNativeImageTestResources' - finalizedBy 'test' + useJUnitPlatform() + + group = 'verification' + testClassesDirs = sourceSets.test.output.classesDirs + classpath = sourceSets.test.runtimeClasspath + + filter { + includeTestsMatching 'javasabr.mqtt.broker.application.NativeImageVerificationTest' + } } From 40cf4340b92aa17c0cc9105cee9ddb1925154c2f Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Mon, 8 Jun 2026 22:25:03 +0200 Subject: [PATCH 08/11] feature: [172] Cover more cases in ClassPathUriResolver --- .../base/util/ClassPathResourceResolver.java | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/base/src/main/java/javasabr/mqtt/base/util/ClassPathResourceResolver.java b/base/src/main/java/javasabr/mqtt/base/util/ClassPathResourceResolver.java index 4347ef12..a14619dc 100644 --- a/base/src/main/java/javasabr/mqtt/base/util/ClassPathResourceResolver.java +++ b/base/src/main/java/javasabr/mqtt/base/util/ClassPathResourceResolver.java @@ -9,27 +9,38 @@ public final class ClassPathResourceResolver { + private static final ClassLoader CLASS_LOADER = ClassPathResourceResolver.class.getClassLoader(); + private ClassPathResourceResolver() {} public static InputStream newInputStream(URI uri) throws IOException { if (uri == null) { throw new IllegalArgumentException("URI must not be null"); } - if ("classpath".equalsIgnoreCase(uri.getScheme())) { - String resourcePath = uri.getSchemeSpecificPart(); - if (resourcePath == null || resourcePath.isEmpty()) { - throw new IllegalArgumentException("Classpath URI must have a non-empty resource path: %s".formatted(uri)); - } - Path localPath = Path.of(resourcePath); - if (Files.exists(localPath)) { - return Files.newInputStream(localPath); - } - throw new FileNotFoundException(uri.toString()); - } - Path localPath = Path.of(uri); - if (Files.exists(localPath)) { - return Files.newInputStream(localPath); + switch (uri.getScheme()) { + case "classpath": + String sanitizedPath = uri.getSchemeSpecificPart(); + if (sanitizedPath == null || sanitizedPath.isEmpty()) { + throw new IllegalArgumentException("Classpath URI must have a non-empty resource path: %s".formatted(uri)); + } + Path resourcePath = Path.of(sanitizedPath); + if (Files.exists(resourcePath)) { + return Files.newInputStream(resourcePath); + } + if (sanitizedPath.startsWith("/")) { + sanitizedPath = sanitizedPath.substring(1); + } + InputStream resourceAsStream = CLASS_LOADER.getResourceAsStream(sanitizedPath); + if (resourceAsStream != null) { + return resourceAsStream; + } + throw new FileNotFoundException(uri.toString()); + default: + Path localPath = Path.of(uri); + if (Files.exists(localPath)) { + return Files.newInputStream(localPath); + } + throw new FileNotFoundException(uri.toString()); } - throw new FileNotFoundException(uri.toString()); } } From 11db9fbd2fb7722ccd3635f168d25c640d2367e8 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Mon, 8 Jun 2026 22:33:28 +0200 Subject: [PATCH 09/11] feature: [172] Replace private constructor with Lombok annotation --- .../javasabr/mqtt/base/util/ClassPathResourceResolver.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/base/src/main/java/javasabr/mqtt/base/util/ClassPathResourceResolver.java b/base/src/main/java/javasabr/mqtt/base/util/ClassPathResourceResolver.java index a14619dc..1d81f62d 100644 --- a/base/src/main/java/javasabr/mqtt/base/util/ClassPathResourceResolver.java +++ b/base/src/main/java/javasabr/mqtt/base/util/ClassPathResourceResolver.java @@ -6,13 +6,14 @@ import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +@NoArgsConstructor(access = AccessLevel.PRIVATE) public final class ClassPathResourceResolver { private static final ClassLoader CLASS_LOADER = ClassPathResourceResolver.class.getClassLoader(); - private ClassPathResourceResolver() {} - public static InputStream newInputStream(URI uri) throws IOException { if (uri == null) { throw new IllegalArgumentException("URI must not be null"); From 83bf7e435cffe23db0b53fe6bcaf8d3903049eeb Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Mon, 8 Jun 2026 22:33:56 +0200 Subject: [PATCH 10/11] feature: [172] Close input streams --- .../impl/GroovyDslBasedAuthorizationService.java | 5 ++--- .../base/util/ClassPathResourceResolverTest.groovy | 10 ++++++++-- .../auth/credentials/source/FileCredentialsSource.java | 5 +++-- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/acl-service/src/main/java/javasabr/mqtt/acl/service/impl/GroovyDslBasedAuthorizationService.java b/acl-service/src/main/java/javasabr/mqtt/acl/service/impl/GroovyDslBasedAuthorizationService.java index 1886488f..a4544fec 100644 --- a/acl-service/src/main/java/javasabr/mqtt/acl/service/impl/GroovyDslBasedAuthorizationService.java +++ b/acl-service/src/main/java/javasabr/mqtt/acl/service/impl/GroovyDslBasedAuthorizationService.java @@ -18,9 +18,8 @@ public class GroovyDslBasedAuthorizationService extends AclEngineBasedAuthorizationService { public void loadFrom(URI resource) { - try { - InputStream localFile = ClassPathResourceResolver.newInputStream(resource); - Map> loadedAclRulesMap = AclRulesLoader.load(localFile); + try (InputStream aclInputStream = ClassPathResourceResolver.newInputStream(resource)) { + Map> loadedAclRulesMap = AclRulesLoader.load(aclInputStream); switchTo(new AclEngine(loadedAclRulesMap)); log.info(resource, loadedAclRulesMap, GroovyDslBasedAuthorizationService::buildServiceDescription); } catch (IOException e) { diff --git a/base/src/test/groovy/javasabr/mqtt/base/util/ClassPathResourceResolverTest.groovy b/base/src/test/groovy/javasabr/mqtt/base/util/ClassPathResourceResolverTest.groovy index 45e0ab57..c19d39a3 100644 --- a/base/src/test/groovy/javasabr/mqtt/base/util/ClassPathResourceResolverTest.groovy +++ b/base/src/test/groovy/javasabr/mqtt/base/util/ClassPathResourceResolverTest.groovy @@ -18,25 +18,30 @@ class ClassPathResourceResolverTest extends UnitSpecification { then: new String(result.readAllBytes(), StandardCharsets.UTF_8) == "hello" cleanup: + result.close() Files.deleteIfExists(tempFile) } def "should resolve classpath URI to existing Path"() { given: - def uri = URI.create("classpath:javasabr/mqtt/base/util/ClassPathResourceStreamer.class") + def uri = URI.create("classpath:javasabr/mqtt/base/util/ClassPathResourceResolver.class") when: def result = ClassPathResourceResolver.newInputStream(uri) then: result.available() > 0 + cleanup: + result.close() } def "should handle classpath URI with leading slash"() { given: - def uri = URI.create("classpath:/javasabr/mqtt/base/util/ClassPathResourceStreamer.class") + def uri = URI.create("classpath:/javasabr/mqtt/base/util/ClassPathResourceResolver.class") when: def result = ClassPathResourceResolver.newInputStream(uri) then: result.available() > 0 + cleanup: + result.close() } def "should throw NullPointerException for null URI"() { @@ -75,6 +80,7 @@ class ClassPathResourceResolverTest extends UnitSpecification { then: new String(result.readAllBytes(), StandardCharsets.UTF_8) == "fallback content" cleanup: + result.close() Files.deleteIfExists(resourceFile) Files.deleteIfExists(testDir) } diff --git a/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java b/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java index b0bf1b4a..44d535e3 100644 --- a/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java +++ b/credentials-source-file/src/main/java/javasabr/mqtt/auth/credentials/source/FileCredentialsSource.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.annotation.JsonValue; import java.io.IOException; +import java.io.InputStream; import java.net.URI; import java.util.Map; import javasabr.mqtt.auth.api.CredentialsSourceType; @@ -22,8 +23,8 @@ public FileCredentialsSource(URI fileName) { } private void init() { - try { - reset(ClassPathResourceResolver.newInputStream(fileName)); + try (InputStream aclInputStream = ClassPathResourceResolver.newInputStream(fileName)) { + reset(aclInputStream); } catch (IOException e) { throw new CredentialsSourceException("Error during credentials file read", e); } From ac2bfd0872aad9c8d46b5b701afb23d42a519f33 Mon Sep 17 00:00:00 2001 From: Maksim Kashapov <56276969+crazyrokr@users.noreply.github.com> Date: Mon, 8 Jun 2026 22:40:57 +0200 Subject: [PATCH 11/11] s --- .../broker/application/MqttBrokerApplication.java | 1 + .../application/config/MqttBrokerSpringConfig.java | 2 ++ .../config/MqttExternalPlainNetworkConfig.java | 6 ++++++ .../application/config/NativeConfigurationHints.java | 12 ++++++------ 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/application/src/main/java/javasabr/mqtt/broker/application/MqttBrokerApplication.java b/application/src/main/java/javasabr/mqtt/broker/application/MqttBrokerApplication.java index fc6df6a7..7d9b9ec8 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/MqttBrokerApplication.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/MqttBrokerApplication.java @@ -3,6 +3,7 @@ import javasabr.mqtt.broker.application.config.MqttBrokerSpringConfig; import javasabr.mqtt.broker.application.config.NativeConfigurationHints; import lombok.RequiredArgsConstructor; +import org.springframework.aot.hint.annotation.RegisterReflectionForBinding; import org.springframework.boot.SpringApplication; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.ImportRuntimeHints; diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java index 9830d9e1..456d3093 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java @@ -59,6 +59,7 @@ import javasabr.mqtt.service.session.MqttSessionService; import javasabr.mqtt.service.session.impl.InMemoryMqttSessionService; import lombok.CustomLog; +import org.springframework.aot.hint.annotation.RegisterReflectionForBinding; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; @@ -129,6 +130,7 @@ PublishDataStorage publishDataStorage() { } @Bean + @RegisterReflectionForBinding(java.util.UUID[].class) IncomingPublishStorage incomingPublishStorage( PublishDataStorage publishDataStorage, @Value("${in.memory.incoming.publish.storage.clean.interval.ms:60000}") int cleanInterval) { diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttExternalPlainNetworkConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttExternalPlainNetworkConfig.java index cf6a36a7..34b11a1b 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttExternalPlainNetworkConfig.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttExternalPlainNetworkConfig.java @@ -12,6 +12,7 @@ import javasabr.rlib.network.ServerNetworkConfig; import javasabr.rlib.network.server.ServerNetwork; import lombok.CustomLog; +import org.springframework.aot.hint.annotation.RegisterReflectionForBinding; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.event.ApplicationStartedEvent; @@ -62,6 +63,11 @@ InetSocketAddress externalPlainNetworkAddress( } @Bean + @RegisterReflectionForBinding({ + java.util.function.Consumer[].class, + java.util.function.BiConsumer[].class, + reactor.core.publisher.FluxSink[].class + }) ServerNetwork externalPlainNetwork( ServerNetworkConfig externalPlainNetworkConfig, MqttConnectionFactory externalPlainConnectionFactory) { diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/NativeConfigurationHints.java b/application/src/main/java/javasabr/mqtt/broker/application/config/NativeConfigurationHints.java index d5950df0..aa012dae 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/NativeConfigurationHints.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/NativeConfigurationHints.java @@ -20,12 +20,12 @@ public class NativeConfigurationHints implements RuntimeHintsRegistrar { }; private static final String[] JDK_ARRAY_TYPES = { - "java.util.function.Consumer[]", - "java.util.function.BiConsumer[]", - "java.nio.ByteBuffer[]", - "reactor.core.publisher.FluxSink[]", - "java.lang.String[]", - "java.util.UUID[]", +// "java.util.function.Consumer[]", +// "java.util.function.BiConsumer[]", +// "java.nio.ByteBuffer[]", +// "reactor.core.publisher.FluxSink[]", +// "java.lang.String[]", +// "java.util.UUID[]", }; @Override