diff --git a/.gitignore b/.gitignore
index 108c596311..49f40df854 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,3 +26,5 @@ dependency_tree.txt
itests/snapshots_repository/
itests/archives/
.env.local
+.venv*
+javadoc_*
diff --git a/api/src/main/java/org/apache/unomi/api/ClusterNode.java b/api/src/main/java/org/apache/unomi/api/ClusterNode.java
index 66a4ee7c4a..a80b5689f1 100644
--- a/api/src/main/java/org/apache/unomi/api/ClusterNode.java
+++ b/api/src/main/java/org/apache/unomi/api/ClusterNode.java
@@ -24,6 +24,7 @@ public class ClusterNode extends Item {
private static final long serialVersionUID = 1281422346318230514L;
+ // Item type identifier for cluster nodes.
public static final String ITEM_TYPE = "clusterNode";
private double cpuLoad;
diff --git a/api/src/main/java/org/apache/unomi/api/ConsentStatus.java b/api/src/main/java/org/apache/unomi/api/ConsentStatus.java
index d51fab0112..1f5b20d276 100644
--- a/api/src/main/java/org/apache/unomi/api/ConsentStatus.java
+++ b/api/src/main/java/org/apache/unomi/api/ConsentStatus.java
@@ -21,7 +21,10 @@
* remove a consent for a profile.
*/
public enum ConsentStatus {
+ // Consent has been granted.
GRANTED,
+ // Consent has been denied.
DENIED,
+ // Consent has been revoked.
REVOKED
}
diff --git a/api/src/main/java/org/apache/unomi/api/ContextRequest.java b/api/src/main/java/org/apache/unomi/api/ContextRequest.java
index f050be6724..090fcce181 100644
--- a/api/src/main/java/org/apache/unomi/api/ContextRequest.java
+++ b/api/src/main/java/org/apache/unomi/api/ContextRequest.java
@@ -190,10 +190,20 @@ public void setFilters(List filters)
this.filters = filters;
}
+ /**
+ * Returns the list of personalization requests.
+ *
+ * @return the list of personalization requests
+ */
public List getPersonalizations() {
return personalizations;
}
+ /**
+ * Sets the list of personalization requests.
+ *
+ * @param personalizations the list of personalization requests
+ */
public void setPersonalizations(List personalizations) {
this.personalizations = personalizations;
}
@@ -292,10 +302,20 @@ public void setProfileId(String profileId) {
this.profileId = profileId;
}
+ /**
+ * Returns the client ID for this request.
+ *
+ * @return the client ID
+ */
public String getClientId() {
return clientId;
}
+ /**
+ * Sets the client ID for this request.
+ *
+ * @param clientId the client ID
+ */
public void setClientId(String clientId) {
this.clientId = clientId;
}
diff --git a/api/src/main/java/org/apache/unomi/api/ContextResponse.java b/api/src/main/java/org/apache/unomi/api/ContextResponse.java
index de2335390d..aa6a00c18d 100644
--- a/api/src/main/java/org/apache/unomi/api/ContextResponse.java
+++ b/api/src/main/java/org/apache/unomi/api/ContextResponse.java
@@ -192,16 +192,27 @@ public void setFilteringResults(Map filteringResults) {
}
+ /**
+ * Returns the number of events processed in this request.
+ *
+ * @return the number of events processed in this request
+ */
public int getProcessedEvents() {
return processedEvents;
}
+ /**
+ * Sets the number of processed events.
+ *
+ * @param processedEvents the count
+ */
public void setProcessedEvents(int processedEvents) {
this.processedEvents = processedEvents;
}
/**
* @deprecated personalizations results are more complex since 2.1.0 and they are now available under: getPersonalizationResults()
+ * @return the personalization results map
*/
@Deprecated
public Map> getPersonalizations() {
@@ -210,6 +221,7 @@ public Map> getPersonalizations() {
/**
* @deprecated personalizations results are more complex since 2.1.0 and they are now available under: setPersonalizationResults()
+ * @param personalizations the personalization results
*/
@Deprecated
public void setPersonalizations(Map> personalizations) {
@@ -224,6 +236,11 @@ public Map getPersonalizationResults() {
return personalizationResults;
}
+ /**
+ * Sets the personalization results.
+ *
+ * @param personalizationResults the results map
+ */
public void setPersonalizationResults(Map personalizationResults) {
this.personalizationResults = personalizationResults;
}
@@ -289,10 +306,20 @@ public void setConsents(Map consents) {
this.consents = consents;
}
+ /**
+ * Returns the request tracing data.
+ *
+ * @return the request tracing data
+ */
public TraceNode getRequestTracing() {
return requestTracing;
}
+ /**
+ * Sets the request tracing data.
+ *
+ * @param requestTracing the tracing node
+ */
public void setRequestTracing(TraceNode requestTracing) {
this.requestTracing = requestTracing;
}
diff --git a/api/src/main/java/org/apache/unomi/api/CustomItem.java b/api/src/main/java/org/apache/unomi/api/CustomItem.java
index a5dceab293..fab0917e45 100644
--- a/api/src/main/java/org/apache/unomi/api/CustomItem.java
+++ b/api/src/main/java/org/apache/unomi/api/CustomItem.java
@@ -68,10 +68,20 @@ public void setProperties(Map properties) {
this.properties = properties;
}
+ /**
+ * Returns the custom item type identifier.
+ *
+ * @return the custom item type identifier
+ */
public String getCustomItemType() {
return customItemType;
}
+ /**
+ * Sets the custom item type identifier.
+ *
+ * @param customItemType the custom item type identifier
+ */
public void setCustomItemType(String customItemType) {
this.customItemType = customItemType;
}
diff --git a/api/src/main/java/org/apache/unomi/api/Item.java b/api/src/main/java/org/apache/unomi/api/Item.java
index ada8a9f7b7..ec796de52f 100644
--- a/api/src/main/java/org/apache/unomi/api/Item.java
+++ b/api/src/main/java/org/apache/unomi/api/Item.java
@@ -124,6 +124,11 @@ public String getItemType() {
return itemType;
}
+ /**
+ * Sets the Item's type.
+ *
+ * @param itemType the Item's type
+ */
public void setItemType(String itemType) {
this.itemType = itemType;
}
@@ -137,6 +142,11 @@ public String getScope() {
return scope;
}
+ /**
+ * Sets the Item's scope.
+ *
+ * @param scope the Item's scope
+ */
public void setScope(String scope) {
this.scope = scope;
}
@@ -164,10 +174,22 @@ public void setVersion(Long version) {
this.version = version;
}
+ /**
+ * Returns the system metadata for the given key.
+ *
+ * @param key the key
+ * @return the system metadata for the given key
+ */
public Object getSystemMetadata(String key) {
return systemMetadata.get(key);
}
+ /**
+ * Sets the system metadata for the given key.
+ *
+ * @param key the key
+ * @param value the value
+ */
public void setSystemMetadata(String key, Object value) {
systemMetadata.put(key, value);
}
@@ -176,6 +198,11 @@ public String getTenantId() {
return tenantId;
}
+ /**
+ * Sets the tenant ID.
+ *
+ * @param tenantId the tenant ID
+ */
public void setTenantId(String tenantId) {
this.tenantId = tenantId;
}
@@ -185,6 +212,11 @@ public String getCreatedBy() {
return createdBy;
}
+ /**
+ * Sets the created by.
+ *
+ * @param createdBy the created by
+ */
public void setCreatedBy(String createdBy) {
this.createdBy = createdBy;
}
@@ -193,38 +225,83 @@ public String getLastModifiedBy() {
return lastModifiedBy;
}
+ /**
+ * Sets the last modified by.
+ *
+ * @param lastModifiedBy the last modified by
+ */
public void setLastModifiedBy(String lastModifiedBy) {
this.lastModifiedBy = lastModifiedBy;
}
+ /**
+ * Returns the date when this item was created.
+ *
+ * @return the date when this item was created
+ */
public Date getCreationDate() {
return creationDate;
}
+ /**
+ * Sets the date when this item was created.
+ *
+ * @param creationDate the creation date
+ */
public void setCreationDate(Date creationDate) {
this.creationDate = creationDate;
}
+ /**
+ * Returns the date when this item was last modified.
+ *
+ * @return the date when this item was last modified
+ */
public Date getLastModificationDate() {
return lastModificationDate;
}
+ /**
+ * Sets the date when this item was last modified.
+ *
+ * @param lastModificationDate the last modification date
+ */
public void setLastModificationDate(Date lastModificationDate) {
this.lastModificationDate = lastModificationDate;
}
+ /**
+ * Returns the source instance ID.
+ *
+ * @return the source instance ID
+ */
public String getSourceInstanceId() {
return sourceInstanceId;
}
+ /**
+ * Sets the source instance ID.
+ *
+ * @param sourceInstanceId the source instance ID
+ */
public void setSourceInstanceId(String sourceInstanceId) {
this.sourceInstanceId = sourceInstanceId;
}
+ /**
+ * Returns the last synchronization date.
+ *
+ * @return the last synchronization date
+ */
public Date getLastSyncDate() {
return lastSyncDate;
}
+ /**
+ * Sets the last synchronization date.
+ *
+ * @param lastSyncDate the last synchronization date
+ */
public void setLastSyncDate(Date lastSyncDate) {
this.lastSyncDate = lastSyncDate;
}
diff --git a/api/src/main/java/org/apache/unomi/api/actions/ActionType.java b/api/src/main/java/org/apache/unomi/api/actions/ActionType.java
index d61245cb30..bf6fb477ab 100644
--- a/api/src/main/java/org/apache/unomi/api/actions/ActionType.java
+++ b/api/src/main/java/org/apache/unomi/api/actions/ActionType.java
@@ -34,6 +34,7 @@
* A type definition for {@link Action}s.
*/
public class ActionType extends MetadataItem implements PluginType, YamlConvertible {
+ /** Item type identifier for action types. */
public static final String ITEM_TYPE = "actionType";
private static final long serialVersionUID = -3522958600710010935L;
diff --git a/api/src/main/java/org/apache/unomi/api/conditions/ConditionType.java b/api/src/main/java/org/apache/unomi/api/conditions/ConditionType.java
index d62c0a3441..e1a00bda19 100644
--- a/api/src/main/java/org/apache/unomi/api/conditions/ConditionType.java
+++ b/api/src/main/java/org/apache/unomi/api/conditions/ConditionType.java
@@ -38,6 +38,7 @@
* parameters may test whether a given property has a specific value: “User property x has value y”.
*/
public class ConditionType extends MetadataItem implements PluginType, YamlConvertible {
+ /** Item type identifier for condition types. */
public static final String ITEM_TYPE = "conditionType";
private static final long serialVersionUID = -6965481691241954969L;
diff --git a/api/src/main/java/org/apache/unomi/api/conditions/ConditionValidation.java b/api/src/main/java/org/apache/unomi/api/conditions/ConditionValidation.java
index 37c7acf37a..9dfd5feafa 100644
--- a/api/src/main/java/org/apache/unomi/api/conditions/ConditionValidation.java
+++ b/api/src/main/java/org/apache/unomi/api/conditions/ConditionValidation.java
@@ -31,15 +31,25 @@
public class ConditionValidation implements Serializable, YamlConvertible {
private static final long serialVersionUID = 1L;
+ /** Defines the expected value types for condition parameters. */
public enum Type {
+ /** String value type. */
STRING,
+ /** Integer value type. */
INTEGER,
+ /** Long value type. */
LONG,
+ /** Float value type. */
FLOAT,
+ /** Double value type. */
DOUBLE,
+ /** Boolean value type. */
BOOLEAN,
+ /** Date value type. */
DATE,
+ /** Condition value type. */
CONDITION,
+ /** Object value type. */
OBJECT
}
@@ -52,69 +62,152 @@ public enum Type {
private boolean recommended; // Parameter is recommended but not required
private transient Class> customType;
+ /**
+ * Instantiates a new ConditionValidation.
+ */
public ConditionValidation() {
}
+ /**
+ * Returns whether this parameter is required.
+ *
+ * @return true if the parameter is required
+ */
public boolean isRequired() {
return required;
}
+ /**
+ * Sets whether this parameter is required.
+ *
+ * @param required true if required
+ */
public void setRequired(boolean required) {
this.required = required;
}
+ /**
+ * Returns the set of allowed string values.
+ *
+ * @return the set of allowed string values
+ */
public Set getAllowedValues() {
return allowedValues;
}
+ /**
+ * Sets the allowed string values.
+ *
+ * @param allowedValues the allowed values
+ */
public void setAllowedValues(Set allowedValues) {
this.allowedValues = allowedValues;
}
+ /**
+ * Returns the allowed condition tags.
+ *
+ * @return the allowed condition tags
+ */
public Set getAllowedConditionTags() {
return allowedConditionTags;
}
+ /**
+ * Sets the allowed condition tags.
+ *
+ * @param allowedConditionTags the allowed tags
+ */
public void setAllowedConditionTags(Set allowedConditionTags) {
this.allowedConditionTags = allowedConditionTags;
}
+ /**
+ * Returns the disallowed condition type identifiers.
+ *
+ * @return the disallowed condition type identifiers
+ */
public Set getDisallowedConditionTypes() {
return disallowedConditionTypes;
}
+ /**
+ * Sets the disallowed condition type identifiers.
+ *
+ * @param disallowedConditionTypes the disallowed types
+ */
public void setDisallowedConditionTypes(Set disallowedConditionTypes) {
this.disallowedConditionTypes = disallowedConditionTypes;
}
+ /**
+ * Returns whether this parameter is mutually exclusive with others in the same group.
+ *
+ * @return true if the parameter is mutually exclusive with others in the same group
+ */
public boolean isExclusive() {
return exclusive;
}
+ /**
+ * Sets whether this parameter is mutually exclusive.
+ *
+ * @param exclusive true if exclusive
+ */
public void setExclusive(boolean exclusive) {
this.exclusive = exclusive;
}
+ /**
+ * Returns the exclusivity group name.
+ *
+ * @return the exclusivity group name
+ */
public String getExclusiveGroup() {
return exclusiveGroup;
}
+ /**
+ * Sets the exclusivity group name.
+ *
+ * @param exclusiveGroup the group name
+ */
public void setExclusiveGroup(String exclusiveGroup) {
this.exclusiveGroup = exclusiveGroup;
}
+ /**
+ * Returns whether this parameter is recommended.
+ *
+ * @return true if the parameter is recommended
+ */
public boolean isRecommended() {
return recommended;
}
+ /**
+ * Sets whether this parameter is recommended.
+ *
+ * @param recommended true if recommended
+ */
public void setRecommended(boolean recommended) {
this.recommended = recommended;
}
+ /**
+ * Returns the custom Java type for this parameter.
+ *
+ * @return the custom Java type for this parameter
+ */
public Class> getCustomType() {
return customType;
}
+ /**
+ * Sets the custom Java type for this parameter.
+ *
+ * @param customType the custom type class
+ */
public void setCustomType(Class> customType) {
this.customType = customType;
}
diff --git a/api/src/main/java/org/apache/unomi/api/exceptions/BadSegmentConditionException.java b/api/src/main/java/org/apache/unomi/api/exceptions/BadSegmentConditionException.java
index 854d9a7a73..8bd908a2c3 100644
--- a/api/src/main/java/org/apache/unomi/api/exceptions/BadSegmentConditionException.java
+++ b/api/src/main/java/org/apache/unomi/api/exceptions/BadSegmentConditionException.java
@@ -22,14 +22,28 @@
*/
public class BadSegmentConditionException extends RuntimeException {
+ /**
+ * Instantiates a new BadSegmentConditionException.
+ */
public BadSegmentConditionException() {
super();
}
+ /**
+ * Constructs with a message.
+ *
+ * @param message the error message
+ */
public BadSegmentConditionException(String message) {
super(message);
}
+ /**
+ * Constructs with a message and cause.
+ *
+ * @param message the error message
+ * @param cause the root cause
+ */
public BadSegmentConditionException(String message, Throwable cause) {
super(message, cause);
}
diff --git a/api/src/main/java/org/apache/unomi/api/query/Aggregate.java b/api/src/main/java/org/apache/unomi/api/query/Aggregate.java
index d68f43390d..9c2f3ffca1 100644
--- a/api/src/main/java/org/apache/unomi/api/query/Aggregate.java
+++ b/api/src/main/java/org/apache/unomi/api/query/Aggregate.java
@@ -24,7 +24,13 @@
import java.util.Map;
/**
- * A specification for an aggregate as part of {@link AggregateQuery}s
+ * Specification for an aggregation used by {@link AggregateQuery}.
+ *
+ * The {@link #type} identifies the aggregation strategy: {@code date}, {@code dateRange},
+ * {@code numericRange}, {@code ipRange}, or {@code null} (terms aggregation on distinct values).
+ * Type-specific configuration is supplied through {@link #parameters} and the corresponding range lists.
+ *
+ * @see AggregateQuery
*/
public class Aggregate implements Serializable {
private String type;
@@ -35,49 +41,119 @@ public class Aggregate implements Serializable {
private List ipRanges = new ArrayList<>();
+ /**
+ * Instantiates a new Aggregate.
+ */
public Aggregate() {
}
+ /**
+ * Retrieves the aggregation type.
+ *
+ * Supported values are {@code date}, {@code dateRange}, {@code numericRange}, and {@code ipRange}.
+ * When {@code null}, a terms aggregation is used on distinct property values.
+ *
+ * @return the aggregation type, or {@code null} for a terms aggregation
+ */
public String getType() {
return type;
}
+ /**
+ * Sets the aggregation type.
+ *
+ * Supported values are {@code date}, {@code dateRange}, {@code numericRange}, and {@code ipRange}.
+ * When {@code null}, a terms aggregation is used on distinct property values.
+ *
+ * @param type the aggregation type
+ */
public void setType(String type) {
this.type = type;
}
+ /**
+ * Retrieves the aggregation parameters.
+ *
+ * For {@code date} aggregations, expected keys are {@code interval} and {@code format}.
+ * For {@code dateRange} aggregations, the {@code format} key defines the date format.
+ *
+ * @return the aggregation parameters
+ */
public Map getParameters() {
return parameters;
}
+ /**
+ * Sets the aggregation parameters.
+ *
+ * For {@code date} aggregations, expected keys are {@code interval} and {@code format}.
+ * For {@code dateRange} aggregations, the {@code format} key defines the date format.
+ *
+ * @param parameters the aggregation parameters
+ */
public void setParameters(Map parameters) {
this.parameters = parameters;
}
+ /**
+ * Retrieves the property to aggregate on.
+ *
+ * @return the property to aggregate on
+ */
public String getProperty() {
return property;
}
+ /**
+ * Sets the property to aggregate on.
+ *
+ * @param property the property name
+ */
public void setProperty(String property) {
this.property = property;
}
+ /**
+ * Retrieves the numeric ranges used by {@code numericRange} aggregations.
+ *
+ * @return the numeric ranges
+ */
public List getNumericRanges() {
return numericRanges;
}
+ /**
+ * Retrieves the date ranges used by {@code dateRange} aggregations.
+ *
+ * @return the date ranges
+ */
public List getDateRanges() {
return dateRanges;
}
+ /**
+ * Sets the date ranges used by {@code dateRange} aggregations.
+ *
+ * @param dateRanges the date ranges
+ */
public void setDateRanges(List dateRanges) {
this.dateRanges = dateRanges;
}
+ /**
+ * Retrieves the IP ranges used by {@code ipRange} aggregations.
+ *
+ * @return the IP ranges
+ */
public List ipRanges() {
return ipRanges;
}
+ /**
+ * Sets the IP ranges used by {@code ipRange} aggregations.
+ *
+ * @param ipRanges the IP ranges
+ */
public void setIpRanges(List ipRanges) {
this.ipRanges = ipRanges;
}
diff --git a/api/src/main/java/org/apache/unomi/api/services/ConditionValidationService.java b/api/src/main/java/org/apache/unomi/api/services/ConditionValidationService.java
index b9d73ea925..2f77c95a8c 100644
--- a/api/src/main/java/org/apache/unomi/api/services/ConditionValidationService.java
+++ b/api/src/main/java/org/apache/unomi/api/services/ConditionValidationService.java
@@ -23,21 +23,29 @@
import java.util.Map;
/**
- * A service to validate conditions against their type definitions
+ * Service that validates {@link Condition} instances against their {@link org.apache.unomi.api.conditions.ConditionType} definitions.
+ *
+ * Used during save operations to ensure condition parameters satisfy type constraints. Parameters containing
+ * references ({@code parameter::}) or script expressions ({@code script::}) are skipped because their values
+ * are resolved at evaluation time.
+ *
+ * @see DefinitionsService#getConditionValidationService()
*/
public interface ConditionValidationService {
/**
* Validates a condition against its type definition.
- * Skips validation for parameters that contain references (`parameter::`) or script expressions (`script::`).
- * Only validates parameters that are NOT parameter references or script expressions.
+ *
+ * Skips validation for parameters that contain references ({@code parameter::}) or script expressions
+ * ({@code script::}). Only literal parameter values are validated.
+ *
* @param condition the condition to validate
- * @return a list of validation errors, empty if the condition is valid (for non-reference/script values)
+ * @return a list of validation errors, empty when the condition is valid
*/
List validate(Condition condition);
/**
- * Represents a validation error with detailed context
+ * A validation error with detailed context about the failing condition, parameter, and cause.
*/
class ValidationError {
private final String parameterName;
@@ -48,10 +56,28 @@ class ValidationError {
private final Map context;
private final ValidationError parentError;
+ /**
+ * Instantiates a validation error for a single parameter.
+ *
+ * @param parameterName the parameter that caused the error
+ * @param message the error description
+ * @param type the error type
+ */
public ValidationError(String parameterName, String message, ValidationErrorType type) {
this(parameterName, message, type, null, null, null, null);
}
+ /**
+ * Instantiates a validation error with full condition context and optional parent error.
+ *
+ * @param parameterName the parameter that caused the error
+ * @param message the error description
+ * @param type the error type
+ * @param conditionId the identifier of the condition being validated
+ * @param conditionTypeId the identifier of the condition type
+ * @param context additional context key-value pairs
+ * @param parentError the parent error that caused this error, or {@code null}
+ */
public ValidationError(String parameterName, String message, ValidationErrorType type,
String conditionId, String conditionTypeId, Map context,
ValidationError parentError) {
@@ -64,43 +90,81 @@ public ValidationError(String parameterName, String message, ValidationErrorType
this.parentError = parentError;
}
+ /**
+ * Retrieves the name of the parameter that caused the error.
+ *
+ * @return the parameter name, or {@code null} if not applicable
+ */
public String getParameterName() {
return parameterName;
}
+ /**
+ * Retrieves the error description message.
+ *
+ * @return the error message
+ */
public String getMessage() {
return message;
}
+ /**
+ * Retrieves the type of validation error.
+ *
+ * @return the error type
+ */
public ValidationErrorType getType() {
return type;
}
+ /**
+ * Retrieves the identifier of the condition associated with this error.
+ *
+ * @return the condition identifier, or {@code null} if not applicable
+ */
public String getConditionId() {
return conditionId;
}
+ /**
+ * Retrieves the identifier of the condition type associated with this error.
+ *
+ * @return the condition type identifier, or {@code null} if not applicable
+ */
public String getConditionTypeId() {
return conditionTypeId;
}
- /** @deprecated Use {@link #getConditionTypeId()} instead. */
+ /**
+ * @deprecated Use {@link #getConditionTypeId()} instead.
+ */
@Deprecated
public String getConditionTypeName() {
return conditionTypeId;
}
+ /**
+ * Retrieves a copy of the additional context map for this error.
+ *
+ * @return the context map
+ */
public Map getContext() {
return new HashMap<>(context);
}
+ /**
+ * Retrieves the parent error that caused this error, if any.
+ *
+ * @return the parent error, or {@code null} if none
+ */
public ValidationError getParentError() {
return parentError;
}
/**
- * Returns a detailed error message including all context information
- * @return A detailed error message
+ * Retrieves a detailed error message including condition, parameter, context, and parent error information.
+ *
+ * @return the detailed error message
*/
public String getDetailedMessage() {
StringBuilder sb = new StringBuilder();
@@ -151,13 +215,18 @@ public String toString() {
}
/**
- * Types of validation errors
+ * Categories of validation errors returned by {@link #validate(Condition)}.
*/
enum ValidationErrorType {
+ /** A required parameter is absent. */
MISSING_REQUIRED_PARAMETER("Required parameter is missing"),
+ /** The value provided for a parameter is not valid. */
INVALID_VALUE("Invalid value provided"),
+ /** The condition type is invalid or not supported. */
INVALID_CONDITION_TYPE("Invalid or unsupported condition type"),
+ /** Mutually exclusive parameters are both present. */
EXCLUSIVE_PARAMETER_VIOLATION("Mutually exclusive parameters conflict"),
+ /** A recommended parameter is absent. */
MISSING_RECOMMENDED_PARAMETER("Recommended parameter is missing");
private final String description;
@@ -166,6 +235,11 @@ enum ValidationErrorType {
this.description = description;
}
+ /**
+ * Retrieves the human-readable description of this error type.
+ *
+ * @return the error type description
+ */
public String getDescription() {
return description;
}
diff --git a/api/src/main/java/org/apache/unomi/api/services/ConfigSharingService.java b/api/src/main/java/org/apache/unomi/api/services/ConfigSharingService.java
index c1d91af287..d847905e5b 100644
--- a/api/src/main/java/org/apache/unomi/api/services/ConfigSharingService.java
+++ b/api/src/main/java/org/apache/unomi/api/services/ConfigSharingService.java
@@ -19,25 +19,88 @@
import java.util.Set;
/**
- * A service to share configuration properties with other bundles. It also support listeners that will be called whenever
- * a property is added/updated/removed. Simply register a service with the @link ConfigSharingServiceConfigChangeListener interface and it will
- * be automatically picked up.
+ * OSGi service for sharing runtime configuration properties across Unomi bundles.
+ *
+ * Bundles that own configuration (for example {@code web-servlets} for cookie settings or
+ * {@code router-core} for import paths) publish values with {@link #setProperty(String, Object)}.
+ * Other bundles read them with {@link #getProperty(String)} without taking a direct dependency on
+ * the publishing module. Initial properties may also be seeded at startup (for example
+ * {@code internalServerAddress} from cluster configuration).
+ *
+ * Property changes notify registered {@link ConfigChangeListener} OSGi services. Register a
+ * component implementing {@link ConfigChangeListener} to react to {@link ConfigChangeEvent}s
+ * when properties are added, updated, or removed.
*/
public interface ConfigSharingService {
+ /**
+ * Retrieves the value of the named property.
+ *
+ * @param name the property name
+ * @return the property value, or {@code null} if not set
+ */
Object getProperty(String name);
+
+ /**
+ * Sets the value of the named property and notifies registered listeners.
+ *
+ * @param name the property name
+ * @param value the new value
+ * @return the previous value, or {@code null} if the property was not previously set
+ */
Object setProperty(String name, Object value);
+
+ /**
+ * Determines whether the named property is currently set.
+ *
+ * @param name the property name
+ * @return {@code true} if the property exists, {@code false} otherwise
+ */
boolean hasProperty(String name);
+
+ /**
+ * Removes the named property and notifies registered listeners.
+ *
+ * @param name the property name
+ * @return the previous value, or {@code null} if the property was not set
+ */
Object removeProperty(String name);
+
+ /**
+ * Retrieves the names of all currently known properties.
+ *
+ * @return the property names
+ */
Set getPropertyNames();
+ /**
+ * Event describing a configuration property change.
+ */
class ConfigChangeEvent {
- public enum ConfigChangeEventType { ADDED, UPDATED, REMOVED };
+ /**
+ * Types of configuration change events.
+ */
+ public enum ConfigChangeEventType {
+ /** Property was added. */
+ ADDED,
+ /** Property was updated. */
+ UPDATED,
+ /** Property was removed. */
+ REMOVED
+ };
private ConfigChangeEventType eventType;
private String name;
private Object oldValue;
private Object newValue;
+ /**
+ * Instantiates a configuration change event.
+ *
+ * @param eventType the type of change
+ * @param name the property name
+ * @param oldValue the previous value, or {@code null} for {@link ConfigChangeEventType#ADDED} events
+ * @param newValue the new value, or {@code null} for {@link ConfigChangeEventType#REMOVED} events
+ */
public ConfigChangeEvent(ConfigChangeEventType eventType, String name, Object oldValue, Object newValue) {
this.eventType = eventType;
this.name = name;
@@ -45,24 +108,55 @@ public ConfigChangeEvent(ConfigChangeEventType eventType, String name, Object ol
this.newValue = newValue;
}
+ /**
+ * Retrieves the type of configuration change.
+ *
+ * @return the event type
+ */
public ConfigChangeEventType getEventType() {
return eventType;
}
+ /**
+ * Retrieves the name of the changed property.
+ *
+ * @return the property name
+ */
public String getName() {
return name;
}
+ /**
+ * Retrieves the previous value of the property.
+ *
+ * @return the old value, or {@code null} for {@link ConfigChangeEventType#ADDED} events
+ */
public Object getOldValue() {
return oldValue;
}
+ /**
+ * Retrieves the new value of the property.
+ *
+ * @return the new value, or {@code null} for {@link ConfigChangeEventType#REMOVED} events
+ */
public Object getNewValue() {
return newValue;
}
}
+ /**
+ * Listener for configuration property changes.
+ *
+ * Implementations are registered as OSGi services and invoked when properties are added,
+ * updated, or removed through {@link ConfigSharingService}.
+ */
interface ConfigChangeListener {
+ /**
+ * Called when a configuration property changes.
+ *
+ * @param configChangeEvent the change event
+ */
void configChanged(ConfigChangeEvent configChangeEvent);
}
diff --git a/api/src/main/java/org/apache/unomi/api/services/ProfileService.java b/api/src/main/java/org/apache/unomi/api/services/ProfileService.java
index b99985d764..78ecb1ffb8 100644
--- a/api/src/main/java/org/apache/unomi/api/services/ProfileService.java
+++ b/api/src/main/java/org/apache/unomi/api/services/ProfileService.java
@@ -123,6 +123,7 @@ public interface ProfileService {
* @param profileID the identifier of the profile
* @param alias the alias which should be unlinked from the profile
* @param clientID the identifier of the client
+ * @return the removed ProfileAlias, or null if not found
*/
ProfileAlias removeAliasFromProfile(String profileID, String alias, String clientID);
diff --git a/api/src/main/java/org/apache/unomi/api/services/SchedulerService.java b/api/src/main/java/org/apache/unomi/api/services/SchedulerService.java
index aff9a843a3..6868ebc65e 100644
--- a/api/src/main/java/org/apache/unomi/api/services/SchedulerService.java
+++ b/api/src/main/java/org/apache/unomi/api/services/SchedulerService.java
@@ -241,7 +241,7 @@ ScheduledTask createTask(String taskType,
* @param runnable the code to execute
* @param persistent whether to store in persistence service (true) or only in memory (false)
* @return the created and scheduled task
- * @throws IllegalArgumentException if period <= 0 or timeUnit is null
+ * @throws IllegalArgumentException if period <= 0 or timeUnit is null
*/
ScheduledTask createRecurringTask(String taskType, long period, TimeUnit timeUnit, Runnable runnable, boolean persistent);
diff --git a/api/src/main/java/org/apache/unomi/api/services/cache/CacheableTypeConfig.java b/api/src/main/java/org/apache/unomi/api/services/cache/CacheableTypeConfig.java
index 3194305f7b..be2632c42c 100644
--- a/api/src/main/java/org/apache/unomi/api/services/cache/CacheableTypeConfig.java
+++ b/api/src/main/java/org/apache/unomi/api/services/cache/CacheableTypeConfig.java
@@ -615,6 +615,13 @@ public CacheableTypeConfig build() {
*/
@FunctionalInterface
public interface TriConsumer {
+ /**
+ * Performs this operation on the given arguments.
+ *
+ * @param t the first input argument
+ * @param u the second input argument
+ * @param v the third input argument
+ */
void accept(T t, U u, V v);
}
}
\ No newline at end of file
diff --git a/api/src/main/java/org/apache/unomi/api/tasks/ScheduledTask.java b/api/src/main/java/org/apache/unomi/api/tasks/ScheduledTask.java
index b971e81f2c..e8afce6788 100644
--- a/api/src/main/java/org/apache/unomi/api/tasks/ScheduledTask.java
+++ b/api/src/main/java/org/apache/unomi/api/tasks/ScheduledTask.java
@@ -26,21 +26,30 @@
import java.util.HashSet;
/**
- * Represents a persistent scheduled task that can be executed across a cluster.
- * This class provides a comprehensive model for task scheduling and execution with features including:
+ * Persistent {@link Item} representing a cluster-aware scheduled task managed by {@link org.apache.unomi.api.services.SchedulerService}.
+ *
+ * Tasks are identified by {@link #taskType} and executed by registered {@link TaskExecutor} implementations. The model supports:
*
- * - Task lifecycle management through states (SCHEDULED, WAITING, RUNNING, etc.)
- * - Lock management for cluster coordination
- * - Execution history and checkpoint data for recovery
- * - Support for one-shot and periodic execution
- * - Task dependencies and parallel execution control
- * - Cluster-wide task distribution
+ * - Lifecycle states via {@link TaskStatus} (scheduled, waiting, running, completed, failed, cancelled, crashed)
+ * - Cluster coordination through lock ownership and executing node tracking
+ * - One-shot and periodic scheduling with fixed-rate or fixed-delay semantics
+ * - Retry, checkpoint, and dependency management for long-running or multi-step work
+ * - Persistent storage (cluster-visible) or in-memory execution (single node)
*
+ *
+ * @see org.apache.unomi.api.services.SchedulerService
+ * @see TaskExecutor
*/
public class ScheduledTask extends Item implements Serializable {
+ /**
+ * Java serialization version; Unomi does not rely on Java serialization of this type as a cross-version persistence contract.
+ */
private static final long serialVersionUID = 1L;
+ /**
+ * Item type identifier for scheduled tasks, used by {@link org.apache.unomi.api.Item#getItemType()}.
+ */
public static final String ITEM_TYPE = "scheduledTask";
/**
@@ -66,129 +75,39 @@ public enum TaskStatus {
private String taskType;
private Map parameters;
- private String executingNodeId; // The ID of the node currently executing this task
- /**
- * The initial delay before first execution, in the specified time unit.
- */
+ private String executingNodeId;
private long initialDelay;
private long period;
private TimeUnit timeUnit;
private boolean fixedRate;
- /**
- * Gets the date of the last execution attempt.
- *
- * @return the last execution date or null if never executed
- */
private Date lastExecutionDate;
- /**
- * Gets the node ID that last executed this task.
- *
- * @return the ID of the last executing node
- */
private String lastExecutedBy;
- /**
- * Gets the error message from the last failed execution.
- *
- * @return the last error message or null if no error
- */
private String lastError;
private boolean enabled;
private String lockOwner;
- /**
- * Gets the date when the current lock was acquired.
- *
- * @return the lock acquisition date or null if unlocked
- */
private Date lockDate;
private boolean oneShot;
private boolean allowParallelExecution;
- /**
- * Gets the current task status.
- *
- * @return the current status
- */
private TaskStatus status;
private Map statusDetails;
- /**
- * Gets the next scheduled execution date for periodic tasks.
- *
- * @return the next scheduled execution date or null if not scheduled
- */
private Date nextScheduledExecution;
- /**
- * Gets the number of consecutive execution failures.
- *
- * @return the failure count
- */
private int failureCount;
- /**
- * Gets the number of successful executions.
- *
- * @return the success count
- */
private int successCount;
- /**
- * Gets the maximum number of retry attempts after failures.
- * For one-shot tasks:
- * - When a task fails, it will be automatically retried up to this many times
- * - Each retry attempt occurs after waiting for retryDelay
- * - After reaching this limit, the task remains in FAILED state until manually retried
- *
- * For periodic tasks:
- * - Retries only apply within a single scheduled execution
- * - If retries are exhausted, the task will still attempt its next scheduled execution
- * - The next scheduled execution resets the failure count
- *
- * A value of 0 means no automatic retries in either case.
- *
- * @return the maximum retry count
- */
private int maxRetries;
- /**
- * Gets the delay between retry attempts.
- * For one-shot tasks:
- * - This delay is applied between each retry attempt after a failure
- * - Helps prevent rapid-fire retries that could overload the system
- *
- * For periodic tasks:
- * - This delay is used between retry attempts within a single scheduled execution
- * - Does not affect the task's configured period/scheduling
- *
- * @return the retry delay in milliseconds
- */
private long retryDelay;
- /**
- * Gets the name of the current execution step.
- * This is used to track progress through multi-step tasks.
- *
- * @return the current step name or null if not set
- */
private String currentStep;
- /**
- * Gets the checkpoint data for task resumption.
- * This data allows a task to resume from where it left off after a crash.
- *
- * @return map of checkpoint data or null if no checkpoint
- */
private Map checkpointData;
- private boolean persistent = true; // By default tasks are persistent
- private boolean runOnAllNodes = false; // By default tasks run on a single node
- /**
- * Indicates if this is a system task that should not be recreated on startup.
- * System tasks are created by the system during initialization and should be
- * preserved across restarts.
- */
- private boolean systemTask = false; // By default tasks are not system tasks
- /**
- * Gets the task type that this task is waiting for a lock on.
- * This is used when tasks of the same type cannot run in parallel.
- *
- * @return the task type being waited on or null if not waiting
- */
+ private boolean persistent = true;
+ private boolean runOnAllNodes = false;
+ private boolean systemTask = false;
private String waitingForTaskType;
- private Set dependsOn = new HashSet<>(); // Set of task IDs this task depends on
- private Set waitingOnTasks = new HashSet<>(); // Set of task IDs this task is currently waiting on
+ private Set dependsOn = new HashSet<>();
+ private Set waitingOnTasks = new HashSet<>();
+ /**
+ * Instantiates a new scheduled task with default status {@link TaskStatus#SCHEDULED},
+ * {@code maxRetries} of 3, and a default {@code retryDelay} of 60 seconds.
+ */
public ScheduledTask() {
super();
this.status = TaskStatus.SCHEDULED;
@@ -198,7 +117,7 @@ public ScheduledTask() {
}
/**
- * Gets the task type identifier.
+ * Retrieves the task type identifier.
* The task type determines which executor will handle this task.
*
* @return the task type identifier
@@ -217,7 +136,7 @@ public void setTaskType(String taskType) {
}
/**
- * Gets the task parameters.
+ * Retrieves the task parameters.
* These parameters are passed to the task executor during execution.
*
* @return map of task parameters
@@ -236,25 +155,25 @@ public void setParameters(Map parameters) {
}
/**
- * Gets the initial delay before first execution.
- *
- * @return the initial delay in the specified time unit
+ * Retrieves the initial delay before the first execution, expressed in {@link #getTimeUnit()}.
+ *
+ * @return the initial delay in the configured time unit
*/
public long getInitialDelay() {
return initialDelay;
}
/**
- * Sets the initial delay before first execution.
- *
- * @param initialDelay the initial delay in the specified time unit
+ * Sets the initial delay before the first execution, expressed in {@link #getTimeUnit()}.
+ *
+ * @param initialDelay the initial delay in the configured time unit
*/
public void setInitialDelay(long initialDelay) {
this.initialDelay = initialDelay;
}
/**
- * Gets the period between successive task executions.
+ * Retrieves the period between successive task executions.
* A period of 0 indicates a one-time task and will automatically set oneShot=true.
*
* @return the period between executions in the specified time unit
@@ -285,7 +204,7 @@ public void setPeriod(long period) {
}
/**
- * Gets the time unit for delay and period values.
+ * Retrieves the time unit for delay and period values.
*
* @return the time unit used for scheduling
*/
@@ -303,11 +222,11 @@ public void setTimeUnit(TimeUnit timeUnit) {
}
/**
- * Gets whether this task uses fixed-rate scheduling.
+ * Determines whether this task uses fixed-rate scheduling.
* If true, executions are scheduled at fixed intervals from the start time.
* If false, executions are scheduled at fixed delays from completion.
*
- * @return true if using fixed-rate scheduling
+ * @return {@code true} if using fixed-rate scheduling
*/
public boolean isFixedRate() {
return fixedRate;
@@ -323,9 +242,9 @@ public void setFixedRate(boolean fixedRate) {
}
/**
- * Gets the date of the last execution attempt.
+ * Retrieves the date of the last execution attempt.
*
- * @return the last execution date or null if never executed
+ * @return the last execution date or {@code null} if never executed
*/
public Date getLastExecutionDate() {
return lastExecutionDate;
@@ -341,7 +260,7 @@ public void setLastExecutionDate(Date lastExecutionDate) {
}
/**
- * Gets the node ID that last executed this task.
+ * Retrieves the node ID that last executed this task.
*
* @return the ID of the last executing node
*/
@@ -359,9 +278,9 @@ public void setLastExecutedBy(String lastExecutedBy) {
}
/**
- * Gets the error message from the last failed execution.
+ * Retrieves the error message from the last failed execution.
*
- * @return the last error message or null if no error
+ * @return the last error message or {@code null} if no error
*/
public String getLastError() {
return lastError;
@@ -377,10 +296,10 @@ public void setLastError(String lastError) {
}
/**
- * Gets whether this task is enabled.
+ * Determines whether this task is enabled.
* Disabled tasks will not be executed.
*
- * @return true if the task is enabled
+ * @return {@code true} if the task is enabled
*/
public boolean isEnabled() {
return enabled;
@@ -396,9 +315,9 @@ public void setEnabled(boolean enabled) {
}
/**
- * Gets the ID of the node that currently holds the execution lock.
+ * Retrieves the ID of the node that currently holds the execution lock.
*
- * @return the current lock owner's node ID or null if unlocked
+ * @return the current lock owner's node ID or {@code null} if unlocked
*/
public String getLockOwner() {
return lockOwner;
@@ -414,9 +333,9 @@ public void setLockOwner(String lockOwner) {
}
/**
- * Gets the date when the current lock was acquired.
+ * Retrieves the date when the current lock was acquired.
*
- * @return the lock acquisition date or null if unlocked
+ * @return the lock acquisition date or {@code null} if unlocked
*/
public Date getLockDate() {
return lockDate;
@@ -432,10 +351,10 @@ public void setLockDate(Date lockDate) {
}
/**
- * Returns whether this task should execute only once.
+ * Determines whether this task should execute only once.
* Tasks with period=0 are automatically marked as one-shot tasks.
*
- * @return true if the task should execute only once
+ * @return {@code true} if the task should execute only once
*/
public boolean isOneShot() {
return oneShot;
@@ -456,11 +375,11 @@ public void setOneShot(boolean oneShot) {
}
/**
- * Gets whether parallel execution is allowed for this task.
+ * Determines whether parallel execution is allowed for this task.
* If true, multiple instances of this task can run simultaneously.
* If false, the task uses locking to ensure only one instance runs at a time.
*
- * @return true if parallel execution is allowed
+ * @return {@code true} if parallel execution is allowed
*/
public boolean isAllowParallelExecution() {
return allowParallelExecution;
@@ -476,7 +395,7 @@ public void setAllowParallelExecution(boolean allowParallelExecution) {
}
/**
- * Gets the current task status.
+ * Retrieves the current task status.
*
* @return the current status
*/
@@ -495,7 +414,7 @@ public void setStatus(TaskStatus status) {
}
/**
- * Gets additional details about the task's current status.
+ * Retrieves additional details about the task's current status.
* This may include execution progress, history, or other metadata.
*
* @return map of status details
@@ -514,9 +433,9 @@ public void setStatusDetails(Map statusDetails) {
}
/**
- * Gets the next scheduled execution date for periodic tasks.
+ * Retrieves the next scheduled execution date for periodic tasks.
*
- * @return the next scheduled execution date or null if not scheduled
+ * @return the next scheduled execution date or {@code null} if not scheduled
*/
public Date getNextScheduledExecution() {
return nextScheduledExecution;
@@ -532,7 +451,7 @@ public void setNextScheduledExecution(Date nextScheduledExecution) {
}
/**
- * Gets the number of consecutive execution failures.
+ * Retrieves the number of consecutive execution failures.
*
* @return the failure count
*/
@@ -550,7 +469,7 @@ public void setFailureCount(int failureCount) {
}
/**
- * Gets the number of successful executions.
+ * Retrieves the number of successful executions.
*
* @return the success count
*/
@@ -568,7 +487,7 @@ public void setSuccessCount(int successCount) {
}
/**
- * Gets the maximum number of retry attempts after failures.
+ * Retrieves the maximum number of retry attempts after failures.
* For one-shot tasks:
* - When a task fails, it will be automatically retried up to this many times
* - Each retry attempt occurs after waiting for retryDelay
@@ -597,7 +516,7 @@ public void setMaxRetries(int maxRetries) {
}
/**
- * Gets the delay between retry attempts.
+ * Retrieves the delay between retry attempts.
* For one-shot tasks:
* - This delay is applied between each retry attempt after a failure
* - Helps prevent rapid-fire retries that could overload the system
@@ -622,10 +541,10 @@ public void setRetryDelay(long retryDelay) {
}
/**
- * Gets the name of the current execution step.
+ * Retrieves the name of the current execution step.
* This is used to track progress through multi-step tasks.
*
- * @return the current step name or null if not set
+ * @return the current step name or {@code null} if not set
*/
public String getCurrentStep() {
return currentStep;
@@ -641,10 +560,10 @@ public void setCurrentStep(String currentStep) {
}
/**
- * Gets the checkpoint data for task resumption.
+ * Retrieves the checkpoint data for task resumption.
* This data allows a task to resume from where it left off after a crash.
*
- * @return map of checkpoint data or null if no checkpoint
+ * @return map of checkpoint data or {@code null} if no checkpoint
*/
public Map getCheckpointData() {
return checkpointData;
@@ -660,25 +579,32 @@ public void setCheckpointData(Map checkpointData) {
}
/**
- * Gets whether this task is stored persistently.
+ * Determines whether this task is stored persistently.
* Persistent tasks survive system restarts and are visible across the cluster.
* Non-persistent tasks exist only in memory on a single node.
*
- * @return true if the task is persistent
+ * @return {@code true} if the task is persistent
*/
public boolean isPersistent() {
return persistent;
}
+ /**
+ * Sets whether this task is stored persistently.
+ * Persistent tasks survive system restarts and are visible across the cluster.
+ * Non-persistent tasks exist only in memory on a single node.
+ *
+ * @param persistent {@code true} to persist the task, {@code false} for in-memory execution
+ */
public void setPersistent(boolean persistent) {
this.persistent = persistent;
}
/**
- * Gets whether this task should run on all cluster nodes.
+ * Determines whether this task should run on all cluster nodes.
* If false, the task runs only on executor nodes.
*
- * @return true if the task should run on all nodes
+ * @return {@code true} if the task should run on all nodes
*/
public boolean isRunOnAllNodes() {
return runOnAllNodes;
@@ -694,11 +620,11 @@ public void setRunOnAllNodes(boolean runOnAllNodes) {
}
/**
- * Gets whether this task is a system task.
+ * Determines whether this task is a system task.
* System tasks are created by the system during initialization and should be
* preserved across restarts rather than being recreated.
*
- * @return true if the task is a system task
+ * @return {@code true} if the task is a system task
*/
public boolean isSystemTask() {
return systemTask;
@@ -706,18 +632,20 @@ public boolean isSystemTask() {
/**
* Sets whether this task is a system task.
- *
- * @param systemTask true to mark the task as a system task
+ * System tasks are created during initialization and should be preserved across
+ * restarts rather than being recreated.
+ *
+ * @param systemTask {@code true} to mark the task as a system task
*/
public void setSystemTask(boolean systemTask) {
this.systemTask = systemTask;
}
/**
- * Gets the task type that this task is waiting for a lock on.
+ * Retrieves the task type that this task is waiting for a lock on.
* This is used when tasks of the same type cannot run in parallel.
*
- * @return the task type being waited on or null if not waiting
+ * @return the task type being waited on or {@code null} if not waiting
*/
public String getWaitingForTaskType() {
return waitingForTaskType;
@@ -733,7 +661,7 @@ public void setWaitingForTaskType(String waitingForTaskType) {
}
/**
- * Gets the set of task IDs that this task depends on.
+ * Retrieves the set of task IDs that this task depends on.
* The task will not execute until all dependencies have completed.
*
* @return set of dependency task IDs
@@ -752,7 +680,7 @@ public void setDependsOn(Set dependsOn) {
}
/**
- * Gets the set of task IDs that this task is currently waiting on.
+ * Retrieves the set of task IDs that this task is currently waiting on.
* This represents the subset of dependencies that have not yet completed.
*
* @return set of task IDs being waited on
@@ -818,11 +746,11 @@ public void removeWaitingOnTask(String taskId) {
}
/**
- * Gets the ID of the node currently executing this task.
+ * Retrieves the ID of the node currently executing this task.
* This is different from lockOwner as it specifically indicates which node
* is actively executing the task, not just holding the lock.
*
- * @return the ID of the executing node or null if not being executed
+ * @return the ID of the executing node or {@code null} if not being executed
*/
public String getExecutingNodeId() {
return executingNodeId;
@@ -837,6 +765,11 @@ public void setExecutingNodeId(String executingNodeId) {
this.executingNodeId = executingNodeId;
}
+ /**
+ * Returns a diagnostic string representation of this task for logging and debugging.
+ *
+ * @return a string containing the main task fields
+ */
@Override
public String toString() {
return "ScheduledTask{" +
diff --git a/api/src/main/java/org/apache/unomi/api/tenants/security/TenantSecurityService.java b/api/src/main/java/org/apache/unomi/api/tenants/security/TenantSecurityService.java
index 0e5a803977..e9116d3a03 100644
--- a/api/src/main/java/org/apache/unomi/api/tenants/security/TenantSecurityService.java
+++ b/api/src/main/java/org/apache/unomi/api/tenants/security/TenantSecurityService.java
@@ -40,7 +40,6 @@ public interface TenantSecurityService {
*
* @param tenantId the ID of the tenants to configure
* @param settings the security settings to apply
- * @throws ConfigurationException if the settings are invalid or cannot be applied
*/
void configureSecuritySettings(String tenantId, SecuritySettings settings);
diff --git a/api/src/main/java/org/apache/unomi/api/utils/ConditionBuilder.java b/api/src/main/java/org/apache/unomi/api/utils/ConditionBuilder.java
index 9871d6a199..c2ce9b02e6 100644
--- a/api/src/main/java/org/apache/unomi/api/utils/ConditionBuilder.java
+++ b/api/src/main/java/org/apache/unomi/api/utils/ConditionBuilder.java
@@ -216,6 +216,7 @@ public ConditionItem condition(String conditionTypeId) {
return new ConditionItem(conditionTypeId, definitionsService);
}
+ /** Base class for comparison-based condition items. */
public abstract class ComparisonCondition extends ConditionItem {
/**
@@ -857,7 +858,9 @@ public class NestedCondition extends ConditionItem {
*/
public class ConditionItem {
+ /** The underlying condition. */
protected Condition condition;
+ /** Service for resolving condition definitions. */
protected DefinitionsService definitionsService;
/**
diff --git a/build.sh b/build.sh
index 7ffa3426c6..a88ce8a0ff 100755
--- a/build.sh
+++ b/build.sh
@@ -272,6 +272,9 @@ IT_DEBUG_PORT=5006
IT_DEBUG_SUSPEND=false
SKIP_MIGRATION_TESTS=false
KEEP_CONTAINER=false
+JAVADOC=false
+LOG_FILE=""
+LOG_FILE_ONLY=false
# Enhanced usage function with color support
usage() {
@@ -312,7 +315,10 @@ EOF
echo -e " ${CYAN}--it-debug-suspend${NC} Suspend integration test until debugger connects"
echo -e " ${CYAN}--skip-migration-tests${NC} Skip migration-related tests"
echo -e " ${CYAN}--keep-container${NC} Keep search engine container running after tests (for post-failure inspection)"
- echo -e " ${CYAN}--ci${NC} CI mode: no Karaf, no Maven build cache, non-interactive"
+ echo -e " ${CYAN}--javadoc${NC} Build and validate Javadoc after install (fails on doclint errors)"
+ echo -e " ${CYAN}--ci${NC} CI mode: no Karaf, no Maven build cache, non-interactive, includes Javadoc"
+ echo -e " ${CYAN}--log-file PATH${NC} Tee all output to PATH (console + file)"
+ echo -e " ${CYAN}--log-file-only${NC} With --log-file: write to file only, suppress console"
else
cat << "EOF"
_ _ _____ _ ____
@@ -348,7 +354,10 @@ EOF
echo " --it-debug-suspend Suspend integration test until debugger connects"
echo " --skip-migration-tests Skip migration-related tests"
echo " --keep-container Keep search engine container running after tests (for post-failure inspection)"
- echo " --ci CI mode: no Karaf, no Maven build cache, non-interactive"
+ echo " --javadoc Build and validate Javadoc after install (fails on doclint errors)"
+ echo " --ci CI mode: no Karaf, no Maven build cache, non-interactive, includes Javadoc"
+ echo " --log-file PATH Tee all output to PATH (console + file)"
+ echo " --log-file-only With --log-file: write to file only, suppress console"
fi
echo
@@ -496,11 +505,22 @@ while [ "$1" != "" ]; do
--keep-container)
KEEP_CONTAINER=true
;;
+ --javadoc)
+ JAVADOC=true
+ ;;
+ --log-file)
+ shift
+ LOG_FILE="$1"
+ ;;
+ --log-file-only)
+ LOG_FILE_ONLY=true
+ ;;
--ci)
NO_KARAF=true
USE_MAVEN_CACHE=false
BUILD_NON_INTERACTIVE=true
MAVEN_QUIET=true
+ JAVADOC=true
;;
*)
echo "Unknown option: $1"
@@ -510,6 +530,20 @@ while [ "$1" != "" ]; do
shift
done
+# Wire up log file output if requested
+if [ "$LOG_FILE_ONLY" = true ] && [ -z "$LOG_FILE" ]; then
+ echo "Error: --log-file-only requires --log-file PATH"
+ exit 1
+fi
+
+if [ -n "$LOG_FILE" ]; then
+ if [ "$LOG_FILE_ONLY" = true ]; then
+ exec > "$LOG_FILE" 2>&1
+ else
+ exec > >(tee -a "$LOG_FILE") 2>&1
+ fi
+fi
+
# Set environment
DIRNAME=`dirname "$0"`
PROGNAME=`basename "$0"`
@@ -522,6 +556,10 @@ if [ "$PURGE_MAVEN_CACHE" = true ]; then
echo "Purging Maven cache..."
rm -rf ~/.m2/build-cache ~/.m2/dependency-cache ~/.m2/dependency-cache_v2
echo "Maven cache purged."
+ # Disable the build cache for this run: a cold cache with the extension still active
+ # causes the workspace resolver to return target/classes instead of built JARs,
+ # breaking karaf-maven-plugin:verify. Disabling matches CI behaviour.
+ USE_MAVEN_CACHE=false
fi
# Function to check if command exists
@@ -974,7 +1012,7 @@ echo "Estimated time: 3-5 minutes for build, 50-60 minutes with integration test
start_timer
# Build phases with enhanced output
-total_steps=2
+[ "$JAVADOC" = true ] && total_steps=3 || total_steps=2
current_step=0
write_it_run_trace_start() {
@@ -1056,6 +1094,21 @@ fi
print_status "success" "Build completed in $(get_elapsed_time)"
+if [ "$JAVADOC" = true ]; then
+ print_section "Javadoc Validation"
+ print_progress $((++current_step)) $total_steps "Generating and validating Javadoc..."
+ if [ "$HAS_COLORS" -eq 1 ]; then
+ echo -e "${GRAY}Running: $MVN_CMD javadoc:javadoc -DskipTests $MVN_OPTS${NC}"
+ else
+ echo "Running: $MVN_CMD javadoc:javadoc -DskipTests $MVN_OPTS"
+ fi
+ $MVN_CMD javadoc:javadoc -DskipTests $MVN_OPTS || {
+ print_status "error" "Javadoc validation failed — fix doclint errors above before pushing"
+ exit 1
+ }
+ print_status "success" "Javadoc validated successfully"
+fi
+
# Deployment section with enhanced output
if [ "$DEPLOY" = true ]; then
# Validate Karaf home directory
diff --git a/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/IRouterCamelContext.java b/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/IRouterCamelContext.java
index 476b36e82f..c6835f143e 100644
--- a/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/IRouterCamelContext.java
+++ b/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/IRouterCamelContext.java
@@ -29,14 +29,12 @@
* Rebuilding export reader routes after an {@link org.apache.unomi.router.api.ExportConfiguration} update
* Optional Camel tracing for troubleshooting route execution
*
- *
*
* Typical usage:
*
* - Management services call update methods when import/export configuration documents change
* - Cleanup paths call {@link #killExistingRoute(String, boolean)} to drop routes whose configs were removed
*
- *
*
* @since 1.0
*/
diff --git a/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/ImportExportConfiguration.java b/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/ImportExportConfiguration.java
index 7c9b5e2382..782c7903ee 100644
--- a/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/ImportExportConfiguration.java
+++ b/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/ImportExportConfiguration.java
@@ -35,7 +35,6 @@
* Tracks execution status and history
* Handles configuration activation/deactivation
*
- *
*
* Usage in Unomi:
*
@@ -43,7 +42,6 @@
* - Consumed by Camel routes to determine how to process data
* - Referenced by import/export processors to format data correctly
*
- *
*
* Configuration properties include:
*
@@ -58,7 +56,6 @@
* - status - current status of the configuration
* - executions - history of execution attempts
*
- *
*
* @see org.apache.unomi.router.api.services.ImportExportConfigurationService
* @since 1.0
diff --git a/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/ProfileToImport.java b/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/ProfileToImport.java
index 957ac88e9e..0ea3a94de2 100644
--- a/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/ProfileToImport.java
+++ b/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/ProfileToImport.java
@@ -33,7 +33,6 @@
* Handles profile deletion flags
* Controls merge vs full-replace behavior for existing profiles (see {@link #isOverwriteExistingProfiles()})
*
- *
*
* Usage in Unomi:
*
@@ -41,7 +40,6 @@
* - Consumed by ProfileImportService for import operations
* - Supports different import strategies (merge/overwrite/delete)
*
- *
*
* @see Profile
* @see org.apache.unomi.router.api.services.ProfileImportService
diff --git a/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/exceptions/BadProfileDataFormatException.java b/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/exceptions/BadProfileDataFormatException.java
index c4156e929b..a348d605d6 100644
--- a/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/exceptions/BadProfileDataFormatException.java
+++ b/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/exceptions/BadProfileDataFormatException.java
@@ -28,14 +28,12 @@
* Malformed multi-value fields
* Empty lines in import files
*
- *
*
* Usage in Unomi:
*
* - Thrown by import line processors (e.g. {@code LineSplitProcessor})
* - Handled by import route error handlers
*
- *
*
* @see org.apache.unomi.router.api.ProfileToImport
* @since 1.0
diff --git a/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/services/ImportExportConfigurationService.java b/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/services/ImportExportConfigurationService.java
index bb20ba2d06..73b8d37e30 100644
--- a/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/services/ImportExportConfigurationService.java
+++ b/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/services/ImportExportConfigurationService.java
@@ -35,7 +35,6 @@
* Coordinating with Camel routes for configuration updates
* Tracking configuration changes that need route updates
*
- *
*
* Usage in Unomi:
*
@@ -43,7 +42,6 @@
* - Consumed by Camel routes to get configuration updates
* - Utilized by admin interfaces for configuration management
*
- *
*
* Implementation considerations:
*
@@ -51,7 +49,6 @@
* - Thread safety should be considered for concurrent operations
* - Configuration changes should be properly propagated to running routes
*
- *
*
* @param The type of configuration (ImportConfiguration or ExportConfiguration)
* @see ImportConfiguration
diff --git a/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/services/ProfileExportService.java b/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/services/ProfileExportService.java
index ccdc3711b6..506a9a1f1a 100644
--- a/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/services/ProfileExportService.java
+++ b/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/services/ProfileExportService.java
@@ -34,7 +34,6 @@
* Handling data formatting and transformation
* Managing export file generation
*
- *
*
* Usage in Unomi:
*
@@ -42,7 +41,6 @@
* - Used during scheduled export operations
* - Integrated with Unomi's segmentation system
*
- *
*
* Implementation considerations:
*
@@ -51,7 +49,6 @@
* - Must respect profile property formatting
* - Should handle multi-valued properties
*
- *
*
* @see Profile
* @see ExportConfiguration
diff --git a/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/services/ProfileImportService.java b/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/services/ProfileImportService.java
index 0d008bbee7..aafffba13b 100644
--- a/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/services/ProfileImportService.java
+++ b/extensions/router/router-api/src/main/java/org/apache/unomi/router/api/services/ProfileImportService.java
@@ -32,7 +32,6 @@
* Handling profile creation for new imports
* Managing profile deletion when specified
*
- *
*
* Usage in Unomi:
*
@@ -40,7 +39,6 @@
* - Used during batch import operations
* - Integrated with Unomi's profile management system
*
- *
*
* Implementation considerations:
*
@@ -49,7 +47,6 @@
* - Must maintain data consistency
* - Expects property values already parsed (type conversion is done upstream, e.g. by import processors)
*
- *
*
* @see ProfileToImport
* @see org.apache.unomi.api.Profile
diff --git a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/bean/CollectProfileBean.java b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/bean/CollectProfileBean.java
index a62b19ece1..d0454e760a 100644
--- a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/bean/CollectProfileBean.java
+++ b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/bean/CollectProfileBean.java
@@ -34,7 +34,6 @@
* Segment-based profile extraction via persistence queries
* Integration with Unomi's persistence service
*
- *
*
* @since 1.0
*/
diff --git a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/context/RouterCamelContext.java b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/context/RouterCamelContext.java
index d8c59857a9..6404482abf 100644
--- a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/context/RouterCamelContext.java
+++ b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/context/RouterCamelContext.java
@@ -65,7 +65,6 @@
* Supports Kafka ({@link RouterConstants#CONFIG_TYPE_KAFKA}) and in-process
* {@code direct:} endpoints when configured as {@link RouterConstants#CONFIG_TYPE_NOBROKER}
*
- *
*
* @since 1.0
*/
diff --git a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/processor/ExportRouteCompletionProcessor.java b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/processor/ExportRouteCompletionProcessor.java
index 7f7a7e7767..a45a524e08 100644
--- a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/processor/ExportRouteCompletionProcessor.java
+++ b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/processor/ExportRouteCompletionProcessor.java
@@ -41,7 +41,6 @@
* Maintains execution history within configured size limits
* Persists updated configuration information
*
- *
*
* @since 1.0
*/
@@ -66,7 +65,6 @@ public class ExportRouteCompletionProcessor implements Processor {
* Maintains the execution history size limit
* Updates the export status to complete
*
- *
*
* @param exchange the Camel exchange containing export execution details
* @throws Exception if an error occurs during processing
diff --git a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/processor/ImportConfigByFileNameProcessor.java b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/processor/ImportConfigByFileNameProcessor.java
index 2673898ea2..0ac7d44992 100644
--- a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/processor/ImportConfigByFileNameProcessor.java
+++ b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/processor/ImportConfigByFileNameProcessor.java
@@ -38,7 +38,7 @@
*
* The processor expects filenames in the format:
*
configurationId.extension
- * where the configurationId matches an existing import configuration.
+ * where the configurationId matches an existing import configuration.
*
* Features:
*
@@ -47,7 +47,6 @@
* - Sets configuration in exchange header for processing
* - Handles missing configurations gracefully
*
- *
*
* @since 1.0
*/
@@ -73,7 +72,6 @@ public class ImportConfigByFileNameProcessor implements Processor {
* Sets the configuration in the exchange header if found
* Stops route processing if no configuration is found
*
- *
*
* @param exchange the Camel exchange containing the file to process
* @throws Exception if an error occurs during processing
diff --git a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/processor/ImportRouteCompletionProcessor.java b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/processor/ImportRouteCompletionProcessor.java
index e34d965b62..25784d6c52 100644
--- a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/processor/ImportRouteCompletionProcessor.java
+++ b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/processor/ImportRouteCompletionProcessor.java
@@ -38,7 +38,6 @@
* Maintains execution history
* Handles both one-shot and recurring imports
*
- *
*
* @since 1.0
*/
@@ -66,7 +65,6 @@ public class ImportRouteCompletionProcessor implements Processor {
* Updates the import configuration with execution results
* Sets the final status based on success/failure counts
*
- *
*
* @param exchange the Camel exchange containing import results
* @throws Exception if an error occurs during processing
diff --git a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/processor/LineBuildProcessor.java b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/processor/LineBuildProcessor.java
index f1748cc0af..2170efc3fb 100644
--- a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/processor/LineBuildProcessor.java
+++ b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/processor/LineBuildProcessor.java
@@ -55,7 +55,6 @@ public LineBuildProcessor(ProfileExportService profileExportService) {
* Converts the profile to a CSV line using the ProfileExportService
* Sets the resulting string as the new exchange body
*
- *
*
* @param exchange the Camel exchange containing the Profile to convert and export configuration
* @throws Exception if an error occurs during processing
diff --git a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/processor/LineSplitFailureHandler.java b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/processor/LineSplitFailureHandler.java
index 2f4a5950de..4b68397b20 100644
--- a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/processor/LineSplitFailureHandler.java
+++ b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/processor/LineSplitFailureHandler.java
@@ -34,7 +34,6 @@
* BadProfileDataFormatException - for data format related errors
* General exceptions - capturing the root cause message
*
- *
*
* For each failure, it creates an ImportLineError object containing:
*
@@ -42,7 +41,6 @@
* - The content of the failed line
* - The line number in the source file
*
- *
*
* @since 1.0
*/
@@ -60,7 +58,6 @@ public class LineSplitFailureHandler implements Processor {
* Extracts the appropriate error message based on the exception type
* Sets the failure information in the exchange for further processing
*
- *
*
* @param exchange the Camel exchange containing the failed message and exception details
* @throws Exception if an error occurs during failure handling
diff --git a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/processor/LineSplitProcessor.java b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/processor/LineSplitProcessor.java
index e93d556374..90d9d4fb8e 100644
--- a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/processor/LineSplitProcessor.java
+++ b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/processor/LineSplitProcessor.java
@@ -49,7 +49,6 @@
* Profile merging configuration
* Delete operation support
*
- *
*
* @since 1.0
*/
@@ -101,7 +100,6 @@ public class LineSplitProcessor implements Processor {
* Sets up profile merging configuration
* Processes delete operations if configured
*
- *
*
* @param exchange the Camel exchange containing the CSV line to process
* @throws Exception if an error occurs during processing, including BadProfileDataFormatException
diff --git a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/processor/UnomiStorageProcessor.java b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/processor/UnomiStorageProcessor.java
index 99dbe0775a..d7335e8ed6 100644
--- a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/processor/UnomiStorageProcessor.java
+++ b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/processor/UnomiStorageProcessor.java
@@ -43,7 +43,6 @@
* Updates profile information with calculated segments
* Persists profiles in the Unomi storage system
*
- *
*
* @since 1.0
*/
@@ -70,7 +69,6 @@ public class UnomiStorageProcessor implements Processor {
* For non-delete operations, calculates and updates segments and scores
* Persists the profile using the ProfileImportService
*
- *
*
* @param exchange the Camel exchange containing the profile to process
* @throws Exception if an error occurs during processing
diff --git a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/ProfileExportCollectRouteBuilder.java b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/ProfileExportCollectRouteBuilder.java
index 27a618c4b2..48fb967f09 100644
--- a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/ProfileExportCollectRouteBuilder.java
+++ b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/ProfileExportCollectRouteBuilder.java
@@ -45,7 +45,6 @@
* Security through endpoint allowlist
* Support for Kafka and in-process {@code direct:} endpoints ({@link RouterConstants#CONFIG_TYPE_KAFKA} / {@link RouterConstants#CONFIG_TYPE_NOBROKER})
*
- *
*
* @since 1.0
*/
@@ -82,7 +81,6 @@ public ProfileExportCollectRouteBuilder(Map kafkaProps, String c
* Processes profiles for export
* Routes data to appropriate endpoints
*
- *
*
* @throws Exception if an error occurs during route configuration
*/
diff --git a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/ProfileExportProducerRouteBuilder.java b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/ProfileExportProducerRouteBuilder.java
index 89619b7da9..e9f52a2a31 100644
--- a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/ProfileExportProducerRouteBuilder.java
+++ b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/ProfileExportProducerRouteBuilder.java
@@ -41,7 +41,6 @@
* Completion handling and status updates
* Support for Kafka and in-process {@code direct:} endpoints ({@link RouterConstants#CONFIG_TYPE_KAFKA} / {@link RouterConstants#CONFIG_TYPE_NOBROKER})
*
- *
*
* @since 1.0
*/
@@ -86,7 +85,6 @@ public void setProfileExportService(ProfileExportService profileExportService) {
* Handles export completion
* Routes data to configured destinations
*
- *
*
* @throws Exception if an error occurs during route configuration
*/
diff --git a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/ProfileImportFromSourceRouteBuilder.java b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/ProfileImportFromSourceRouteBuilder.java
index 9a68e37974..8199d44e5c 100644
--- a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/ProfileImportFromSourceRouteBuilder.java
+++ b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/ProfileImportFromSourceRouteBuilder.java
@@ -52,7 +52,6 @@
* Support for Kafka and in-process {@code direct:} endpoints ({@link RouterConstants#CONFIG_TYPE_KAFKA} / {@link RouterConstants#CONFIG_TYPE_NOBROKER})
* Graceful shutdown handling
*
- *
*
* @since 1.0
*/
@@ -92,7 +91,6 @@ public ProfileImportFromSourceRouteBuilder(Map kafkaProps, Strin
* Route processed data to appropriate endpoints
* Manage graceful completion of imports
*
- *
*
* @throws Exception if an error occurs during route configuration
*/
diff --git a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/ProfileImportOneShotRouteBuilder.java b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/ProfileImportOneShotRouteBuilder.java
index 3f0d8ca1a6..3c9575cac0 100644
--- a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/ProfileImportOneShotRouteBuilder.java
+++ b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/ProfileImportOneShotRouteBuilder.java
@@ -43,7 +43,6 @@
* Automatic file movement after processing
* Error reporting and failed file handling
*
- *
*
* @since 1.0
*/
@@ -79,7 +78,6 @@ public ProfileImportOneShotRouteBuilder(Map kafkaProps, String c
* Handles validation and format errors
* Routes processed data to appropriate endpoints
*
- *
*
* @throws Exception if an error occurs during route configuration
*/
diff --git a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/ProfileImportToUnomiRouteBuilder.java b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/ProfileImportToUnomiRouteBuilder.java
index 7f31e57bbb..0e463018e2 100644
--- a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/ProfileImportToUnomiRouteBuilder.java
+++ b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/ProfileImportToUnomiRouteBuilder.java
@@ -40,7 +40,6 @@
* Import completion handling
* Error handling and reporting
*
- *
*
* @since 1.0
*/
@@ -75,7 +74,6 @@ public ProfileImportToUnomiRouteBuilder(Map kafkaProps, String c
* Handles import completion
* Manages error reporting
*
- *
*
* @throws Exception if an error occurs during route configuration
*/
diff --git a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/RouterAbstractRouteBuilder.java b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/RouterAbstractRouteBuilder.java
index 69586990fa..720274ec0b 100644
--- a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/RouterAbstractRouteBuilder.java
+++ b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/route/RouterAbstractRouteBuilder.java
@@ -41,7 +41,6 @@
* Profile service integration
* Endpoint security through allowlist
*
- *
*
* @since 1.0
*/
@@ -110,7 +109,6 @@ public RouterAbstractRouteBuilder(Map kafkaProps, String configT
* Returns direct endpoint URIs when not using Kafka
* Configures consumer properties for incoming endpoints
*
- *
*
* @param direction the direction of the endpoint (to/from)
* @param operationDepositBuffer the operation buffer identifier
diff --git a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/strategy/ArrayListAggregationStrategy.java b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/strategy/ArrayListAggregationStrategy.java
index c113e3aebd..93f3cb8d64 100644
--- a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/strategy/ArrayListAggregationStrategy.java
+++ b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/strategy/ArrayListAggregationStrategy.java
@@ -31,7 +31,6 @@
* For the first message (when oldExchange is null), it creates a new ArrayList and adds the message body to it
* For subsequent messages, it adds the new message body to the existing ArrayList
*
- *
*
* The ArrayList is maintained in the exchange body, allowing for easy access to all aggregated items
* once the aggregation is complete.
@@ -50,7 +49,6 @@ public class ArrayListAggregationStrategy implements AggregationStrategy {
* The new body is added to the ArrayList
* The ArrayList is maintained in the exchange body for subsequent aggregations
*
- *
*
* @param oldExchange the previous exchange being aggregated (may be null on first invocation)
* @param newExchange the current exchange being aggregated (contains the new item to add to the list)
diff --git a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/strategy/StringLinesAggregationStrategy.java b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/strategy/StringLinesAggregationStrategy.java
index 8ccabe6871..82f0ad2ccf 100644
--- a/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/strategy/StringLinesAggregationStrategy.java
+++ b/extensions/router/router-core/src/main/java/org/apache/unomi/router/core/strategy/StringLinesAggregationStrategy.java
@@ -31,7 +31,6 @@
* For the first message (when oldExchange is null), it simply returns the new exchange
* For subsequent messages, it appends the new content to the existing content using the configured line separator
*
- *
*
* The line separator used for aggregation is obtained from the ExportConfiguration object
* stored in the exchange header under the key "exportConfig".
@@ -50,7 +49,6 @@ public class StringLinesAggregationStrategy implements AggregationStrategy {
* If there's an old exchange, the new content is appended to it with the line separator
* If there's no old exchange, the new exchange is returned as is
*
- *
*
* @param oldExchange the previous exchange being aggregated (may be null on first invocation)
* @param newExchange the current exchange being aggregated (contains the new line to append)
diff --git a/itests/src/test/java/org/apache/unomi/itests/graphql/GraphQLListIT.java b/itests/src/test/java/org/apache/unomi/itests/graphql/GraphQLListIT.java
index 540c3ebff4..b75a83ecb3 100644
--- a/itests/src/test/java/org/apache/unomi/itests/graphql/GraphQLListIT.java
+++ b/itests/src/test/java/org/apache/unomi/itests/graphql/GraphQLListIT.java
@@ -102,7 +102,8 @@ public void testCRUD() throws Exception {
Object profileId = context.getValue("data.cdp.findLists.edges[0].node.active.edges[0].node.cdp_profileIDs[0].id");
return profile.getItemId().equals(profileId);
},
- DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES);
+ // async rule-engine processing needs more retries on loaded CI
+ DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES * 3);
Assert.assertEquals("testListId", findListsContext.getValue("data.cdp.findLists.edges[0].node.id"));
diff --git a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/ConditionContextHelper.java b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/ConditionContextHelper.java
index 5e96d2d6dd..e338fafeaa 100644
--- a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/ConditionContextHelper.java
+++ b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/ConditionContextHelper.java
@@ -127,10 +127,8 @@ public static Condition getContextualCondition(Condition condition, Map context,
@@ -139,6 +137,17 @@ public static Condition getContextualCondition(
return getContextualCondition(condition, context, scriptExecutor, definitionsService, null);
}
+ /**
+ * Resolves parameter references and script expressions in a condition,
+ * with optional type validation and execution tracing.
+ *
+ * @param condition the condition to resolve
+ * @param context context map for parameter resolution
+ * @param scriptExecutor executor for script expressions
+ * @param definitionsService optional service for parameter type information
+ * @param tracerService optional tracer service for validation warnings
+ * @return resolved condition with all parameter references resolved
+ */
public static Condition getContextualCondition(
Condition condition,
Map context,
diff --git a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/PastEventConditionPersistenceQueryBuilder.java b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/PastEventConditionPersistenceQueryBuilder.java
index 3adffc589c..ba4edbda65 100644
--- a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/PastEventConditionPersistenceQueryBuilder.java
+++ b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/PastEventConditionPersistenceQueryBuilder.java
@@ -51,7 +51,6 @@
* the result means "events occurred within bounds" or "no events occurred".
*
*
- * @see org.apache.unomi.plugins.advancedconditions.conditions.PastEventConditionEvaluator
*/
public interface PastEventConditionPersistenceQueryBuilder {
diff --git a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/evaluator/ConditionEvaluatorDispatcher.java b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/evaluator/ConditionEvaluatorDispatcher.java
index 8259311cbb..791f6ce944 100644
--- a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/evaluator/ConditionEvaluatorDispatcher.java
+++ b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/evaluator/ConditionEvaluatorDispatcher.java
@@ -42,7 +42,6 @@
* {@code PastEventConditionEvaluator} for a typical evaluator.
*
* @see org.apache.unomi.persistence.spi.conditions.evaluator.impl.ConditionEvaluatorDispatcherImpl
- * @see org.apache.unomi.plugins.advancedconditions.conditions.PastEventConditionEvaluator
*/
public interface ConditionEvaluatorDispatcher {
diff --git a/services-common/src/main/java/org/apache/unomi/services/common/cache/AbstractMultiTypeCachingService.java b/services-common/src/main/java/org/apache/unomi/services/common/cache/AbstractMultiTypeCachingService.java
index a5d29f809a..499c7aa7d0 100644
--- a/services-common/src/main/java/org/apache/unomi/services/common/cache/AbstractMultiTypeCachingService.java
+++ b/services-common/src/main/java/org/apache/unomi/services/common/cache/AbstractMultiTypeCachingService.java
@@ -49,7 +49,15 @@
import static org.apache.unomi.api.tenants.TenantService.SYSTEM_TENANT;
/**
- * Base service supporting multiple cacheable types
+ * Abstract base for services that cache multiple {@link CacheableTypeConfig} types per tenant.
+ *
+ * Handles OSGi bundle lifecycle (loading predefined JSON from {@code META-INF/cxs/}), periodic
+ * cache refresh via {@link SchedulerService}, tenant-aware persistence queries, and system-tenant
+ * inheritance. Concrete implementations include {@code DefinitionsServiceImpl},
+ * {@code SegmentServiceImpl}, and {@code RulesServiceImpl}.
+ *
+ * @see MultiTypeCacheService
+ * @see CacheableTypeConfig
*/
public abstract class AbstractMultiTypeCachingService extends AbstractContextAwareService implements SynchronousBundleListener {
@@ -81,26 +89,55 @@ public abstract class AbstractMultiTypeCachingService extends AbstractContextAwa
// Each service defines its supported types
protected abstract Set> getTypeConfigs();
+ /**
+ * Sets the OSGi bundle context used for bundle lifecycle listening and predefined item loading.
+ *
+ * @param bundleContext the bundle context
+ */
public void setBundleContext(BundleContext bundleContext) {
this.bundleContext = bundleContext;
}
+ /**
+ * Sets the scheduler service used for periodic cache refresh tasks.
+ *
+ * @param schedulerService the scheduler service
+ */
public void setSchedulerService(SchedulerService schedulerService) {
this.schedulerService = schedulerService;
}
+ /**
+ * Sets the multi-type cache service backing this service.
+ *
+ * @param cacheService the cache service
+ */
public void setCacheService(MultiTypeCacheService cacheService) {
this.cacheService = cacheService;
}
+ /**
+ * Sets the tenant service used to enumerate tenants during cache refresh.
+ *
+ * @param tenantService the tenant service
+ */
public void setTenantService(TenantService tenantService) {
this.tenantService = tenantService;
}
+ /**
+ * Sets the audit service used when saving items to persistence.
+ *
+ * @param auditService the audit service
+ */
public void setAuditService(AuditService auditService) {
this.auditService = auditService;
}
+ /**
+ * Initializes caches, loads predefined items from bundles, registers a bundle listener,
+ * loads initial persistence data, and starts refresh timers.
+ */
public void postConstruct() {
logger.debug("postConstruct {{}}", bundleContext.getBundle());
@@ -148,6 +185,9 @@ protected void loadInitialDataForAllTypes() {
}
}
+ /**
+ * Shuts down the service by removing the bundle listener and cancelling refresh timers.
+ */
public void preDestroy() {
bundleContext.removeBundleListener(this);
shutdownTimers();
@@ -441,7 +481,7 @@ protected void loadPredefinedItems(BundleContext bundleContext) {
}
/**
- * Get all items contributed by a specific bundle.
+ * Retrieves all items contributed by a specific bundle.
*
* @param bundleId the ID of the bundle
* @return a list of items contributed by that bundle, or an empty list if none
@@ -575,6 +615,11 @@ else if (config.getPostProcessor() != null) {
}
}
+ /**
+ * Handles OSGi bundle start and stop events to load or remove predefined items.
+ *
+ * @param event the bundle event
+ */
@Override
public void bundleChanged(BundleEvent event) {
contextManager.executeAsSystem(() -> {
@@ -672,16 +717,16 @@ protected void removeItemOnBundleStop(Object item, String itemId, String itemTyp
}
/**
- * Get a map of all plugin types indexed by plugin ID (bundle ID).
+ * Retrieves a map of plugin types indexed by contributing bundle identifier.
*
- * @return Map where key is the bundle ID, value is the list of plugin types from that bundle
+ * @return map of bundle ID to plugin types contributed by that bundle
*/
public Map> getTypesByPlugin() {
return pluginTypes;
}
/**
- * Get all items of a specific type for the current tenant.
+ * Retrieves all items of a specific type for the current tenant.
*
* @param the type of items to retrieve
* @param itemClass the class of the items to retrieve
@@ -696,7 +741,7 @@ protected Collection getAllItems(Class itemClass,
}
/**
- * Get items of a specific type filtered by tag.
+ * Retrieves items of a specific type filtered by tag.
*
* @param the type of items to retrieve
* @param itemClass the class of the items to retrieve
@@ -713,7 +758,7 @@ protected Set getItemsByTag(Class itemClas
}
/**
- * Get items of a specific type filtered by system tag.
+ * Retrieves items of a specific type filtered by system tag.
*
* @param the type of items to retrieve
* @param itemClass the class of the items to retrieve
@@ -730,12 +775,12 @@ protected Set getItemsBySystemTag(Class it
}
/**
- * Get a specific item by ID.
+ * Retrieves a specific item by identifier.
*
* @param the type of item to retrieve
* @param id the ID of the item
* @param itemClass the class of the item
- * @return the item with the specified ID, or null if not found
+ * @return the item with the specified identifier, or {@code null} if not found
*/
protected T getItem(String id, Class itemClass) {
String tenantId = contextManager.getCurrentContext().getTenantId();
diff --git a/services-common/src/main/java/org/apache/unomi/services/common/security/AuditServiceImpl.java b/services-common/src/main/java/org/apache/unomi/services/common/security/AuditServiceImpl.java
index 1952e0cfa5..e6c2e6bc3f 100644
--- a/services-common/src/main/java/org/apache/unomi/services/common/security/AuditServiceImpl.java
+++ b/services-common/src/main/java/org/apache/unomi/services/common/security/AuditServiceImpl.java
@@ -25,15 +25,31 @@
import java.util.*;
+/**
+ * Default implementation of {@link AuditService} backed by {@link PersistenceService}.
+ *
+ * Records create, update, and delete metadata on {@link Item} instances and supports
+ * tenant synchronization queries (modified items and last-sync tracking).
+ */
public class AuditServiceImpl implements AuditService {
private static final Logger LOGGER = LoggerFactory.getLogger(AuditServiceImpl.class);
private PersistenceService persistenceService;
+ /**
+ * Binds the persistence service used for sync queries and updates.
+ *
+ * @param persistenceService the persistence service
+ */
public void bindPersistenceService(PersistenceService persistenceService) {
this.persistenceService = persistenceService;
}
+ /**
+ * Unbinds the persistence service.
+ *
+ * @param persistenceService the persistence service being unbound
+ */
public void unbindPersistenceService(PersistenceService persistenceService) {
this.persistenceService = null;
}
@@ -132,6 +148,12 @@ public void logTenantOperation(String tenantId, String operation) {
LOGGER.info("Tenant operation: {} performed on tenant {}", operation, tenantId);
}
+ /**
+ * Updates the last-modified metadata on an item.
+ *
+ * @param item the item to update
+ * @param userId the user performing the modification
+ */
public void updateModificationMetadata(Item item, String userId) {
item.setLastModifiedBy(userId);
item.setLastModificationDate(new Date());
diff --git a/services-common/src/main/java/org/apache/unomi/services/common/security/ExecutionContextManagerImpl.java b/services-common/src/main/java/org/apache/unomi/services/common/security/ExecutionContextManagerImpl.java
index 5553380153..6cf39713a7 100644
--- a/services-common/src/main/java/org/apache/unomi/services/common/security/ExecutionContextManagerImpl.java
+++ b/services-common/src/main/java/org/apache/unomi/services/common/security/ExecutionContextManagerImpl.java
@@ -30,6 +30,16 @@
import java.util.Set;
import java.util.function.Supplier;
+/**
+ * Thread-local implementation of {@link ExecutionContextManager}.
+ *
+ * Maintains the current {@link ExecutionContext} per thread, derived from the active
+ * {@link SecurityService} subject. {@link #executeAsSystem(Supplier)} switches both the
+ * security subject and execution context to the system tenant for the duration of the
+ * operation, then restores the previous state.
+ *
+ * @see KarafSecurityService
+ */
public class ExecutionContextManagerImpl implements ExecutionContextManager {
private static final Logger LOGGER = LoggerFactory.getLogger(ExecutionContextManagerImpl.class);
@@ -37,6 +47,11 @@ public class ExecutionContextManagerImpl implements ExecutionContextManager {
private final ThreadLocal currentContext = new ThreadLocal<>();
private SecurityService securityService;
+ /**
+ * Sets the security service used to resolve subjects, roles, and permissions.
+ *
+ * @param securityService the security service
+ */
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
diff --git a/services-common/src/main/java/org/apache/unomi/services/common/security/KarafSecurityService.java b/services-common/src/main/java/org/apache/unomi/services/common/security/KarafSecurityService.java
index 8f5ac2686b..b084dff8ae 100644
--- a/services-common/src/main/java/org/apache/unomi/services/common/security/KarafSecurityService.java
+++ b/services-common/src/main/java/org/apache/unomi/services/common/security/KarafSecurityService.java
@@ -32,9 +32,20 @@
import java.util.Set;
import java.util.stream.Collectors;
+/**
+ * Karaf JAAS-based implementation of {@link SecurityService}.
+ *
+ * Resolves the active {@link Subject} from the JAAS context, a temporary privileged subject,
+ * or the current request subject. Role and permission checks consult all active subjects in that
+ * order. Provides the system subject used by {@link ExecutionContextManagerImpl} for elevated
+ * operations.
+ *
+ * @see ExecutionContextManagerImpl
+ */
public class KarafSecurityService implements SecurityService {
private static final Logger LOGGER = LoggerFactory.getLogger(KarafSecurityService.class);
+ /** The system tenant identifier used for system-wide operations. */
public static final String SYSTEM_TENANT = "system";
private final Subject SYSTEM_SUBJECT;
@@ -45,6 +56,9 @@ public class KarafSecurityService implements SecurityService {
private final ThreadLocal currentSubject = new ThreadLocal<>();
private final ThreadLocal privilegedSubject = new ThreadLocal<>();
+ /**
+ * Creates the security service and initializes the system subject.
+ */
public KarafSecurityService() {
SYSTEM_SUBJECT = createSystemSubject();
}
@@ -58,6 +72,9 @@ private Subject createSystemSubject() {
return subject;
}
+ /**
+ * Initializes the service with default configuration if none was injected.
+ */
public void init() {
if (configuration == null) {
configuration = new SecurityServiceConfiguration();
@@ -65,6 +82,9 @@ public void init() {
updateSystemSubject();
}
+ /**
+ * Shuts down the security service.
+ */
public void destroy() {
// Cleanup
}
@@ -78,18 +98,38 @@ private void updateSystemSubject() {
}
}
+ /**
+ * Sets the audit service used for tenant operation logging.
+ *
+ * @param tenantAuditService the tenant audit service
+ */
public void setTenantAuditService(AuditService tenantAuditService) {
this.tenantAuditService = tenantAuditService;
}
+ /**
+ * Sets the security configuration (role-to-permission mappings and system roles).
+ *
+ * @param configuration the security configuration
+ */
public void setConfiguration(SecurityServiceConfiguration configuration) {
this.configuration = configuration;
}
+ /**
+ * Binds the encryption service for tenant key retrieval.
+ *
+ * @param encryptionService the encryption service
+ */
public void bindEncryptionService(EncryptionService encryptionService) {
this.encryptionService = encryptionService;
}
+ /**
+ * Unbinds the encryption service.
+ *
+ * @param encryptionService the encryption service being unbound
+ */
public void unbindEncryptionService(EncryptionService encryptionService) {
this.encryptionService = null;
}
diff --git a/services-common/src/main/java/org/apache/unomi/services/common/service/AbstractContextAwareService.java b/services-common/src/main/java/org/apache/unomi/services/common/service/AbstractContextAwareService.java
index 64940cbc88..59bb5e6ac7 100644
--- a/services-common/src/main/java/org/apache/unomi/services/common/service/AbstractContextAwareService.java
+++ b/services-common/src/main/java/org/apache/unomi/services/common/service/AbstractContextAwareService.java
@@ -35,7 +35,15 @@
import static org.apache.unomi.api.tenants.TenantService.SYSTEM_TENANT;
/**
- * Base class for services that need to be context-aware and handle inheritance from the system tenant.
+ * Base class for services that operate within a tenant {@link org.apache.unomi.api.ExecutionContext} and support
+ * inheritance from the system tenant.
+ *
+ * Subclasses use {@link #loadWithInheritance(String, Class)} and {@link #getMetadatas(Query, Class)}
+ * to resolve tenant-scoped data with fallback to the system tenant. System-tenant operations are
+ * delegated to {@link ExecutionContextManager#executeAsSystem(Runnable)}.
+ *
+ * @see org.apache.unomi.api.services.ExecutionContextManager
+ * @see org.apache.unomi.services.common.cache.AbstractMultiTypeCachingService
*/
public abstract class AbstractContextAwareService {
@@ -44,25 +52,42 @@ public abstract class AbstractContextAwareService {
protected PersistenceService persistenceService;
protected volatile ExecutionContextManager contextManager = null;
+ /**
+ * Sets the persistence service used for loading and saving items.
+ *
+ * @param persistenceService the persistence service
+ */
public void setPersistenceService(PersistenceService persistenceService) {
this.persistenceService = persistenceService;
}
+ /**
+ * Sets the execution context manager for tenant-scoped operations.
+ *
+ * @param contextManager the execution context manager
+ */
public void setContextManager(ExecutionContextManager contextManager) {
this.contextManager = contextManager;
}
+ /**
+ * Retrieves the persistence service.
+ *
+ * @return the persistence service
+ */
public PersistenceService getPersistenceService() {
return persistenceService;
}
/**
- * Load an item with tenant inheritance support.
- * First tries to load from the current tenant, then falls back to the system tenant if not found.
+ * Loads an item with tenant inheritance support.
+ *
+ * First loads from the current tenant; if not found, falls back to the system tenant.
*
- * @param itemId The ID of the item to load
- * @param itemClass The class of the item
- * @return The loaded item or null if not found in either tenant
+ * @param the item type
+ * @param itemId the identifier of the item to load
+ * @param itemClass the item class
+ * @return the loaded item, or {@code null} if not found in either tenant
*/
protected T loadWithInheritance(String itemId, Class itemClass) {
T item = persistenceService.load(itemId, itemClass);
@@ -75,10 +100,11 @@ protected T loadWithInheritance(String itemId, Class itemCla
}
/**
- * Save an item with tenant awareness.
- * Ensures the item is saved to the current tenant and handles any inheritance implications.
+ * Saves an item to the current tenant.
+ *
+ * Sets the item's tenant identifier from the current execution context before persisting.
*
- * @param item The item to save
+ * @param item the item to save
*/
protected void saveWithTenant(Item item) {
String currentTenant = contextManager.getCurrentContext().getTenantId();
@@ -89,11 +115,12 @@ protected void saveWithTenant(Item item) {
}
/**
- * Get metadata items with tenant awareness and inheritance.
+ * Retrieves metadata for items matching a query in the current tenant.
*
- * @param query The query to execute
- * @param clazz The class of items to retrieve
- * @return A partial list of metadata items
+ * @param the metadata item type
+ * @param query the query to execute
+ * @param clazz the item class
+ * @return a partial list of metadata, or empty if no tenant context is set
*/
protected PartialList getMetadatas(Query query, Class clazz) {
String currentTenantId = contextManager.getCurrentContext().getTenantId();
@@ -109,7 +136,10 @@ protected PartialList getMetadatas(Query quer
}
/**
- * Create a condition to filter by tenant
+ * Creates a condition that filters items by tenant identifier.
+ *
+ * @param tenantId the tenant identifier
+ * @return a condition matching the given tenant
*/
protected Condition createTenantCondition(String tenantId) {
Condition tenantCondition = new Condition();
@@ -121,7 +151,11 @@ protected Condition createTenantCondition(String tenantId) {
}
/**
- * Combine a query condition with a tenant condition
+ * Combines a query condition with a tenant filter using a logical AND.
+ *
+ * @param queryCondition the user query condition
+ * @param tenantCondition the tenant filter condition
+ * @return the combined condition
*/
protected Condition combineTenantCondition(Condition queryCondition, Condition tenantCondition) {
Condition finalCondition = new Condition();
@@ -132,7 +166,11 @@ protected Condition combineTenantCondition(Condition queryCondition, Condition t
}
/**
- * Convert a list of items to a list of metadata
+ * Converts a partial list of metadata items to a partial list of {@link Metadata}.
+ *
+ * @param the metadata item type
+ * @param items the source items
+ * @return the converted metadata list with the same paging metadata
*/
protected PartialList convertToMetadataList(PartialList items) {
List metadatas = new LinkedList<>();
@@ -143,9 +181,9 @@ protected PartialList convertToMetadataList(P
}
/**
- * Check if the current tenant is the system tenant
+ * Determines whether the current execution context is the system tenant.
*
- * @return true if the current tenant is the system tenant
+ * @return {@code true} if the current tenant is the system tenant
*/
protected boolean isSystemTenant() {
String currentTenant = contextManager.getCurrentContext().getTenantId();
@@ -153,19 +191,20 @@ protected boolean isSystemTenant() {
}
/**
- * Execute code in the context of the system tenant
+ * Executes an operation in the system tenant context.
*
- * @param runnable The code to execute
+ * @param operation the operation to execute
*/
protected void executeAsSystem(Runnable operation) {
contextManager.executeAsSystem(operation);
}
/**
- * Execute code in the context of the system tenant and return a value
+ * Executes an operation in the system tenant context and returns its result.
*
- * @param supplier The code to execute that returns a value
- * @return The value returned by the supplier
+ * @param the result type
+ * @param operation the operation to execute
+ * @return the value returned by the operation
*/
protected T executeAsSystem(Supplier operation) {
return contextManager.executeAsSystem(operation);
diff --git a/services/src/main/java/org/apache/unomi/services/impl/scheduler/SchedulerServiceImpl.java b/services/src/main/java/org/apache/unomi/services/impl/scheduler/SchedulerServiceImpl.java
index 6f6c577890..74f3cbe711 100644
--- a/services/src/main/java/org/apache/unomi/services/impl/scheduler/SchedulerServiceImpl.java
+++ b/services/src/main/java/org/apache/unomi/services/impl/scheduler/SchedulerServiceImpl.java
@@ -790,6 +790,43 @@ public void preDestroy() {
LOGGER.debug("Error shutting down execution manager: {}", e.getMessage());
}
+ // Mark tasks still in RUNNING state as CRASHED — they were interrupted mid-execution.
+ // This allows the next scheduler instance to reschedule them via CRASHED→SCHEDULED,
+ // and prevents invalid RUNNING→SCHEDULED transitions in shared persistence environments.
+ // We go directly to persistenceProvider/nonPersistentTasks here (instead of
+ // getAllTasks()/saveTask()) because shutdownNow is already true at this point,
+ // and those wrapper methods short-circuit to no-ops once that flag is set.
+ if (stateManager != null) {
+ try {
+ List tasksToCheck = new ArrayList<>(nonPersistentTasks.values());
+ if (persistenceProvider != null) {
+ // Only crash RUNNING tasks owned by this node, or stale orphans with no lock owner.
+ // Do not touch RUNNING tasks locked by other nodes in a cluster.
+ for (ScheduledTask task : persistenceProvider.findTasksByStatus(ScheduledTask.TaskStatus.RUNNING)) {
+ String lockOwner = task.getLockOwner();
+ if (lockOwner == null || nodeId.equals(lockOwner)) {
+ tasksToCheck.add(task);
+ }
+ }
+ }
+ for (ScheduledTask task : tasksToCheck) {
+ if (ScheduledTask.TaskStatus.RUNNING.equals(task.getStatus())) {
+ try {
+ stateManager.updateTaskState(task, ScheduledTask.TaskStatus.CRASHED,
+ "Interrupted by scheduler shutdown", nodeId);
+ if (task.isPersistent() && persistenceProvider != null) {
+ persistenceProvider.saveTask(task);
+ }
+ } catch (Exception e) {
+ LOGGER.warn("Error marking task {} as crashed during shutdown: {}", task.getItemId(), e.getMessage());
+ }
+ }
+ }
+ } catch (Exception e) {
+ LOGGER.debug("Error marking running tasks as crashed during shutdown: {}", e.getMessage());
+ }
+ }
+
// Release all manager references
this.recoveryManager = null;
this.executionManager = null;
diff --git a/services/src/test/java/org/apache/unomi/services/impl/scheduler/SchedulerServiceImplTest.java b/services/src/test/java/org/apache/unomi/services/impl/scheduler/SchedulerServiceImplTest.java
index 3a1b04b920..7a31d62abe 100644
--- a/services/src/test/java/org/apache/unomi/services/impl/scheduler/SchedulerServiceImplTest.java
+++ b/services/src/test/java/org/apache/unomi/services/impl/scheduler/SchedulerServiceImplTest.java
@@ -86,7 +86,7 @@ public class SchedulerServiceImplTest {
/** Maximum number of retries for storage operations */
private static final int MAX_RETRIES = 10;
/** Default timeout for test assertions */
- private static final long TEST_TIMEOUT = 5000; // 5 seconds
+ private static final long TEST_TIMEOUT = 15000; // 15 seconds — extra margin for loaded CI runners
/** Time unit for test timeouts */
private static final TimeUnit TEST_TIME_UNIT = TimeUnit.MILLISECONDS;
/** Lock timeout for testing lock expiration */
@@ -2474,4 +2474,87 @@ public void execute(ScheduledTask task, TaskStatusCallback callback) {
// Clean up
newSchedulerService.preDestroy();
}
+
+ /**
+ * Regression test for the preDestroy() shutdown sequence: a persistent task still in
+ * RUNNING state when the node goes down must be marked CRASHED so the next scheduler
+ * instance can reschedule it via the CRASHED->SCHEDULED transition. This exercises the
+ * direct persistenceProvider/nonPersistentTasks path used during shutdown, bypassing the
+ * getAllTasks()/saveTask() wrappers which become no-ops once shutdownNow is set.
+ */
+ @Test
+ @Tag("RecoveryTests")
+ public void testPreDestroyMarksStaleRunningPersistentTaskAsCrashed() throws Exception {
+ ScheduledTask runningTask = createTestTask("predestroy-crash-test", ScheduledTask.TaskStatus.RUNNING);
+ persistenceService.refresh();
+
+ schedulerService.preDestroy();
+
+ ScheduledTask reloadedTask = persistenceService.load(runningTask.getItemId(), ScheduledTask.class);
+ assertNotNull(reloadedTask, "Task should still exist after shutdown");
+ assertEquals(
+ ScheduledTask.TaskStatus.CRASHED,
+ reloadedTask.getStatus(),
+ "Task still RUNNING at shutdown should be marked CRASHED");
+ assertEquals(
+ "Interrupted by scheduler shutdown",
+ reloadedTask.getLastError(),
+ "Crashed task should record the shutdown as the cause");
+ }
+
+ /**
+ * End-to-end version of the above: a task actually executing (and ignoring the
+ * interrupt sent by executionManager.shutdown()) must still be observed as RUNNING
+ * and marked CRASHED by the time preDestroy() returns.
+ */
+ @Test
+ @Tag("RecoveryTests")
+ public void testPreDestroyMarksActivelyExecutingTaskAsCrashed() throws Exception {
+ String taskType = "predestroy-active-crash-test";
+ CountDownLatch startLatch = new CountDownLatch(1);
+ AtomicBoolean shouldStop = new AtomicBoolean(false);
+
+ TaskExecutor executor = new TaskExecutor() {
+ @Override
+ public String getTaskType() {
+ return taskType;
+ }
+
+ @Override
+ public void execute(ScheduledTask task, TaskStatusCallback callback) {
+ startLatch.countDown();
+ // Block past the shutdown's interrupt so the task is still RUNNING
+ // when preDestroy() scans for crashed tasks.
+ while (!shouldStop.get()) {
+ try {
+ Thread.sleep(TEST_SLEEP);
+ } catch (InterruptedException ignored) {
+ Thread.interrupted(); // clear the flag so the loop keeps running
+ }
+ }
+ }
+ };
+
+ schedulerService.registerTaskExecutor(executor);
+
+ ScheduledTask task = schedulerService.newTask(taskType).disallowParallelExecution().schedule();
+
+ assertTrue(startLatch.await(TEST_TIMEOUT, TEST_TIME_UNIT), "Task should start executing");
+ TestHelper.retryUntil(
+ () -> schedulerService.getTask(task.getItemId()).getStatus(),
+ status -> status == ScheduledTask.TaskStatus.RUNNING);
+
+ try {
+ schedulerService.preDestroy();
+
+ ScheduledTask reloadedTask = persistenceService.load(task.getItemId(), ScheduledTask.class);
+ assertNotNull(reloadedTask, "Task should still exist after shutdown");
+ assertEquals(
+ ScheduledTask.TaskStatus.CRASHED,
+ reloadedTask.getStatus(),
+ "Task actively executing at shutdown should be marked CRASHED");
+ } finally {
+ shouldStop.set(true);
+ }
+ }
}