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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions hivemq-edge/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -605,3 +605,42 @@ artifacts {
add(releaseJar.name, tasks.shadowJar)
add(thirdPartyLicenses.name, tasks.updateThirdPartyLicenses.flatMap { it.outputDirectory })
}

/* ******************** XSD Generation ******************** */

val generateXsd by tasks.registering(JavaExec::class) {
group = "build"
description = "Generates XSD schema from JAXB-annotated configuration entity classes"

dependsOn(tasks.testClasses)

mainClass.set("com.hivemq.configuration.GenSchemaMain")
classpath = sourceSets.test.get().runtimeClasspath

val outputDir = layout.buildDirectory.dir("generated-resources/xsd")
val outputFile = outputDir.map { it.file("config.xsd") }
args(outputFile.get().asFile.absolutePath)

outputs.dir(outputDir)

doFirst {
outputDir.get().asFile.mkdirs()
}
}

// Copy XSD to resources directory for version control
// The XSD is included in the jar automatically via processResources from src/main/resources
val copyXsdToResources by tasks.registering(Copy::class) {
group = "build"
description = "Copies generated XSD to src/main/resources for version control"

dependsOn(generateXsd)

from(generateXsd.map { it.outputs.files })
into(file("src/main/resources"))
}

// Run XSD copy as part of the build (after jar, avoiding circular dependency with processResources)
tasks.build {
dependsOn(copyXsdToResources)
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,16 @@
import org.jetbrains.annotations.Nullable;

import jakarta.xml.bind.ValidationEvent;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

@XmlRootElement(name = "instruction")
@XmlAccessorType(XmlAccessType.NONE)
public class InstructionEntity implements EntityValidatable {

@JsonProperty("source")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public class AdminApiEntity extends EnabledEntity {
@XmlElementRefs({
@XmlElementRef(required = false, type = HttpListenerEntity.class),
@XmlElementRef(required = false, type = HttpsListenerEntity.class)})
private @NotNull List<ApiListenerEntity> listeners;
private @NotNull List<ApiListenreserEntity> listeners;

@XmlElementRef(required = false)
private @NotNull ApiJwsEntity jws;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,18 @@
import org.jetbrains.annotations.Nullable;

import jakarta.xml.bind.ValidationEvent;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlElementWrapper;
import jakarta.xml.bind.annotation.XmlRootElement;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;


@XmlRootElement(name = "data-combiner")
@XmlAccessorType(XmlAccessType.NONE)
public class DataCombinerEntity {
@JsonProperty(value = "id", required = true)
@XmlElement(name = "id", required = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,17 @@
package com.hivemq.configuration.entity.combining;

import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Objects;

@XmlRootElement(name = "destination")
@XmlAccessorType(XmlAccessType.NONE)
public class DataCombiningDestinationEntity {

@JsonProperty(value = "assetId")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,19 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlElementWrapper;
import jakarta.xml.bind.annotation.XmlRootElement;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;


// JAXB can not handle records ... :-(
@XmlRootElement(name = "data-combining")
@XmlAccessorType(XmlAccessType.NONE)
public class DataCombiningEntity {

@JsonProperty(value = "id", required = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,18 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlElementWrapper;
import jakarta.xml.bind.annotation.XmlRootElement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;


@XmlRootElement(name = "sources")
@XmlAccessorType(XmlAccessType.NONE)
public class DataCombiningSourcesEntity {

@JsonProperty("primaryReference")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,15 @@
import org.jetbrains.annotations.NotNull;

import jakarta.xml.bind.ValidationEvent;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import java.util.List;
import java.util.Objects;

@XmlRootElement(name = "primary-reference")
@XmlAccessorType(XmlAccessType.NONE)
public class DataIdentifierReferenceEntity implements EntityValidatable {

@JsonProperty("id")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,14 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import java.util.Objects;

@XmlRootElement(name = "entity-reference")
@XmlAccessorType(XmlAccessType.NONE)
public class EntityReferenceEntity {

@JsonProperty(value = "type", required = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,30 @@
*/
package com.hivemq.configuration.entity.mqtt;

import jakarta.xml.bind.Marshaller;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlAnyElement;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlMixed;
import jakarta.xml.bind.annotation.XmlRootElement;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

import static com.hivemq.configuration.entity.mqtt.MqttConfigurationDefaults.MAX_EXPIRY_INTERVAL_DEFAULT;

/**
* Configuration entity for message expiry settings.
* <p>
* Supports two XML formats for backwards compatibility:
* <ul>
* <li>Simple format: {@code <message-expiry>123</message-expiry>}</li>
* <li>Nested format: {@code <message-expiry><max-interval>123</max-interval></message-expiry>}</li>
* </ul>
* Always writes in nested format for consistency.
*
* @author Florian Limpöck
* @since 4.0.0
*/
Expand All @@ -33,12 +47,65 @@
@SuppressWarnings({"FieldMayBeFinal", "FieldCanBeLocal"})
public class MessageExpiryConfigEntity {

// For reading: captures both text content and child elements
@XmlMixed
@XmlAnyElement
private List<Object> content = new ArrayList<>();

// For writing: outputs as <max-interval>value</max-interval>
@XmlElement(name = "max-interval", defaultValue = "4294967296")
// => 136 Years = Unsigned Integer Max Value in seconds
private long maxInterval = MAX_EXPIRY_INTERVAL_DEFAULT;
private Long maxIntervalForWrite;

// Cached parsed value
private Long parsedMaxInterval;

public long getMaxInterval() {
return maxInterval;
if (parsedMaxInterval == null) {
parsedMaxInterval = parseValue();
}
return parsedMaxInterval != null ? parsedMaxInterval : MAX_EXPIRY_INTERVAL_DEFAULT;
}

/**
* Called by JAXB before marshalling to ensure the write field is populated.
*/
@SuppressWarnings("unused")
void beforeMarshal(final Marshaller marshaller) {
maxIntervalForWrite = getMaxInterval();
content = null; // Clear mixed content for clean output
}

/**
* Parses the value from either the @XmlElement field (nested format) or the mixed content (simple format).
* <p>
* Priority:
* 1. If maxIntervalForWrite was set by JAXB via @XmlElement, use it (nested format)
* 2. Otherwise, check the mixed content for simple text format
*/
private Long parseValue() {
// First check if JAXB parsed the nested <max-interval> element
if (maxIntervalForWrite != null) {
return maxIntervalForWrite;
}

// Otherwise, check mixed content for simple text format
if (content == null) {
return null;
}
for (final Object item : content) {
if (item instanceof String text) {
// Simple text format: 123
final String trimmed = text.trim();
if (!trimmed.isEmpty()) {
try {
return Long.parseLong(trimmed);
} catch (final NumberFormatException e) {
// Whitespace or invalid text, ignore
}
}
}
}
return null;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,30 @@
*/
package com.hivemq.configuration.entity.mqtt;

import jakarta.xml.bind.Marshaller;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlAnyElement;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlMixed;
import jakarta.xml.bind.annotation.XmlRootElement;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

import static com.hivemq.mqtt.message.connect.Mqtt5CONNECT.SESSION_EXPIRY_MAX;

/**
* Configuration entity for session expiry settings.
* <p>
* Supports two XML formats for backwards compatibility:
* <ul>
* <li>Simple format: {@code <session-expiry>123</session-expiry>}</li>
* <li>Nested format: {@code <session-expiry><max-interval>123</max-interval></session-expiry>}</li>
* </ul>
* Always writes in nested format for consistency.
*
* @author Florian Limpöck
* @since 4.0.0
*/
Expand All @@ -33,12 +47,65 @@
@SuppressWarnings({"FieldMayBeFinal", "FieldCanBeLocal"})
public class SessionExpiryConfigEntity {

// For reading: captures both text content and child elements
@XmlMixed
@XmlAnyElement
private List<Object> content = new ArrayList<>();

// For writing: outputs as <max-interval>value</max-interval>
@XmlElement(name = "max-interval", defaultValue = "4294967295")
// => 136 Years = Unsigned Integer Max Value in seconds
private long maxInterval = SESSION_EXPIRY_MAX;
private Long maxIntervalForWrite;

// Cached parsed value
private Long parsedMaxInterval;

public long getMaxInterval() {
return maxInterval;
if (parsedMaxInterval == null) {
parsedMaxInterval = parseValue();
}
return parsedMaxInterval != null ? parsedMaxInterval : SESSION_EXPIRY_MAX;
}

/**
* Called by JAXB before marshalling to ensure the write field is populated.
*/
@SuppressWarnings("unused")
void beforeMarshal(final Marshaller marshaller) {
maxIntervalForWrite = getMaxInterval();
content = null; // Clear mixed content for clean output
}

/**
* Parses the value from either the @XmlElement field (nested format) or the mixed content (simple format).
* <p>
* Priority:
* 1. If maxIntervalForWrite was set by JAXB via @XmlElement, use it (nested format)
* 2. Otherwise, check the mixed content for simple text format
*/
private Long parseValue() {
// First check if JAXB parsed the nested <max-interval> element
if (maxIntervalForWrite != null) {
return maxIntervalForWrite;
}

// Otherwise, check mixed content for simple text format
if (content == null) {
return null;
}
for (final Object item : content) {
if (item instanceof String text) {
// Simple text format: 123
final String trimmed = text.trim();
if (!trimmed.isEmpty()) {
try {
return Long.parseLong(trimmed);
} catch (final NumberFormatException e) {
// Whitespace or invalid text, ignore
}
}
}
}
return null;
}

@Override
Expand Down
Loading
Loading