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); + } + } }